diff --git a/.evergreen/.evg.yml b/.evergreen/.evg.yml new file mode 100644 index 00000000000..49d1c6df521 --- /dev/null +++ b/.evergreen/.evg.yml @@ -0,0 +1,721 @@ +######################################## +# Evergreen Template for MongoDB Drivers +######################################## + +# When a task that used to pass starts to fail +# Go through all versions that may have been skipped to detect +# when the task started failing +stepback: true + +# Mark a failure as a system/bootstrap failure (purple box) rather then a task +# failure by default. +# Actual testing tasks are marked with `type: test` +command_type: system + +# Protect ourself against rogue test case, or curl gone wild, that runs forever +# 12 minutes is the longest we'll ever run +exec_timeout_secs: 3600 # 12 minutes is the longest we'll ever run + +# What to do when evergreen hits the timeout (`post:` tasks are run automatically) +timeout: + - command: shell.exec + params: + script: | + ls -la + +functions: + "fetch source": + # Executes git clone and applies the submitted patch, if any + - command: git.get_project + params: + directory: "src" + # Applies the subitted patch, if any + # Deprecated. Should be removed. But still needed for certain agents (ZAP) + - command: git.apply_patch + # Make an evergreen exapanstion file with dynamic values + - command: shell.exec + params: + working_dir: "src" + script: | + # Get the current unique version of this checkout + if [ "${is_patch}" = "true" ]; then + CURRENT_VERSION=$(git describe)-patch-${version_id} + else + CURRENT_VERSION=latest + fi + + export DRIVERS_TOOLS="$(pwd)/../drivers-tools" + + # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory + if [ "Windows_NT" == "$OS" ]; then # Magic variable in cygwin + export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS) + fi + + export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration" + export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" + export UPLOAD_BUCKET="${project}" + export PROJECT_DIRECTORY="$(pwd)" + + cat < expansion.yml + CURRENT_VERSION: "$CURRENT_VERSION" + DRIVERS_TOOLS: "$DRIVERS_TOOLS" + MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME" + MONGODB_BINARIES: "$MONGODB_BINARIES" + UPLOAD_BUCKET: "$UPLOAD_BUCKET" + PROJECT_DIRECTORY: "$PROJECT_DIRECTORY" + PREPARE_SHELL: | + set -o errexit + set -o xtrace + export DRIVERS_TOOLS="$DRIVERS_TOOLS" + export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME" + export MONGODB_BINARIES="$MONGODB_BINARIES" + export UPLOAD_BUCKET="$UPLOAD_BUCKET" + export PROJECT_DIRECTORY="$PROJECT_DIRECTORY" + + export TMPDIR="$MONGO_ORCHESTRATION_HOME/db" + export PATH="$MONGODB_BINARIES:$PATH" + export PROJECT="${project}" + EOT + # See what we've done + cat expansion.yml + + # Load the expansion file to make an evergreen variable with the current unique version + - command: expansions.update + params: + file: src/expansion.yml + + "prepare resources": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + rm -rf $DRIVERS_TOOLS + if [ "${project}" = "drivers-tools" ]; then + # If this was a patch build, doing a fresh clone would not actually test the patch + cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS + else + git clone git://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS + fi + echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config + + # Upload build artifacts that other tasks may depend on + # Note this URL needs to be totally unique, while predictable for the next task + # so it can automatically download the artifacts + "upload build": + # Compress and upload the entire build directory + - command: archive.targz_pack + params: + # Example: mongo_c_driver_releng_9dfb7d741efbca16faa7859b9349d7a942273e43_16_11_08_19_29_52.tar.gz + target: "${build_id}.tar.gz" + source_dir: ${PROJECT_DIRECTORY}/ + include: + - "./**" + - command: s3.put + params: + aws_key: ${aws_key} + aws_secret: ${aws_secret} + local_file: ${build_id}.tar.gz + # Example: /mciuploads/${UPLOAD_BUCKET}/gcc49/9dfb7d741efbca16faa7859b9349d7a942273e43/debug-compile-nosasl-nossl/mongo_c_driver_releng_9dfb7d741efbca16faa7859b9349d7a942273e43_16_11_08_19_29_52.tar.gz + remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${task_name}/${build_id}.tar.gz + bucket: mciuploads + permissions: public-read + content_type: ${content_type|application/x-gzip} + + "exec script" : + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + ${PROJECT_DIRECTORY}/${file} + + "upload mo artifacts": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + find $MONGO_ORCHESTRATION_HOME -name \*.log | xargs tar czf mongodb-logs.tar.gz + - command: s3.put + params: + aws_key: ${aws_key} + aws_secret: ${aws_secret} + local_file: mongodb-logs.tar.gz + remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz + bucket: mciuploads + permissions: public-read + content_type: ${content_type|application/x-gzip} + display_name: "mongodb-logs.tar.gz" + - command: s3.put + params: + aws_key: ${aws_key} + aws_secret: ${aws_secret} + local_file: drivers-tools/.evergreen/orchestration/server.log + remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-orchestration.log + bucket: mciuploads + permissions: public-read + content_type: ${content_type|text/plain} + display_name: "orchestration.log" + + "upload working dir": + - command: archive.targz_pack + params: + target: "working-dir.tar.gz" + source_dir: ${PROJECT_DIRECTORY}/ + include: + - "./**" + - command: s3.put + params: + aws_key: ${aws_key} + aws_secret: ${aws_secret} + local_file: working-dir.tar.gz + remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-working-dir.tar.gz + bucket: mciuploads + permissions: public-read + content_type: ${content_type|application/x-gzip} + display_name: "working-dir.tar.gz" + - command: archive.targz_pack + params: + target: "drivers-dir.tar.gz" + source_dir: ${DRIVERS_TOOLS} + include: + - "./**" + - command: s3.put + params: + aws_key: ${aws_key} + aws_secret: ${aws_secret} + local_file: drivers-dir.tar.gz + remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-drivers-dir.tar.gz + bucket: mciuploads + permissions: public-read + content_type: ${content_type|application/x-gzip} + display_name: "drivers-dir.tar.gz" + + "upload test results": + - command: attach.xunit_results + params: + file: ./src/*/build/test-results/test/TEST-*.xml + + "bootstrap mongo-orchestration": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + MONGODB_VERSION=${VERSION} TOPOLOGY=${TOPOLOGY} AUTH=${AUTH} SSL=${SSL} STORAGE_ENGINE=${STORAGE_ENGINE} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh + # run-orchestration generates expansion file with the MONGODB_URI for the cluster + - command: expansions.update + params: + file: mo-expansion.yml + + "run tests": + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + AUTH="${AUTH}" SSL="${SSL}" MONGODB_URI="${MONGODB_URI}" SAFE_FOR_MULTI_MONGOS="${SAFE_FOR_MULTI_MONGOS}" TOPOLOGY="${TOPOLOGY}" COMPRESSOR="${COMPRESSOR}" JDK="${JDK}" AWS_ACCESS_KEY_ID=${aws_access_key_id} AWS_SECRET_ACCESS_KEY=${aws_secret_access_key} .evergreen/run-tests.sh + + "run slow tests": + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + AUTH="${AUTH}" SSL="${SSL}" MONGODB_URI="${MONGODB_URI}" TOPOLOGY="${TOPOLOGY}" COMPRESSOR="${COMPRESSOR}" JDK="${JDK}" SLOW_TESTS_ONLY=true .evergreen/run-tests.sh + + "run socket tests": + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + AUTH="${AUTH}" MONGODB_URI="${MONGODB_URI}" TOPOLOGY="${TOPOLOGY}" COMPRESSOR="${COMPRESSOR}" JDK="${JDK}" .evergreen/run-socket-tests.sh + + "run embedded tests": + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + JDK="${JDK}" .evergreen/run-embedded-tests.sh + + "run plain auth test": + - command: shell.exec + type: test + params: + silent: true + working_dir: "src" + script: | + # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) + JDK="jdk8" MONGODB_URI="${plain_auth_mongodb_uri}" .evergreen/run-plain-auth-test.sh + + "run gssapi auth test": + - command: shell.exec + type: test + params: + silent: true + working_dir: "src" + script: | + # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) + PROJECT_DIRECTORY=${PROJECT_DIRECTORY} JDK=${JDK} MONGODB_URI=${gssapi_auth_mongodb_uri} KDC=${gssapi_auth_kdc} REALM=${gssapi_auth_realm} KEYTAB_BASE64=${gssapi_auth_keytab_base64} .evergreen/run-gssapi-auth-test.sh + + "run mmapv1 storage test": + - command: shell.exec + type: test + params: + silent: true + working_dir: "src" + script: | + ${PREPARE_SHELL} + PROJECT_DIRECTORY=${PROJECT_DIRECTORY} JDK=${JDK} TOPOLOGY=${TOPOLOGY} STORAGE_ENGINE=${STORAGE_ENGINE} MONGODB_URI="${MONGODB_URI}" .evergreen/run-mmapv1-storage-test.sh + + "run atlas test": + - command: shell.exec + type: test + params: + silent: true + working_dir: "src" + script: | + # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) + JDK="jdk8" .evergreen/run-connectivity-tests.sh '${atlas_free_tier_uri}' '${atlas_replica_set_uri}' '${atlas_sharded_uri}' '${atlas_tls_v11_uri}' '${atlas_tls_v12_uri}' '${atlas_free_tier_uri_srv}' '${atlas_replica_set_uri_srv}' '${atlas_sharded_uri_srv}' '${atlas_tls_v11_uri_srv}' '${atlas_tls_v12_uri_srv}' + + "publish snapshot": + - command: shell.exec + type: test + params: + silent: true + working_dir: "src" + script: | + # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) + PROJECT_DIRECTORY=${PROJECT_DIRECTORY} NEXUS_USERNAME=${nexus_username} NEXUS_PASSWORD=${nexus_password} SIGNING_PASSWORD=${signing_password} SIGNING_KEY_ID=${signing_keyId} RING_FILE_GPG_BASE64=${ring_file_gpg_base64} .evergreen/publish.sh + + "cleanup": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + cd "$MONGO_ORCHESTRATION_HOME" + # source the mongo-orchestration virtualenv if it exists + if [ -f venv/bin/activate ]; then + . venv/bin/activate + elif [ -f venv/Scripts/activate ]; then + . venv/Scripts/activate + fi + mongo-orchestration stop || true + cd - + rm -rf $DRIVERS_TOOLS || true + + "fix absolute paths": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + for filename in $(find ${DRIVERS_TOOLS} -name \*.json); do + perl -p -i -e "s|ABSOLUTE_PATH_REPLACEMENT_TOKEN|${DRIVERS_TOOLS}|g" $filename + done + + "windows fix": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do + cat $i | tr -d '\r' > $i.new + mv $i.new $i + done + + "make files executable": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do + chmod +x $i + done + + "init test-results": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + echo '{"results": [{ "status": "FAIL", "test_file": "Build", "log_raw": "No test-results.json found was created" } ]}' > ${PROJECT_DIRECTORY}/test-results.json + + "run perf tests": + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + PROJECT_DIRECTORY=${PROJECT_DIRECTORY} .evergreen/run-perf-tests.sh + + "send dashboard data": + - command: json.send + params: + name: perf + file: src/results.json + +# Anchors + +hosts: &hosts + - rhel70-small + +pre: + - func: "fetch source" + - func: "prepare resources" + - func: "windows fix" + - func: "fix absolute paths" + - func: "init test-results" + - func: "make files executable" + +post: + # Removed, causing timeouts + # - func: "upload working dir" + - func: "upload mo artifacts" + - func: "upload test results" + - func: "cleanup" + +tasks: + + # Compile / check build variant + - name: static-analysis + commands: + - func: "exec script" + vars: + file: ".evergreen/compile.sh" + - func: "upload build" + + - name: "test" + commands: + - func: "bootstrap mongo-orchestration" + - func: "run tests" + + - name: "plain-auth-test" + commands: + - func: "run plain auth test" + + - name: "gssapi-auth-test" + commands: + - func: "run gssapi auth test" + + - name: "slow-test" + commands: + - func: "bootstrap mongo-orchestration" + - func: "run slow tests" + + - name: "socket-test" + commands: + - func: "bootstrap mongo-orchestration" + - func: "run socket tests" + + - name: "embedded-test" + commands: + - func: "run embedded tests" + + - name: "atlas-test" + commands: + - func: "run atlas test" + + - name: publish-snapshot + depends_on: + - variant: "static-checks" + name: "static-analysis" + - variant: ".tests-variant" + name: "test" + - variant: ".tests-slow-variant" + name: "slow-test" + - variant: ".test-gssapi-variant" + name: "gssapi-auth-test" + - variant: "plain-auth-test" + name: "plain-auth-test" + - variant: ".test-embedded-variant" + name: "embedded-test" + commands: + - func: "publish snapshot" + + - name: "perf" + tags: ["perf"] + commands: + - func: "bootstrap mongo-orchestration" + - func: "run perf tests" + - func: "send dashboard data" + + - name: "mmapv1-storage-test" + commands: + - func: "bootstrap mongo-orchestration" + - func: "run mmapv1 storage test" + +axes: + - id: version + display_name: MongoDB Version + values: + - id: "latest" + display_name: "latest" + variables: + VERSION: "latest" + # Multiple mongos instances can be specified in the connection string + # for this version. + SAFE_FOR_MULTI_MONGOS: true + - id: "4.2" + display_name: "4.2" + variables: + VERSION: "4.2" + # Multiple mongos instances can be specified in the connection string + # for this version. + SAFE_FOR_MULTI_MONGOS: true + - id: "4.0" + display_name: "4.0" + variables: + VERSION: "4.0" + - id: "3.6" + display_name: "3.6" + variables: + VERSION: "3.6" + - id: "3.4" + display_name: "3.4" + variables: + VERSION: "3.4" + - id: "3.2" + display_name: "3.2" + variables: + VERSION: "3.2" + - id: "3.0" + display_name: "3.0" + variables: + VERSION: "3.0" + - id: "2.6" + display_name: "2.6" + variables: + VERSION: "2.6" + - id: os + display_name: OS + values: + - id: "linux" + display_name: "Linux" + run_on: *hosts + - id: "ubuntu" + display_name: "Ubuntu 18.04" + run_on: ubuntu1804-test + + - id: topology + display_name: Topology + values: + - id: "standalone" + display_name: Standalone + variables: + TOPOLOGY: "server" + - id: "replicaset" + display_name: Replica Set + variables: + TOPOLOGY: "replica_set" + - id: "sharded-cluster" + display_name: Sharded Cluster + variables: + TOPOLOGY: "sharded_cluster" + - id: auth + display_name: Authentication + values: + - id: "auth" + display_name: Auth + variables: + AUTH: "auth" + - id: "noauth" + display_name: NoAuth + variables: + AUTH: "noauth" + - id: ssl + display_name: SSL + values: + - id: "ssl" + display_name: SSL + variables: + SSL: "ssl" + - id: "nossl" + display_name: NoSSL + variables: + SSL: "nossl" + - id: compressor + display_name: Compressor + values: + - id: "snappy" + display_name: Snappy + variables: + COMPRESSOR: "snappy" + - id: "zlib" + display_name: Zlib + variables: + COMPRESSOR: "zlib" + - id: "zstd" + display_name: Zstd + variables: + COMPRESSOR: "zstd" + - id: jdk + display_name: JDK + values: + - id: "jdk11" + display_name: JDK11 + variables: + JDK: "jdk11" + - id: "jdk9" + display_name: JDK9 + variables: + JDK: "jdk9" + - id: "jdk8" + display_name: JDK8 + variables: + JDK: "jdk8" + - id: "jdk7" + display_name: JDK7 + variables: + JDK: "jdk7" + - id: "jdk6" + display_name: JDK6 + variables: + JDK: "jdk6" + + # Choice of MongoDB storage engine + - id: storage-engine + display_name: Storage + values: + - id: mmapv1 + display_name: MMAPv1 + variables: + STORAGE_ENGINE: "mmapv1" + +buildvariants: + +# Test packaging and other release related routines +- name: static-checks + display_name: "Static Checks" + run_on: *hosts + tasks: + - name: "static-analysis" + +- matrix_name: "tests-zlib-compression" + matrix_spec: { compressor : "zlib", auth: "noauth", ssl: "nossl", jdk: "jdk6", version: ["3.6", "4.0", "4.2", "latest"], topology: "standalone", os: "linux" } + display_name: "${version} ${compressor} ${topology} ${auth} ${ssl} ${jdk} ${os} " + tags: ["tests-variant"] + tasks: + - name: "test" + +- matrix_name: "tests-snappy-compression" + matrix_spec: { compressor : "snappy", auth: "noauth", ssl: "nossl", jdk: "jdk7", version: ["3.4", "3.6", "4.0", "4.2", "latest"], topology: "standalone", os: "linux" } + display_name: "${version} ${compressor} ${topology} ${auth} ${ssl} ${jdk} ${os} " + tags: ["tests-variant"] + tasks: + - name: "test" + +- matrix_name: "tests-zstd-compression" + matrix_spec: { compressor : "zstd", auth: "noauth", ssl: "nossl", jdk: "jdk7", version: ["4.2", "latest"], topology: "standalone", os: "linux" } + display_name: "${version} ${compressor} ${topology} ${auth} ${ssl} ${jdk} ${os} " + tags: ["tests-variant"] + tasks: + - name: "test" + +- matrix_name: "tests-jdk6-unsecure" + matrix_spec: { auth: "noauth", ssl: "nossl", jdk: "jdk6", version: "*", topology: "*", os: "linux" } + display_name: "${version} ${topology} ${auth} ${ssl} ${jdk} ${os} " + tags: ["tests-variant"] + tasks: + - name: "test" + +- matrix_name: "tests-jdk6-secure" + matrix_spec: { auth: "auth", ssl: "ssl", jdk: "jdk6", version: "*", topology: "*", os: "linux" } + exclude_spec: { auth: "auth", ssl: "ssl", jdk: "jdk6", version: ["4.0", "4.2", "latest"], topology: "*", os: "linux" } + display_name: "${version} ${topology} ${auth} ${ssl} ${jdk} ${os} " + tags: ["tests-variant"] + tasks: + - name: "test" + +- matrix_name: "tests-jdk-newer-secure" + matrix_spec: { auth: "auth", ssl: "ssl", jdk: ["jdk8", "jdk11"], version: ["4.0", "4.2", "latest"], topology: "*", os: "linux" } + display_name: "${version} ${topology} ${auth} ${ssl} ${jdk} ${os} " + tags: ["tests-variant"] + tasks: + - name: "test" + +- matrix_name: "tests-jdk7-secure" + matrix_spec: { auth: "auth", ssl: "ssl", jdk: "jdk7", version: "3.6", topology: "standalone", os: "linux" } + display_name: "${version} ${topology} ${auth} ${ssl} ${jdk} ${os} " + tags: ["tests-variant"] + tasks: + - name: "test" + +- matrix_name: "tests-slow" + matrix_spec: { auth: "noauth", ssl: "*", jdk: "jdk8", version: ["4.2"], topology: "*", os: "linux" } + display_name: "Slow: ${version} ${topology} ${ssl} ${jdk} ${os} " + tags: ["tests-slow-variant"] + tasks: + - name: "slow-test" + +- matrix_name: "tests-socket" + matrix_spec: { auth: "*", ssl: "nossl", jdk: "jdk8", version: ["4.2"], topology: "standalone", os: "linux" } + display_name: "Socket: ${version} ${topology} ${auth} ${jdk} ${os} " + tags: ["tests-socket-variant"] + tasks: + - name: "socket-test" + +- matrix_name: "tests-socket-snappy-compression" + matrix_spec: { compressor : "snappy", auth: "noauth", ssl: "nossl", jdk: "jdk7", version: ["4.2"], topology: "standalone", os: "linux" } + display_name: "Socket: ${version} ${compressor} ${topology} ${auth} ${jdk} ${os} " + tags: ["tests-socket-variant"] + tasks: + - name: "socket-test" + +- matrix_name: "tests-socket-zstd-compression" + matrix_spec: { compressor : "zstd", auth: "noauth", ssl: "nossl", jdk: "jdk7", version: ["4.2"], topology: "standalone", os: "linux" } + display_name: "Socket: ${version} ${compressor} ${topology} ${auth} ${jdk} ${os} " + tags: ["tests-socket-variant"] + tasks: + - name: "socket-test" + +- matrix_name: "test-gssapi" + matrix_spec: { jdk: "*", os: "linux" } + display_name: "GSSAPI (Kerberos) Auth test ${jdk} ${os} " + tags: ["test-gssapi-variant"] + tasks: + - name: "gssapi-auth-test" + +- matrix_name: "perf" + matrix_spec: { auth: "noauth", ssl: "nossl", jdk: "jdk9", version: "*", topology: "standalone", os: "linux" } + batchtime: 1440 # run once a day + display_name: "Perf Tests ${version} " + tags: ["perf-variant"] + tasks: + - name: "perf" + +- matrix_name: "tests-embedded" + matrix_spec: { auth: "noauth", ssl: "nossl", jdk: "*", version: ["4.2"], topology: "standalone", os: "ubuntu" } + exclude_spec: { auth: "*", ssl: "*", jdk: "jdk6", version: "*", topology: "*", os: "*" } + display_name: "Embedded: ${jdk}" + tags: ["test-embedded-variant"] + tasks: + - name: "embedded-test" + +- name: plain-auth-test + display_name: "PLAIN (LDAP) Auth test" + run_on: *hosts + tasks: + - name: "plain-auth-test" + +- name: atlas-test + display_name: "Atlas test" + run_on: *hosts + tasks: + - name: "atlas-test" + +- name: publish-snapshot + display_name: "Publish Snapshot" + run_on: ubuntu1804-test + tasks: + - name: "publish-snapshot" + +- matrix_name: "tests-storage-engines" + matrix_spec: { auth: "noauth", ssl: "nossl", jdk: "jdk8", os: "linux", version: ["3.6", "4.0"], topology: ["replicaset", "sharded-cluster"], storage-engine: "mmapv1" } + display_name: "${version} Storage ${storage-engine} ${jdk} ${os} ${topology}" + tasks: + - name: "mmapv1-storage-test" diff --git a/.evergreen/compile.sh b/.evergreen/compile.sh new file mode 100755 index 00000000000..1e1adb970c2 --- /dev/null +++ b/.evergreen/compile.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -o xtrace # Write all commands first to stderr +set -o errexit # Exit the script with error if any of the commands fail + +############################################ +# Main Program # +############################################ + +echo "Compiling java driver with jdk11" + +# We always compile with the latest version of java +export JAVA_HOME="/opt/java/jdk11" +./gradlew -version +./gradlew -PxmlReports.enabled=true --info -x test clean check jar testClasses docs diff --git a/.evergreen/publish.sh b/.evergreen/publish.sh new file mode 100755 index 00000000000..74c6b35acb7 --- /dev/null +++ b/.evergreen/publish.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# DO NOT ECHO COMMANDS AS THEY CONTAIN SECRETS! + +set -o errexit # Exit the script with error if any of the commands fail + +############################################ +# Main Program # +############################################ + +echo ${RING_FILE_GPG_BASE64} | base64 -d > ${PROJECT_DIRECTORY}/secring.gpg + +trap "rm ${PROJECT_DIRECTORY}/secring.gpg; exit" EXIT HUP + + + +export ORG_GRADLE_PROJECT_nexusUsername=${NEXUS_USERNAME} +export ORG_GRADLE_PROJECT_nexusPassword=${NEXUS_PASSWORD} +export ORG_GRADLE_PROJECT_signing_keyId=${SIGNING_KEY_ID} +export ORG_GRADLE_PROJECT_signing_password=${SIGNING_PASSWORD} +export ORG_GRADLE_PROJECT_signing_secretKeyRingFile=${PROJECT_DIRECTORY}/secring.gpg + +echo "Publishing snapshot with jdk11" +export JAVA_HOME="/opt/java/jdk11" + +./gradlew -version +./gradlew publishSnapshots diff --git a/.evergreen/run-connectivity-tests.sh b/.evergreen/run-connectivity-tests.sh new file mode 100755 index 00000000000..057fe88c8a8 --- /dev/null +++ b/.evergreen/run-connectivity-tests.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Don't trace since the URI contains a password that shouldn't show up in the logs +set -o errexit # Exit the script with error if any of the commands fail + +# Supported/used environment variables: +# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java +# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9" +# Support arguments: +# Pass as many MongoDB URIS as arguments to this script as required + +JDK=${JDK:-jdk} + +############################################ +# Main Program # +############################################ + +echo "Running connectivity tests with ${JDK}" + +# We always compile with the latest version of java +export JAVA_HOME="/opt/java/jdk11" + +./gradlew -version + +for MONGODB_URI in $@; do + ./gradlew -PjdkHome=/opt/java/${JDK} -Dorg.mongodb.test.uri=${MONGODB_URI} --stacktrace --info -Dtest.single=ConnectivityTest --rerun-tasks driver-sync:test + ./gradlew -PjdkHome=/opt/java/${JDK} -Dorg.mongodb.test.uri=${MONGODB_URI} --stacktrace --info -Dtest.single=ConnectivityTest --rerun-tasks driver-async:test + ./gradlew -PjdkHome=/opt/java/${JDK} -Dorg.mongodb.test.uri=${MONGODB_URI} --stacktrace --info -Dtest.single=ConnectivityTest --rerun-tasks driver-legacy:test +done diff --git a/.evergreen/run-embedded-tests.sh b/.evergreen/run-embedded-tests.sh new file mode 100755 index 00000000000..960c2beab5b --- /dev/null +++ b/.evergreen/run-embedded-tests.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -o xtrace # Write all commands first to stderr +set -o errexit # Exit the script with error if any of the commands fail + +# Supported/used environment variables: +# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java +# "jdk6", "jdk7", "jdk8", "jdk9" + +JDK=${JDK:-jdk} + +############################################ +# Main Program # +############################################ + +# We always compile with the latest version of java +export JAVA_HOME="/opt/java/jdk11" + +export EMBEDDED_PATH="${PROJECT_DIRECTORY}/tmp/mongo-embedded-java" +mkdir -p ${EMBEDDED_PATH} + +echo "Running tests with ${JDK}" +./gradlew -version +./gradlew -PjdkHome=/opt/java/${JDK} --stacktrace --info :driver-embedded:test -Dorg.mongodb.test.embedded.path=${EMBEDDED_PATH} diff --git a/.evergreen/run-gssapi-auth-test.sh b/.evergreen/run-gssapi-auth-test.sh new file mode 100755 index 00000000000..0d791dc7a98 --- /dev/null +++ b/.evergreen/run-gssapi-auth-test.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Don't trace since the URI contains a password that shouldn't show up in the logs +set -o errexit # Exit the script with error if any of the commands fail + +# Supported/used environment variables: +# MONGODB_URI Set the URI, including username/password to use to connect to the server via PLAIN authentication mechanism +# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java +# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9", "jdk11" +# KDC The KDC +# REALM The realm +# KEYTAB_BASE64 The BASE64-encoded keytab +# PROJECT_DIRECTORY The project directory + +JDK=${JDK:-jdk} + +############################################ +# Main Program # +############################################ + +echo "Running GSSAPI authentication tests" + +echo ${KEYTAB_BASE64} | base64 -d > ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab + +trap "rm ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab; exit" EXIT HUP + +cat << EOF > .evergreen/java.login.drivers.config +com.sun.security.jgss.krb5.initiate { + com.sun.security.auth.module.Krb5LoginModule required + doNotPrompt=true useKeyTab=true keyTab="${PROJECT_DIRECTORY}/.evergreen/drivers.keytab" principal=drivers; +}; +EOF + +echo "Compiling java driver with jdk11" + +# We always compile with the latest version of java +export JAVA_HOME="/opt/java/jdk11" + +echo "Running tests with ${JDK}" +./gradlew -version +./gradlew -PjdkHome=/opt/java/${JDK} --stacktrace --info \ +-Dorg.mongodb.test.uri=${MONGODB_URI} \ +-Pgssapi.enabled=true -Psun.security.krb5.debug=true -Pauth.login.config=file://${PROJECT_DIRECTORY}/.evergreen/java.login.drivers.config \ +-Pkrb5.kdc=${KDC} -Pkrb5.realm=${REALM} -Psun.security.krb5.debug=true \ +-Dtest.single=GSSAPIAuthenticationSpecification driver-core:test diff --git a/.evergreen/run-mmapv1-storage-test.sh b/.evergreen/run-mmapv1-storage-test.sh new file mode 100644 index 00000000000..3ca436b8d89 --- /dev/null +++ b/.evergreen/run-mmapv1-storage-test.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -o errexit # Exit the script with error if any of the commands fail + +# Supported/used environment variables: +# MONGODB_URI Set the URI, including username/password to use to connect to the server via PLAIN authentication mechanism +# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java +# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9" + +JDK=${JDK:-jdk} + +############################################ +# Main Program # +############################################ + +echo "Running MMAPv1 Storage Test" + +echo "Compiling java driver with jdk11" + +# We always compile with the latest version of java +export JAVA_HOME="/opt/java/jdk11" + +echo "Running tests with ${JDK}" +./gradlew -version +./gradlew -PjdkHome=/opt/java/${JDK} --stacktrace --info \ +-Dorg.mongodb.test.uri=${MONGODB_URI} \ +-Dtest.single=RetryableWritesProseTest driver-sync:test driver-async:test diff --git a/.evergreen/run-perf-tests.sh b/.evergreen/run-perf-tests.sh new file mode 100755 index 00000000000..7bf115383be --- /dev/null +++ b/.evergreen/run-perf-tests.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -o xtrace +set -o errexit + +rm -rf driver-performance-test-data +git clone https://github.com/mongodb-labs/driver-performance-test-data.git +cd driver-performance-test-data +tar xf extended_bson.tgz +tar xf parallel.tgz +tar xf single_and_multi_document.tgz +cd .. + +export JAVA_HOME="/opt/java/jdk11" + +export TEST_PATH="${PROJECT_DIRECTORY}/driver-performance-test-data/" +export OUTPUT_FILE="${PROJECT_DIRECTORY}/results.json" + +start_time=$(date +%s) +./gradlew -Dorg.mongodb.benchmarks.data=${TEST_PATH} -Dorg.mongodb.benchmarks.output=${OUTPUT_FILE} driver-benchmarks:run +end_time=$(date +%s) +elapsed_secs=$((end_time-start_time)) + diff --git a/.evergreen/run-plain-auth-test.sh b/.evergreen/run-plain-auth-test.sh new file mode 100755 index 00000000000..c72cb3a2dce --- /dev/null +++ b/.evergreen/run-plain-auth-test.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Don't trace since the URI contains a password that shouldn't show up in the logs +set -o errexit # Exit the script with error if any of the commands fail + +# Supported/used environment variables: +# MONGODB_URI Set the URI, including username/password to use to connect to the server via PLAIN authentication mechanism +# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java +# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9" + +JDK=${JDK:-jdk} + +############################################ +# Main Program # +############################################ + +echo "Running PLAIN authentication tests" + +# We always compile with the latest version of java +export JAVA_HOME="/opt/java/jdk11" + +echo "Running tests with ${JDK}" +./gradlew -version +./gradlew -PjdkHome=/opt/java/${JDK} -Dorg.mongodb.test.uri=${MONGODB_URI} --stacktrace --info -Dtest.single=PlainAuthenticationSpecification driver-core:test diff --git a/.evergreen/run-socket-tests.sh b/.evergreen/run-socket-tests.sh new file mode 100755 index 00000000000..b7c4d634d2c --- /dev/null +++ b/.evergreen/run-socket-tests.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +set -o xtrace # Write all commands first to stderr +set -o errexit # Exit the script with error if any of the commands fail + +# Supported/used environment variables: +# AUTH Set to enable authentication. Values are: "auth" / "noauth" (default) +# MONGODB_URI Set the suggested connection MONGODB_URI (including credentials and topology info) +# TOPOLOGY Allows you to modify variables and the MONGODB_URI based on test topology +# Supported values: "server", "replica_set", "sharded_cluster" +# COMPRESSOR Set to enable compression. Values are "snappy" and "zlib" (default is no compression) +# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java +# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9", "jdk11" + +AUTH=${AUTH:-noauth} +MONGODB_URI=${MONGODB_URI:-} +JDK=${JDK:-jdk} +TOPOLOGY=${TOPOLOGY:-server} +COMPRESSOR=${COMPRESSOR:-} + +############################################ +# Main Program # +############################################ + +SOCKET_REGEX='(.*)localhost:([0-9]+)?(.*)' +while [[ $MONGODB_URI =~ $SOCKET_REGEX ]]; do + MONGODB_URI="${BASH_REMATCH[1]}%2Ftmp%2Fmongodb-${BASH_REMATCH[2]}.sock${BASH_REMATCH[3]}" +done + +# Provision the correct connection string and set up SSL if needed +if [ "$TOPOLOGY" == "sharded_cluster" ]; then + + if [ "$AUTH" = "auth" ]; then + export MONGODB_URI="mongodb://bob:pwd123@%2Ftmp%2Fmongodb-27017.sock/?authSource=admin" + else + export MONGODB_URI="mongodb://%2Ftmp%2Fmongodb-27017.sock/" + fi +fi + +if [ "$COMPRESSOR" != "" ]; then + if [[ "$MONGODB_URI" == *"?"* ]]; then + export MONGODB_URI="${MONGODB_URI}&compressors=${COMPRESSOR}" + else + export MONGODB_URI="${MONGODB_URI}/?compressors=${COMPRESSOR}" + fi +fi + +echo "Running $AUTH tests over for $TOPOLOGY and connecting to $MONGODB_URI" + +# We always compile with the latest version of java +export JAVA_HOME="/opt/java/jdk11" + +echo "Running tests with ${JDK}" +./gradlew -version +./gradlew -PjdkHome=/opt/java/${JDK} -Dorg.mongodb.test.uri=${MONGODB_URI} ${GRADLE_EXTRA_VARS} --stacktrace --info :driver-legacy:test :driver-sync:test diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh new file mode 100755 index 00000000000..040ce207a03 --- /dev/null +++ b/.evergreen/run-tests.sh @@ -0,0 +1,126 @@ +#!/bin/bash + +set -o xtrace # Write all commands first to stderr +set -o errexit # Exit the script with error if any of the commands fail + +# Supported/used environment variables: +# AUTH Set to enable authentication. Values are: "auth" / "noauth" (default) +# SSL Set to enable SSL. Values are "ssl" / "nossl" (default) +# MONGODB_URI Set the suggested connection MONGODB_URI (including credentials and topology info) +# TOPOLOGY Allows you to modify variables and the MONGODB_URI based on test topology +# Supported values: "server", "replica_set", "sharded_cluster" +# COMPRESSOR Set to enable compression. Values are "snappy" and "zlib" (default is no compression) +# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java +# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9" +# SLOW_TESTS_ONLY Set to true to only run the slow tests +# AWS_ACCESS_KEY_ID The AWS access key identifier for client-side encryption +# AWS_SECRET_ACCESS_KEY The AWS secret access key for client-side encryption + +AUTH=${AUTH:-noauth} +SSL=${SSL:-nossl} +MONGODB_URI=${MONGODB_URI:-} +JDK=${JDK:-jdk} +TOPOLOGY=${TOPOLOGY:-server} +COMPRESSOR=${COMPRESSOR:-} +SLOW_TESTS_ONLY=${SLOW_TESTS_ONLY:-false} + +# JDK6 needs async.type=netty +if [ "$JDK" == "jdk6" ]; then + export ASYNC_TYPE="-Dorg.mongodb.async.type=netty" +else + export ASYNC_TYPE="-Dorg.mongodb.async.type=nio2" +fi + +# We always compile with the latest version of java +export JAVA_HOME="/opt/java/jdk11" + + +############################################ +# Functions # +############################################ + +provision_ssl () { + echo "SSL !" + + # We generate the keystore and truststore on every run with the certs in the drivers-tools repo + if [ ! -f client.pkc ]; then + openssl pkcs12 -CAfile ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -export -in ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem -out client.pkc -password pass:bithere + fi + + cp ${JAVA_HOME}/lib/security/cacerts mongo-truststore + ${JAVA_HOME}/bin/keytool -importcert -trustcacerts -file ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem -keystore mongo-truststore -storepass changeit -storetype JKS -noprompt + + # We add extra gradle arguments for SSL + export GRADLE_EXTRA_VARS="-Pssl.enabled=true -Pssl.keyStoreType=pkcs12 -Pssl.keyStore=`pwd`/client.pkc -Pssl.keyStorePassword=bithere -Pssl.trustStoreType=jks -Pssl.trustStore=`pwd`/mongo-truststore -Pssl.trustStorePassword=changeit" + # Arguments for auth + SSL + if [ "$AUTH" != "noauth" ] || [ "$TOPOLOGY" == "replica_set" ]; then + export MONGODB_URI="${MONGODB_URI}&ssl=true&sslInvalidHostNameAllowed=true" + if [ "$SAFE_FOR_MULTI_MONGOS" == "true" ]; then + export TRANSACTION_URI="${TRANSACTION_URI}&ssl=true&sslInvalidHostNameAllowed=true" + fi + else + export MONGODB_URI="${MONGODB_URI}/?ssl=true&sslInvalidHostNameAllowed=true" + if [ "$SAFE_FOR_MULTI_MONGOS" == "true" ]; then + export TRANSACTION_URI="${TRANSACTION_URI}/?ssl=true&sslInvalidHostNameAllowed=true" + fi + fi +} + +############################################ +# Main Program # +############################################ + +# Provision the correct connection string and set up SSL if needed +if [ "$TOPOLOGY" == "sharded_cluster" ]; then + if [ "$SAFE_FOR_MULTI_MONGOS" == "true" ]; then + if [ "$AUTH" = "auth" ]; then + export TRANSACTION_URI="mongodb://bob:pwd123@localhost:27017,localhost:27018/?authSource=admin" + else + export TRANSACTION_URI="${MONGODB_URI}" + fi + fi + + if [ "$AUTH" = "auth" ]; then + export MONGODB_URI="mongodb://bob:pwd123@localhost:27017/?authSource=admin" + else + export MONGODB_URI="mongodb://localhost:27017" + fi +fi + +if [ "$COMPRESSOR" != "" ]; then + if [[ "$MONGODB_URI" == *"?"* ]]; then + export MONGODB_URI="${MONGODB_URI}&compressors=${COMPRESSOR}" + else + export MONGODB_URI="${MONGODB_URI}/?compressors=${COMPRESSOR}" + fi + + if [ "$SAFE_FOR_MULTI_MONGOS" == "true" ]; then + if [[ "$TRANSACTION_URI" == *"?"* ]]; then + export TRANSACTION_URI="${TRANSACTION_URI}&compressors=${COMPRESSOR}" + else + export TRANSACTION_URI="${TRANSACTION_URI}/?compressors=${COMPRESSOR}" + fi + fi +fi + +if [ "$SSL" != "nossl" ]; then + provision_ssl +fi + +if [ "$SAFE_FOR_MULTI_MONGOS" == "true" ]; then + export TRANSACTION_URI="-Dorg.mongodb.test.transaction.uri=${TRANSACTION_URI}" +fi + +echo "Running $AUTH tests over $SSL for $TOPOLOGY and connecting to $MONGODB_URI" + +echo "Running tests with ${JDK}" +./gradlew -version + +if [ "$SLOW_TESTS_ONLY" == "true" ]; then + ./gradlew -PjdkHome=/opt/java/${JDK} -Dorg.mongodb.test.uri=${MONGODB_URI} \ + ${TRANSACTION_URI} ${GRADLE_EXTRA_VARS} ${ASYNC_TYPE} --stacktrace --info testSlowOnly +else + ./gradlew -PjdkHome=/opt/java/${JDK} -Dorg.mongodb.test.uri=${MONGODB_URI} \ + -Dorg.mongodb.test.awsAccessKeyId=${AWS_ACCESS_KEY_ID} -Dorg.mongodb.test.awsSecretAccessKey=${AWS_SECRET_ACCESS_KEY} \ + ${TRANSACTION_URI} ${GRADLE_EXTRA_VARS} ${ASYNC_TYPE} --stacktrace --info test +fi diff --git a/.evg.yml b/.evg.yml deleted file mode 100644 index 9c702058ff5..00000000000 --- a/.evg.yml +++ /dev/null @@ -1,218 +0,0 @@ -######################################### -# Java Driver Config for Evergreen # -######################################### - -java_driver_variables: -## Common download urls (merge in as hashes) - mongo_download_url_prefixes: - linux64: &mongo_url_linux64 - mongo_url_prefix: "http://downloads.mongodb.org/linux/mongodb-linux-x86_64-" - ubuntu1404: &mongo_url_ubuntu1404 - mongo_url_platform: "ubuntu1404-" - <<: *mongo_url_linux64 - -####################################### -# Functions # -####################################### - -functions: - "fetch source" : - command: git.get_project - params: - directory: "mongo-java-driver" - - "set topology standalone" : - command: expansions.update - params: - updates: - - key: "orchestration_file" - value: "basic.json" - - key: "topology_type" - value: "server" - - key: "mongodb_test_uri" - value: "mongodb://localhost:27017" - - key: "rs_enabled" - value: "false" - - - "set topology replica set" : - command: expansions.update - params: - updates: - - key: "orchestration_file" - value: "basic.json" - - key: "topology_type" - value: "replica_set" - - key: "mongodb_test_uri" - value: "mongodb://localhost:27017,localhost:27018" - - key: "rs_enabled" - value: "true" - - "set topology sharded" : - command: expansions.update - params: - updates: - - key: "orchestration_file" - value: "basic.json" - - key: "topology_type" - value: "sharded_cluster" - - key: "mongodb_test_uri" - value: "mongodb://localhost:27017" - - key: "rs_enabled" - value: "false" - - "set version latest" : - command: expansions.update - params: - updates: - - key: "mongo_url" - value: ${mongo_url_prefix}${mongo_url_platform|}latest.${mongo_url_extension|tgz} - - "fetch mongodb" : - command: shell.exec - params: - working_dir: "mongo-java-driver" - script: | - set -o verbose - set -o errexit - ls -la - curl -s ${mongo_url} --output mongo-archive.${ext|tgz} - ${decompress} mongo-archive.${ext|tgz} - mv mongodb* mongodb - chmod +x ./mongodb/bin/mongod${extension} - if [ ${windows|false} = true ]; then - rm -rf /cygdrive/c/mongodb - cp -r mongodb /cygdrive/c/mongodb - fi - - "run tests" : - command: shell.exec - params: - working_dir: "mongo-java-driver" - script: | - export ORCHESTRATION_FILE="orchestration_configs/${topology_type}s/${orchestration_file}" - - mkdir -p $(dirname "$ORCHESTRATION_FILE") - - # Borrow MO config from the C driver - # - curl --location "https://raw.githubusercontent.com/mongodb/mongo-c-driver/master/orchestration_configs/${topology_type}s/${orchestration_file}" > "$ORCHESTRATION_FILE" - export ORCHESTRATION_URL="http://localhost:8889/v1/${topology_type}s" - export MONGO_ORCHESTRATION_HOME=/tmp/orchestration-home - - if [ ! -d /tmp/orchestration-home ]; then - mkdir /tmp/orchestration-home - fi - - ${start_mongo_orchestration} - ${start_topology} - ./gradlew -Dorg.mongodb.test.uri=${mongodb_test_uri} -Prs.enabled=${rs_enabled} --stacktrace --info test - - "attach junit results" : - command: attach.xunit_results - params: - file: ./mongo-java-driver/*/build/test-results/TEST-*.xml - -####################################### -# Tasks # -####################################### - -tasks: - - name: compileAndAnalyze - commands: - - func: "fetch source" - - command: git.apply_patch - params: - directory: "mongo-java-driver" - - command: shell.exec - params: - working_dir: "mongo-java-driver" - script: | - ./gradlew -PxmlReports.enabled=true --info -x test clean check jar testClasses - - name: test-standalone - commands: - - func: "fetch source" - - command: git.apply_patch - params: - directory: "mongo-java-driver" - - func: "set topology standalone" - - func: "set version latest" - - func: "fetch mongodb" - - func: "run tests" - - func: "attach junit results" - - name: test-replica-set - commands: - - func: "fetch source" - - command: git.apply_patch - params: - directory: "mongo-java-driver" - - func: "set topology replica set" - - func: "set version latest" - - func: "fetch mongodb" - - func: "run tests" - - func: "attach junit results" - - name: test-sharded - commands: - - func: "fetch source" - - command: git.apply_patch - params: - directory: "mongo-java-driver" - - func: "set topology sharded" - - func: "set version latest" - - func: "fetch mongodb" - - func: "run tests" - - func: "attach junit results" - -scripts: - ## Scripts that are shared between buildvariants - scripts: - mongo_orchestration: - unix: &mongo_orchestration_unix - start_mongo_orchestration: | - trap 'set +o errexit; mongo-orchestration --pidfile /data/mo.pid stop;' EXIT - pidfile=/data/mo.pid - if [ -f $pidfile ]; then - echo "Existing pidfile $pidfile" - cat $pidfile - mongo-orchestration --pidfile /data/mo.pid stop; - rm -f $pidfile - fi - df -h - ls -la - echo "Starting Mongo Orchestration..." - echo "{ \"releases\": { \"default\": \"`pwd`/mongodb/bin\" } }" > orchestration.config - TMPDIR=/data/db mongo-orchestration -f orchestration.config -e default start --socket-timeout-ms=60000 --bind=127.0.0.1 --enable-majority-read-concern --pidfile $pidfile - curl -s http://localhost:8889/ - start_topology_command: &start_topology_command - start_topology: | - curl -s --data @"$ORCHESTRATION_FILE" "$ORCHESTRATION_URL" - ./mongodb/bin/mongo $MONGO_SHELL_CONNECTION_FLAGS --eval 'printjson(db.serverBuildInfo())' admin - ./mongodb/bin/mongo $MONGO_SHELL_CONNECTION_FLAGS --eval 'printjson(db.adminCommand({getCmdLineOpts:1}))' admin - ./mongodb/bin/mongo $MONGO_SHELL_CONNECTION_FLAGS --eval 'printjson(db.isMaster())' admin - -####################################### -# Variants # -####################################### - -buildvariants: -- name: ubuntu-1404 - display_name: "Ubuntu-1404" - expansions: - <<: *start_topology_command - <<: *mongo_url_ubuntu1404 # sets ${mongo_url_prefix} - <<: *mongo_orchestration_unix # sets start_topology and start_mongo_orchestration - run_on: - - ubuntu1404-test - tasks: - - name: compileAndAnalyze - run_on: - - ubuntu1404-build - - name: test-standalone - depends_on: - - name: compileAndAnalyze - - name: test-replica-set - depends_on: - - name: compileAndAnalyze - - name: test-sharded - depends_on: - - name: compileAndAnalyze diff --git a/.gitignore b/.gitignore index db821035a65..941dd18a4a3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ .#* .git *# -*.sh # os x stuff *Thumbs.db* @@ -34,8 +33,18 @@ codereview.rc # local settings **/gradle.properties +local.properties # doc settings docs/reference/public docs/landing/public docs/hugo* + +# jenv +.java-version + +# mongocryptd +**/mongocryptd.pid + +# shell scripts +*.sh \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 7eca32b7673..596a6c1c5ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ +dist: trusty +sudo: true language: java jdk: -- oraclejdk8 +- oraclejdk9 notifications: email: @@ -9,8 +11,6 @@ notifications: - ross@mongodb.com on_success: change on_failure: always - flowdock: - secure: "Ziw1Be2tV0QAYuiYDrepfdNH/oBfNhnXFMji3AdRi6MePVpc6CtOOT/b9Fra\nQgMMrX3AHk/QIDo6QQx9/aVB3FS1fzOPGQkSsEZmiljZU7wZCct1sSSyttf/\nsRG0lyTnmgFNTHyTSDT3JbXAkyF/vJmG/JJJoBUZhmFxzR2fM0Q=" branches: only: @@ -18,31 +18,27 @@ branches: env: global: - - MONGO_REPO="http://repo.mongodb.com/apt/ubuntu" - - REPO_TYPE="precise/mongodb-enterprise/2.6 multiverse" - - SOURCES_LOC="/etc/apt/sources.list.d/mongodb-enterprise.list" - - KEY_SERVER="hkp://keyserver.ubuntu.com:80" - - MONGOD_PARAMS="--setParameter=enableTestCommands=1" - - MONGOD_OPTS="--dbpath ./data --fork --logpath mongod.log ${MONGOD_PARAMS}" - - TERM=dumb - -before_install: - # MongoDB Enterprise Edition 2.6 - - sudo apt-key adv --keyserver ${KEY_SERVER} --recv 7F0CEB10 - - echo "deb ${MONGO_REPO} ${REPO_TYPE}" | sudo tee ${SOURCES_LOC} - - # Update all the repositories - - sudo apt-get update -qq + - MONGODB_FILE_NAME=mongodb-linux-x86_64-enterprise-ubuntu1404 + - MONGODB=3.6.1 + +addons: + apt: + packages: + - libsnmp-dev install: - # Install MongoDB Enterprise - - sudo apt-get install mongodb-enterprise-server - - sudo stop mongod - - mkdir data + - wget http://downloads.mongodb.com/linux/${MONGODB_FILE_NAME}-${MONGODB}.tgz + - tar xzf ${MONGODB_FILE_NAME}-${MONGODB}.tgz + - ${PWD}/${MONGODB_FILE_NAME}-${MONGODB}/bin/mongod --version before_script: - - mongod ${MONGOD_OPTS} + - mkdir ${PWD}/${MONGODB_FILE_NAME}-${MONGODB}/data + - ${PWD}/${MONGODB_FILE_NAME}-${MONGODB}/bin/mongod --dbpath ${PWD}/${MONGODB_FILE_NAME}-${MONGODB}/data --logpath ${PWD}/${MONGODB_FILE_NAME}-${MONGODB}/mongodb.log --setParameter enableTestCommands=1 --fork --smallfiles --nojournal script: - ./gradlew -q assemble - - ./gradlew check -i -S -Ptravistest=true + - ./gradlew check -Ptravistest=true + - ./gradlew docs + +after_script: + - pkill mongod diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb639d527a1..9a2ecd97527 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,9 +17,11 @@ Pull Requests Pull requests should generally be made against the master (default) branch and include relevant tests, if applicable. -Code should compile and tests should pass under all Java versions which the driver currently supports. Currently the Java driver -supports a minimum version of Java 6. Please run './gradlew test' to confirm. By default, running the tests requires that you started a -mongod server on localhost, listening on the default port. At minimum, please test against the latest release version of the MongoDB +Code should compile with the Java 9 compiler and tests should pass under all Java versions which the driver currently +supports. Currently the Java driver supports a minimum version of Java 6. Please run './gradlew test' to confirm. By default, running the +tests requires that you start a mongod server on localhost, listening on the default port and configured to run with +[`enableTestCommands`](http://docs.mongodb.org/manual/reference/parameters/#param.enableTestCommands), which may be set with the +`--setParameter enableTestCommands=1` command-line parameter. At minimum, please test against the latest release version of the MongoDB server. The results of pull request testing will be appended to the request. If any tests do not pass, or relevant tests are not included, the diff --git a/History.md b/History.md deleted file mode 100644 index 863790fb5b7..00000000000 --- a/History.md +++ /dev/null @@ -1,99 +0,0 @@ - -2.7.3 / 2012-01-30 -================== - - * synchronized access to encoder/decoder - * JAVA-505: Made encoder creation work just like decoder creation - + using cached DBDecoder in most cases to avoid excessive memory allocation caused by too many instances of DefaultDBDecoder being created - * JAVA-505 / JAVA-421 - Regression in performance of Java Driver should be rolled back (GC Related) - -2.7.3RC1 / 2012-01-17 -================== - - * Remove defunct BSONInputTest - * JAVA-505 / JAVA-421 - Regression in performance of Java Driver should be rolled back (GC Related) - -2.7.2 / 2011-11-10 -================== - - * JAVA-469: java.net.NetworkInterface.getNetworkInterfaces may fail with IBM JVM, which prevents from using driver - * deprecated replica pair constructors. - - updated javadocs and removed replica pair javadocs in class doc. - * JAVA-428: Fixed an issue where read preferences not set on cursor - -2.7.1 / 2011-11-08 -================== - - * JAVA-467 - added _check call to getServerAddress if _it is null - * JAVA-467 - Moved variable calls to method to fix read preference hierarchy - * simplified getServerAddress method. - -2.7.0 / 2011-11-04 -=================== - - * Released Java Driver 2.7.0 - * See change notes from Release Candidate series - * Please report any bugs or issues to https://jira.mongodb.org/browse/JAVA - - -2.7.0-rc4 / 2011-11-03 -======================= - - * New Secondary Selection tests for Replica Sets introduced - * To correct a regression, make WriteConcern immutable (Otherwise you can mutate static presets like SAFE at runtime) * Reintroduced constructors which accept continueOnErrorForInsert args * To enable you to set continueOnErrorForInsert with the presets like SAFE, immutable "new WriteConcern like this with COEI changed" method added. - -2.7.0-rc3 / 2011-10-31 -======================= - - * changed if statement to improve readability. - * JAVA-462: GridFSInputFile does not close input streams when constructed by the driver (greenlaw110) - - Add closeStreamOnPersist option when creating GridFSInputFile - * JAVA-425 fixes - - attempt to clean up and standardize writeConcern - - throw exception if w is wrong type - - fix cast exception in case W is a String - * Documented continue on error better. - * Close inputstream of GridFSInputFile once chunk saved - * JAVA-461: the logic to spread requests around slaves may select a slave over latency limit - * Reset buffer when the object is checked out and before adding back. - * added MongoOptions test - * use the socket factory from the Mongo for ReplicaSetStatus connections - * added MongoOptions.copy - -2.7.0-rc2 / 2011-10-26 -======================== - - * JAVA-459: smooth the latency measurements (for secondary/slave server selection) - * JAVA-428: Fixed edge cases where slaveOK / ReadPreference.SECONDARY wasn't working - * JAVA-444: make ensureIndex first do a read on index collection to see if index exists * If an embedded field was referenced in an index a hard failure occurred, due to triggering of the 'no dots in key names' logic via the insert. Moved code to use the lower level API method which permits disabling of key checks. - * Introduced "bamboo-test" task which does NOT halt on failure to allow bamboo tests to complete and report properly (and various fixes) - * added unit test for x.y ensureIndex. - -2.7.0-rc1 / 2011-10-24 -======================= - - * JAVA-434: replaced isEmpty() with 1.5 compatible alternative - * JAVA-363: NPE in GridFS.validate - * JAVA-356: make JSONParseException public - * JAVA-413: intern dbref.ns - * JAVA-444: query before insert on index creation. - * JAVA-404: slaveOk support for inline mapreduce (routes to secondaries); changed CommandResult to include serverUsed, made readPref-Secondary set slaveOk query flag, toString/toJSON on Node/ReplicaSetStatus. - * Import javax.net in an OSGi environment. - * JAVA-428 - Support new ReadPreference semantics, deprecate SlaveOK - * Added a skip method to ensure continueOnInsertError test is only run on Server versions 2.0+ - * JAVA-448 - Tailable Cursor behavior while not in AWAIT mode - + Fixed non-await empty tailable cursor behavior to be more consistently inline with other drivers & user expectations. Instead of forcing a sleep of 500 milliseconds on "no data", we instead when tailable catch an empty cursor and return null instead. This should be more safely non blocking for users who need to roll their own event driven code, for which the sleep breaks logic. - * JAVA-439: Check keys to disallow dot in key names for embedded maps - * added getObjectId method. - * add partial query option flag - * JAVA-425 - Support MongoDB Server 2.0 getLastError Changes (j, w=string/number) - * JAVA-427 - Support ContinueOnError Flag for bulk insert - * JAVA-422 - Memoize field locations (and other calculated data) for LazyBSONObject - * JAVA-421 - LazyBSONObject exhibits certain behavioral breakages - * JAVA-420: LazyBSONObject does not handle DBRef - * Fix & cleanup of Javadoc comments (kevinsawicki) - * JAVA-365: Poor concurrency handling in DBApiLayer.doGetCollection - * JAVA-364: MapReduceOutput sometimes returns empty results in a replica set when SLAVE_OK=true - * JAVA-333: Allow bson decoder per operation (find/cursor) - * JAVA-323: When executing a command, only toplevel object returned should be a CommandResult (not sub-objects) - diff --git a/Issues.txt b/Issues.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/LICENSE.txt b/LICENSE.txt index 2f44352e735..261eeb9e9f8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,192 @@ -1) Copyright 2008-2015 MongoDB, Inc + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11,77 +199,3 @@ 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. - -2) The following files: Immutable.java, NotThreadSafe.java, ThreadSafe.java - - Copyright (c) 2005 Brian Goetz and Tim Peierls - Released under the Creative Commons Attribution License (http://creativecommons.org/licenses/by/2.5) - Official home: http://www.jcip.net - - Any republication or derived work distributed in source code form - must include this copyright and license notice. - -3) The following files: Assertions.java, AbstractCopyOnWriteMap.java, CopyOnWriteMap.java - - Copyright (c) 2008-2014 Atlassian Pty Ltd - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -4) The following files: Beta.java - - Copyright 2010 The Guava Authors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -5) The following files: Base64Codec.java - - Copyright 1999,2005 The Apache Software Foundation. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -6) The following files: ReadTimeoutHandler.java - - Copyright 2015 MongoDB, Inc. - Copyright 2012 The Netty Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md index 448deae19b4..489cb537711 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,7 @@ Javadoc for all major and minor releases is available [here](http://api.mongodb. For issues with, questions about, or feedback for the MongoDB Java driver, please look into our [support channels](http://www.mongodb.org/about/support). Please do not email any of the Java driver developers directly with issues or -questions - you're more likely to get an answer on the [mongodb-user] -(http://groups.google.com/group/mongodb-user) list on Google Groups. +questions - you're more likely to get an answer on the [mongodb-user](http://groups.google.com/group/mongodb-user) list on Google Groups. At a minimum, please include in your description the exact version of the driver that you are using. If you are having connectivity issues, it's often also useful to paste in the line of code where you construct the MongoClient instance, @@ -31,8 +30,7 @@ case in our issue management tool, JIRA: Bug reports in JIRA for the driver and the Core Server (i.e. SERVER) project are **public**. If you’ve identified a security vulnerability in a driver or any other -MongoDB project, please report it according to the [instructions here] -(http://docs.mongodb.org/manual/tutorial/create-a-vulnerability-report). +MongoDB project, please report it according to the [instructions here](http://docs.mongodb.org/manual/tutorial/create-a-vulnerability-report). ## Versioning @@ -43,9 +41,9 @@ called out in the release notes. Minor 3.x increments (such as 3.1, 3.2, etc) will occur when non-trivial new functionality is added or significant enhancements or bug fixes occur that may have behavioral changes that may affect some edge cases (such as dependence on behavior resulting from a bug). An example of an enhancement is a method or class added to support new functionality added to the MongoDB server. Minor releases will -almost always be binary compatible with prior minor releases from the same major release branch, exept as noted below. +almost always be binary compatible with prior minor releases from the same major release branch, except as noted below. -Patch 3.x.y increments (such as 3.0.0 -> 3.0.1, 3.1.1 -> 3.1.2, etc) will occur for bug fixes only and will always be binary compitible +Patch 3.x.y increments (such as 3.0.0 -> 3.0.1, 3.1.1 -> 3.1.2, etc) will occur for bug fixes only and will always be binary compatible with prior patch releases of the same minor release branch. #### @Beta @@ -106,7 +104,7 @@ For binaries containing the asynchronous API, see the [driver-async README](driv ## Build -To build and test the driver: +Java 9+ is required to build and compile the source. To build and test the driver: ``` $ git clone https://github.com/mongodb/mongo-java-driver.git @@ -114,12 +112,33 @@ $ cd mongo-java-driver $ ./gradlew check ``` -The test suite requires mongod to be running with [`enableTestCommands`](http://docs.mongodb.org/manual/reference/parameters/#param.enableTestCommands). +The test suite requires mongod to be running with [`enableTestCommands`](http://docs.mongodb.org/manual/reference/parameters/#param.enableTestCommands), which may be set with the `--setParameter enableTestCommands=1` +command-line parameter: +``` +$ mongod --dbpath ./data/db --logpath ./data/mongod.log --port 27017 --logappend --fork --setParameter enableTestCommands=1 +``` + +If you encounter `"Too many open files"` errors when running the tests then you will need to increase +the number of available file descriptors prior to starting mongod as described in [https://docs.mongodb.com/manual/reference/ulimit/](https://docs.mongodb.com/manual/reference/ulimit/) + +## IntelliJ IDEA + +A couple of manual configuration steps are required to run the code in IntelliJ: + +- Java 9+ is required to build and compile the source. + +- **Error:** `java: cannot find symbol: class SNIHostName location: package javax.net.ssl`
+ **Fix:** Settings > Build, Execution, Deployment > Compiler > Java Compiler - untick "Use '--release' option for cross-compilation (Java 9 and later)" +- **Error:** `java: package com.mongodb.internal.build does not exist`
+ **Fixes:** Any of the following:
+ - Run the `compileBuildConfig` task: eg: `./gradlew compileBuildConfig` or via Gradle > driver-core > Tasks > other > compileBuildConfig + - Set `compileBuildConfig` to execute Before Build. via Gradle > Tasks > other > right click compileBuildConfig - click on "Execute Before Build" + - Delegate all build actions to Gradle: Settings > Build, Execution, Deployment > Build Tools > Gradle > Runner - tick "Delegate IDE build/run actions to gradle" ### Build status: -[![Build Status](https://travis-ci.org/mongodb/mongo-java-driver.svg?branch=master)](https://travis-ci.org/mongodb/mongo-java-driver) | [![Build Status](https://jenkins.10gen.com/job/mongo-java-driver-3.x/badge/icon)](https://jenkins.10gen.com/job/mongo-java-driver/) +[![Build Status](https://travis-ci.org/mongodb/mongo-java-driver.svg?branch=master)](https://travis-ci.org/mongodb/mongo-java-driver) ## Maintainers @@ -140,6 +159,5 @@ YourKit is supporting this open source project with its [YourKit Java Profiler]( JetBrains is supporting this open source project with: -[![Intellij IDEA](http://www.jetbrains.com/img/logos/logo_intellij_idea.png)] -(http://www.jetbrains.com/idea/) +[![Intellij IDEA](http://www.jetbrains.com/img/logos/logo_intellij_idea.png)](http://www.jetbrains.com/idea/) diff --git a/THIRD-PARTY-NOTICES b/THIRD-PARTY-NOTICES new file mode 100644 index 00000000000..ee82fd2607f --- /dev/null +++ b/THIRD-PARTY-NOTICES @@ -0,0 +1,150 @@ +The MongoDB Java Driver uses third-party libraries or other resources that may +be distributed under licenses different than the MongoDB Java Driver software. + +In the event that we accidentally failed to list a required notice, +please bring it to our attention through any of the ways detailed here: + + mongodb-dev@googlegroups.com + +The attached notices are provided for information only. + +For any licenses that require disclosure of source, sources are available at +https://github.com/mongodb/mongo-java-driver. + + +1) The following files: Immutable.java, NotThreadSafe.java, ThreadSafe.java + + Copyright (c) 2005 Brian Goetz and Tim Peierls + Released under the Creative Commons Attribution License (http://creativecommons.org/licenses/by/2.5) + Official home: http://www.jcip.net + + Any republication or derived work distributed in source code form + must include this copyright and license notice. + +2) The following files: Assertions.java, AbstractCopyOnWriteMap.java, CopyOnWriteMap.java + + Copyright (c) 2008-2014 Atlassian Pty Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +3) The following files: Beta.java, UnsignedLongs.java, UnsignedLongsTest.java + + Copyright 2010 The Guava Authors + Copyright 2011 The Guava Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +4) The following files: ReadTimeoutHandler.java + + Copyright 2008-present MongoDB, Inc. + Copyright 2012 The Netty Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +5) The following files: InstantCodec.java, Jsr310CodecProvider.java, LocalDateCodec.java, LocalDateTimeCodec.java, LocalTimeCodec.java + + Copyright 2008-present MongoDB, Inc. + Copyright 2018 Cezary Bartosiak + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +6) The following files: SaslPrep.java + + Copyright 2008-present MongoDB, Inc. + Copyright 2017 Tom Bentley + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +7) The following files (originally from https://github.com/marianobarrios/tls-channel): + + AsynchronousTlsChannel.java + AsynchronousTlsChannelGroup.java + ExtendedAsynchronousByteChannel.java + BufferHolder.java + ByteBufferSet.java + ByteBufferUtil.java + TlsChannelImpl.java + TlsChannelCallbackException.java + Util.java + BufferAllocator.java + ClientTlsChannel.java + NeedsReadException.java + NeedsTaskException.java + NeedsWriteException.java + TlsChannel.java + TlsChannelBuilder.java + TlsChannelFlowControlException.java + TrackingAllocator.java + WouldBlockException.java + + Copyright (c) [2015-2018] all contributors + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/bson/build.gradle b/bson/build.gradle index 9eb2441dafc..749e719c17a 100644 --- a/bson/build.gradle +++ b/bson/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,26 @@ * limitations under the License. */ -apply plugin: 'osgi' -apply plugin: 'org.kordamp.gradle.clirr' - -def configDir = new File(rootDir, 'config') archivesBaseName = 'bson' +description = 'The BSON library' + +ext { + pomName = 'BSON' + pomURL = 'https://bsonspec.org' +} clirr { excludeFilter = new File("$configDir/clirr-exclude.yml") - baseline 'org.mongodb:bson:3.2.0' + baseline 'org.mongodb:bson:3.8.0' failOnErrors = true } jar { manifest { + instruction 'Automatic-Module-Name', 'org.mongodb.bson' + instruction 'Build-Version', project.gitVersion instruction 'Import-Package', 'javax.xml.bind.*', 'org.slf4j;resolution:=optional' } } - -modifyPom { - project { - name 'BSON' - description 'The BSON library' - url 'http://bsonspec.org' - } -} diff --git a/bson/src/main/org/bson/AbstractBsonReader.java b/bson/src/main/org/bson/AbstractBsonReader.java index d1f75a57ff8..a7195344324 100644 --- a/bson/src/main/org/bson/AbstractBsonReader.java +++ b/bson/src/main/org/bson/AbstractBsonReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,9 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; -import java.io.Closeable; - import static java.lang.String.format; import static java.util.Arrays.asList; @@ -28,7 +27,7 @@ * * @since 3.0 */ -public abstract class AbstractBsonReader implements Closeable, BsonReader { +public abstract class AbstractBsonReader implements BsonReader { private State state; private Context context; private BsonType currentBsonType; @@ -119,6 +118,14 @@ protected boolean isClosed() { */ protected abstract byte doPeekBinarySubType(); + /** + * Handles the logic to peek at the binary size. + * + * @return the binary size + * @since 3.4 + */ + protected abstract int doPeekBinarySize(); + /** * Handles the logic to read booleans * @@ -164,6 +171,15 @@ protected boolean isClosed() { */ protected abstract long doReadInt64(); + + /** + * Handles the logic to read Decimal128 + * + * @return the Decimal128 value + * @since 3.4 + */ + protected abstract Decimal128 doReadDecimal128(); + /** * Handles the logic to read Javascript functions * @@ -273,6 +289,12 @@ public byte peekBinarySubType() { return doPeekBinarySubType(); } + @Override + public int peekBinarySize() { + checkPreconditions("readBinaryData", BsonType.BINARY); + return doPeekBinarySize(); + } + @Override public boolean readBoolean() { checkPreconditions("readBoolean", BsonType.BOOLEAN); @@ -353,6 +375,13 @@ public long readInt64() { return doReadInt64(); } + @Override + public Decimal128 readDecimal128() { + checkPreconditions("readDecimal", BsonType.DECIMAL128); + setState(getNextState()); + return doReadDecimal128(); + } + @Override public String readJavaScript() { checkPreconditions("readJavaScript", BsonType.JAVASCRIPT); @@ -513,6 +542,12 @@ public long readInt64(final String name) { return readInt64(); } + @Override + public Decimal128 readDecimal128(final String name) { + verifyName(name); + return readDecimal128(); + } + @Override public String readJavaScript(final String name) { verifyName(name); @@ -736,12 +771,13 @@ private void setStateOnEnd() { throw new BSONException(format("Unexpected ContextType %s.", getContext().getContextType())); } } - protected class Mark { - private State state; - private Context parentContext; - private BsonContextType contextType; - private BsonType currentBsonType; - private String currentName; + + protected class Mark implements BsonReaderMark { + private final State state; + private final Context parentContext; + private final BsonContextType contextType; + private final BsonType currentBsonType; + private final String currentName; protected Context getParentContext() { return parentContext; @@ -759,7 +795,7 @@ protected Mark() { currentName = AbstractBsonReader.this.currentName; } - protected void reset() { + public void reset() { AbstractBsonReader.this.state = state; AbstractBsonReader.this.currentBsonType = currentBsonType; AbstractBsonReader.this.currentName = currentName; diff --git a/bson/src/main/org/bson/AbstractBsonWriter.java b/bson/src/main/org/bson/AbstractBsonWriter.java index e141646be76..5dc229d7307 100644 --- a/bson/src/main/org/bson/AbstractBsonWriter.java +++ b/bson/src/main/org/bson/AbstractBsonWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,17 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; import java.io.Closeable; import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.Stack; import static java.lang.String.format; +import static org.bson.assertions.Assertions.notNull; /** * Represents a BSON writer for some external format (see subclasses). @@ -185,6 +189,14 @@ protected void setContext(final Context context) { */ protected abstract void doWriteInt64(long value); + /** + * Handles the logic of writing a Decimal128 value + * + * @param value the {@code Decimal128} value to write + * @since 3.4 + */ + protected abstract void doWriteDecimal128(Decimal128 value); + /** * Handles the logic of writing a JavaScript function * @@ -341,12 +353,15 @@ public void writeEndArray() { @Override public void writeBinaryData(final String name, final BsonBinary binary) { + notNull("name", name); + notNull("value", binary); writeName(name); writeBinaryData(binary); } @Override public void writeBinaryData(final BsonBinary binary) { + notNull("value", binary); checkPreconditions("writeBinaryData", State.VALUE, State.INITIAL); doWriteBinaryData(binary); setState(getNextState()); @@ -380,12 +395,15 @@ public void writeDateTime(final long value) { @Override public void writeDBPointer(final String name, final BsonDbPointer value) { + notNull("name", name); + notNull("value", value); writeName(name); writeDBPointer(value); } @Override public void writeDBPointer(final BsonDbPointer value) { + notNull("value", value); checkPreconditions("writeDBPointer", State.VALUE, State.INITIAL); doWriteDBPointer(value); setState(getNextState()); @@ -430,14 +448,33 @@ public void writeInt64(final long value) { setState(getNextState()); } + @Override + public void writeDecimal128(final Decimal128 value) { + notNull("value", value); + checkPreconditions("writeInt64", State.VALUE); + doWriteDecimal128(value); + setState(getNextState()); + } + + @Override + public void writeDecimal128(final String name, final Decimal128 value) { + notNull("name", name); + notNull("value", value); + writeName(name); + writeDecimal128(value); + } + @Override public void writeJavaScript(final String name, final String code) { + notNull("name", name); + notNull("value", code); writeName(name); writeJavaScript(code); } @Override public void writeJavaScript(final String code) { + notNull("value", code); checkPreconditions("writeJavaScript", State.VALUE); doWriteJavaScript(code); setState(getNextState()); @@ -445,12 +482,15 @@ public void writeJavaScript(final String code) { @Override public void writeJavaScriptWithScope(final String name, final String code) { + notNull("name", name); + notNull("value", code); writeName(name); writeJavaScriptWithScope(code); } @Override public void writeJavaScriptWithScope(final String code) { + notNull("value", code); checkPreconditions("writeJavaScriptWithScope", State.VALUE); doWriteJavaScriptWithScope(code); setState(State.SCOPE_DOCUMENT); @@ -484,19 +524,27 @@ public void writeMinKey() { @Override public void writeName(final String name) { + notNull("name", name); if (state != State.NAME) { throwInvalidState("WriteName", State.NAME); } - if (name == null) { - throw new IllegalArgumentException("BSON field name can not be null"); - } if (!fieldNameValidatorStack.peek().validate(name)) { throw new IllegalArgumentException(format("Invalid BSON field name %s", name)); } + doWriteName(name); context.name = name; state = State.VALUE; } + /** + * Handles the logic of writing the element name. + * + * @param name the name of the element + * @since 3.5 + */ + protected void doWriteName(final String name) { + } + @Override public void writeNull(final String name) { writeName(name); @@ -512,12 +560,15 @@ public void writeNull() { @Override public void writeObjectId(final String name, final ObjectId objectId) { + notNull("name", name); + notNull("value", objectId); writeName(name); writeObjectId(objectId); } @Override public void writeObjectId(final ObjectId objectId) { + notNull("value", objectId); checkPreconditions("writeObjectId", State.VALUE); doWriteObjectId(objectId); setState(getNextState()); @@ -525,12 +576,15 @@ public void writeObjectId(final ObjectId objectId) { @Override public void writeRegularExpression(final String name, final BsonRegularExpression regularExpression) { + notNull("name", name); + notNull("value", regularExpression); writeName(name); writeRegularExpression(regularExpression); } @Override public void writeRegularExpression(final BsonRegularExpression regularExpression) { + notNull("value", regularExpression); checkPreconditions("writeRegularExpression", State.VALUE); doWriteRegularExpression(regularExpression); setState(getNextState()); @@ -538,12 +592,15 @@ public void writeRegularExpression(final BsonRegularExpression regularExpression @Override public void writeString(final String name, final String value) { + notNull("name", name); + notNull("value", value); writeName(name); writeString(value); } @Override public void writeString(final String value) { + notNull("value", value); checkPreconditions("writeString", State.VALUE); doWriteString(value); setState(getNextState()); @@ -552,12 +609,15 @@ public void writeString(final String value) { @Override public void writeSymbol(final String name, final String value) { + notNull("name", name); + notNull("value", value); writeName(name); writeSymbol(value); } @Override public void writeSymbol(final String value) { + notNull("value", value); checkPreconditions("writeSymbol", State.VALUE); doWriteSymbol(value); setState(getNextState()); @@ -565,12 +625,15 @@ public void writeSymbol(final String value) { @Override public void writeTimestamp(final String name, final BsonTimestamp value) { + notNull("name", name); + notNull("value", value); writeName(name); writeTimestamp(value); } @Override public void writeTimestamp(final BsonTimestamp value) { + notNull("value", value); checkPreconditions("writeTimestamp", State.VALUE); doWriteTimestamp(value); setState(getNextState()); @@ -689,39 +752,73 @@ public void close() { @Override public void pipe(final BsonReader reader) { - pipeDocument(reader); + notNull("reader", reader); + pipeDocument(reader, null); + } + + /** + * Reads a single document from the given BsonReader and writes it to this, appending the given extra elements to the document. + * + * @param reader the source of the document + * @param extraElements the extra elements to append to the document + * @since 3.6 + */ + public void pipe(final BsonReader reader, final List extraElements) { + notNull("reader", reader); + notNull("extraElements", extraElements); + pipeDocument(reader, extraElements); } - private void pipeDocument(final BsonReader reader) { + /** + * Pipe a list of extra element to this writer + * + * @param extraElements the extra elements + */ + protected void pipeExtraElements(final List extraElements) { + notNull("extraElements", extraElements); + for (BsonElement cur : extraElements) { + writeName(cur.getName()); + pipeValue(cur.getValue()); + } + } + + /** + * Return true if the current execution of the pipe method should be aborted. + * + * @return true if the current execution of the pipe method should be aborted. + * + * @since 3.7 + */ + protected boolean abortPipe() { + return false; + } + + private void pipeDocument(final BsonReader reader, final List extraElements) { reader.readStartDocument(); writeStartDocument(); while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { writeName(reader.readName()); pipeValue(reader); + if (abortPipe()) { + return; + } } reader.readEndDocument(); - writeEndDocument(); - } - - private void pipeArray(final BsonReader reader) { - reader.readStartArray(); - writeStartArray(); - while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { - pipeValue(reader); + if (extraElements != null) { + pipeExtraElements(extraElements); } - reader.readEndArray(); - writeEndArray(); + writeEndDocument(); } private void pipeJavascriptWithScope(final BsonReader reader) { writeJavaScriptWithScope(reader.readJavaScriptWithScope()); - pipeDocument(reader); + pipeDocument(reader, null); } private void pipeValue(final BsonReader reader) { switch (reader.getCurrentBsonType()) { case DOCUMENT: - pipeDocument(reader); + pipeDocument(reader, null); break; case ARRAY: pipeArray(reader); @@ -773,6 +870,9 @@ private void pipeValue(final BsonReader reader) { case INT64: writeInt64(reader.readInt64()); break; + case DECIMAL128: + writeDecimal128(reader.readDecimal128()); + break; case MIN_KEY: reader.readMinKey(); writeMinKey(); @@ -789,6 +889,111 @@ private void pipeValue(final BsonReader reader) { } } + private void pipeDocument(final BsonDocument value) { + writeStartDocument(); + for (Map.Entry cur : value.entrySet()) { + writeName(cur.getKey()); + pipeValue(cur.getValue()); + } + writeEndDocument(); + } + + private void pipeArray(final BsonReader reader) { + reader.readStartArray(); + writeStartArray(); + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + pipeValue(reader); + if (abortPipe()) { + return; + } + } + reader.readEndArray(); + writeEndArray(); + } + + private void pipeArray(final BsonArray array) { + writeStartArray(); + for (BsonValue cur : array) { + pipeValue(cur); + } + writeEndArray(); + } + + private void pipeJavascriptWithScope(final BsonJavaScriptWithScope javaScriptWithScope) { + writeJavaScriptWithScope(javaScriptWithScope.getCode()); + pipeDocument(javaScriptWithScope.getScope()); + } + + private void pipeValue(final BsonValue value) { + switch (value.getBsonType()) { + case DOCUMENT: + pipeDocument(value.asDocument()); + break; + case ARRAY: + pipeArray(value.asArray()); + break; + case DOUBLE: + writeDouble(value.asDouble().getValue()); + break; + case STRING: + writeString(value.asString().getValue()); + break; + case BINARY: + writeBinaryData(value.asBinary()); + break; + case UNDEFINED: + writeUndefined(); + break; + case OBJECT_ID: + writeObjectId(value.asObjectId().getValue()); + break; + case BOOLEAN: + writeBoolean(value.asBoolean().getValue()); + break; + case DATE_TIME: + writeDateTime(value.asDateTime().getValue()); + break; + case NULL: + writeNull(); + break; + case REGULAR_EXPRESSION: + writeRegularExpression(value.asRegularExpression()); + break; + case JAVASCRIPT: + writeJavaScript(value.asJavaScript().getCode()); + break; + case SYMBOL: + writeSymbol(value.asSymbol().getSymbol()); + break; + case JAVASCRIPT_WITH_SCOPE: + pipeJavascriptWithScope(value.asJavaScriptWithScope()); + break; + case INT32: + writeInt32(value.asInt32().getValue()); + break; + case TIMESTAMP: + writeTimestamp(value.asTimestamp()); + break; + case INT64: + writeInt64(value.asInt64().getValue()); + break; + case DECIMAL128: + writeDecimal128(value.asDecimal128().getValue()); + break; + case MIN_KEY: + writeMinKey(); + break; + case DB_POINTER: + writeDBPointer(value.asDBPointer()); + break; + case MAX_KEY: + writeMaxKey(); + break; + default: + throw new IllegalArgumentException("unhandled BSON type: " + value.getBsonType()); + } + } + /** * The state of a writer. Indicates where in a document the writer is. */ diff --git a/driver/src/main/org/bson/BSON.java b/bson/src/main/org/bson/BSON.java similarity index 96% rename from driver/src/main/org/bson/BSON.java rename to bson/src/main/org/bson/BSON.java index 671efaa8c8b..02ce9b68873 100644 --- a/driver/src/main/org/bson/BSON.java +++ b/bson/src/main/org/bson/BSON.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.bson; -import org.bson.util.ClassMap; - import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Pattern; @@ -27,7 +25,10 @@ * supports the registration of encoding and decoding hooks to transform BSON types during encoding or decoding. * * @see org.bson.Transformer + * @deprecated there is no replacement for this class */ +@Deprecated +@SuppressWarnings("deprecation") public class BSON { public static final byte EOO = 0; @@ -85,8 +86,8 @@ public class BSON { private static volatile boolean encodeHooks = false; private static volatile boolean decodeHooks = false; - private static final ClassMap> encodingHooks = new ClassMap>(); - private static final ClassMap> decodingHooks = new ClassMap>(); + private static final org.bson.util.ClassMap> encodingHooks = new org.bson.util.ClassMap>(); + private static final org.bson.util.ClassMap> decodingHooks = new org.bson.util.ClassMap>(); /** * Gets whether any encoding transformers have been registered for any classes. diff --git a/driver/src/main/org/bson/BSONCallback.java b/bson/src/main/org/bson/BSONCallback.java similarity index 94% rename from driver/src/main/org/bson/BSONCallback.java rename to bson/src/main/org/bson/BSONCallback.java index 6d9258634ac..09995ce21e8 100644 --- a/driver/src/main/org/bson/BSONCallback.java +++ b/bson/src/main/org/bson/BSONCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; /** @@ -135,6 +136,16 @@ public interface BSONCallback { */ void gotDouble(String name, double value); + /** + * Called when reading a field with a {@link org.bson.BsonType#DECIMAL128} value. + * + * @param name the field name + * @param value the Decimal128 field value + * @since 3.4 + * @mongodb.server.release 3.4 + */ + void gotDecimal128(String name, Decimal128 value); + /** * Called when reading a field with a {@link org.bson.BsonType#INT32} value. * @@ -217,7 +228,7 @@ public interface BSONCallback { * * @param name the name of the field * @param data the field's value - * @deprecated + * @deprecated this method is no longer called by the decoder */ @Deprecated void gotBinaryArray(String name, byte[] data); diff --git a/driver/src/main/org/bson/BSONCallbackAdapter.java b/bson/src/main/org/bson/BSONCallbackAdapter.java similarity index 95% rename from driver/src/main/org/bson/BSONCallbackAdapter.java rename to bson/src/main/org/bson/BSONCallbackAdapter.java index 6aa9bd1ce71..cc6d192a3db 100644 --- a/driver/src/main/org/bson/BSONCallbackAdapter.java +++ b/bson/src/main/org/bson/BSONCallbackAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; import static org.bson.io.Bits.readLong; @@ -122,6 +123,11 @@ protected void doWriteInt64(final long value) { bsonCallback.gotLong(getName(), value); } + @Override + protected void doWriteDecimal128(final Decimal128 value) { + bsonCallback.gotDecimal128(getName(), value); + } + @Override protected void doWriteJavaScript(final String value) { bsonCallback.gotCode(getName(), value); @@ -200,7 +206,7 @@ public class Context extends AbstractBsonWriter.Context { private String code; private String name; - public Context(final Context parentContext, final BsonContextType contextType) { + Context(final Context parentContext, final BsonContextType contextType) { super(parentContext, contextType); } diff --git a/driver/src/main/org/bson/BSONDecoder.java b/bson/src/main/org/bson/BSONDecoder.java similarity index 98% rename from driver/src/main/org/bson/BSONDecoder.java rename to bson/src/main/org/bson/BSONDecoder.java index f869a9780d9..9b7e7dd326d 100644 --- a/driver/src/main/org/bson/BSONDecoder.java +++ b/bson/src/main/org/bson/BSONDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/BSONEncoder.java b/bson/src/main/org/bson/BSONEncoder.java similarity index 98% rename from driver/src/main/org/bson/BSONEncoder.java rename to bson/src/main/org/bson/BSONEncoder.java index 06d91f33c42..6974147b845 100644 --- a/driver/src/main/org/bson/BSONEncoder.java +++ b/bson/src/main/org/bson/BSONEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BSONException.java b/bson/src/main/org/bson/BSONException.java index 0af0405f230..dd445268a03 100644 --- a/bson/src/main/org/bson/BSONException.java +++ b/bson/src/main/org/bson/BSONException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/BSONObject.java b/bson/src/main/org/bson/BSONObject.java similarity index 89% rename from driver/src/main/org/bson/BSONObject.java rename to bson/src/main/org/bson/BSONObject.java index f23126d156f..84142dce097 100644 --- a/driver/src/main/org/bson/BSONObject.java +++ b/bson/src/main/org/bson/BSONObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ public interface BSONObject { * * @param key Name to set * @param v Corresponding value - * @return the previous value associated with key, or null if there was no mapping for key. (A null - * return can also indicate that the map previously associated null with key.) + * @return the previous value associated with {@code key}, or {@code null} if there was no mapping for {@code key}. (A + * {@code null} return can also indicate that the map previously associated {@code null} with {@code key}.) */ Object put(String key, Object v); diff --git a/driver/src/main/org/bson/BasicBSONCallback.java b/bson/src/main/org/bson/BasicBSONCallback.java similarity index 97% rename from driver/src/main/org/bson/BasicBSONCallback.java rename to bson/src/main/org/bson/BasicBSONCallback.java index e9b98d5aad7..88247e69d72 100644 --- a/driver/src/main/org/bson/BasicBSONCallback.java +++ b/bson/src/main/org/bson/BasicBSONCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.CodeWScope; +import org.bson.types.Decimal128; import org.bson.types.MaxKey; import org.bson.types.MinKey; import org.bson.types.ObjectId; @@ -36,6 +37,7 @@ /** * An implementation of {@code BsonCallback} that creates an instance of BSONObject. */ +@SuppressWarnings("deprecation") public class BasicBSONCallback implements BSONCallback { private Object root; @@ -177,6 +179,11 @@ public void gotLong(final String name, final long value) { _put(name, value); } + @Override + public void gotDecimal128(final String name, final Decimal128 value) { + _put(name, value); + } + @Override public void gotDate(final String name, final long millis) { _put(name, new Date(millis)); diff --git a/driver/src/main/org/bson/BasicBSONDecoder.java b/bson/src/main/org/bson/BasicBSONDecoder.java similarity index 98% rename from driver/src/main/org/bson/BasicBSONDecoder.java rename to bson/src/main/org/bson/BasicBSONDecoder.java index 1775fee24ed..7233aaf2396 100644 --- a/driver/src/main/org/bson/BasicBSONDecoder.java +++ b/bson/src/main/org/bson/BasicBSONDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/BasicBSONEncoder.java b/bson/src/main/org/bson/BasicBSONEncoder.java similarity index 96% rename from driver/src/main/org/bson/BasicBSONEncoder.java rename to bson/src/main/org/bson/BasicBSONEncoder.java index 0b3c81ce51f..5de070b0603 100644 --- a/driver/src/main/org/bson/BasicBSONEncoder.java +++ b/bson/src/main/org/bson/BasicBSONEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,13 @@ package org.bson; -import com.mongodb.DBRef; import org.bson.io.BasicOutputBuffer; import org.bson.io.OutputBuffer; import org.bson.types.BSONTimestamp; import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.CodeWScope; +import org.bson.types.Decimal128; import org.bson.types.MaxKey; import org.bson.types.MinKey; import org.bson.types.ObjectId; @@ -37,11 +37,10 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; -import static org.bson.BSON.regexFlags; - /** * This is meant to be pooled or cached. There is some per instance memory for string conversion, etc... */ +@SuppressWarnings("deprecation") public class BasicBSONEncoder implements BSONEncoder { private BsonBinaryWriter bsonWriter; @@ -153,6 +152,8 @@ protected void _putObjectField(final String name, final Object initialValue) { putNull(name); } else if (value instanceof Date) { putDate(name, (Date) value); + } else if (value instanceof Decimal128) { + putDecimal128(name, (Decimal128) value); } else if (value instanceof Number) { putNumber(name, (Number) value); } else if (value instanceof Character) { @@ -187,15 +188,6 @@ protected void _putObjectField(final String name, final Object initialValue) { putCodeWScope(name, (CodeWScope) value); } else if (value instanceof Code) { putCode(name, (Code) value); - } else if (value instanceof DBRef) { - BSONObject temp = new BasicBSONObject(); - DBRef dbRef = (DBRef) value; - temp.put("$ref", dbRef.getCollectionName()); - temp.put("$id", dbRef.getId()); - if (dbRef.getDatabaseName() != null) { - temp.put("$db", dbRef.getDatabaseName()); - } - putObject(name, temp); } else if (value instanceof MinKey) { putMinKey(name); } else if (value instanceof MaxKey) { @@ -307,6 +299,19 @@ protected void putNumber(final String name, final Number number) { } } + /** + * Encodes a Decimal128 field. + * + * @param name the field name + * @param value the value + * @since 3.4 + * @mongodb.server.release 3.4 + */ + protected void putDecimal128(final String name, final Decimal128 value) { + putName(name); + bsonWriter.writeDecimal128(value); + } + /** * Encodes a byte array field * @@ -380,7 +385,7 @@ protected void putString(final String name, final String value) { */ protected void putPattern(final String name, final Pattern value) { putName(name); - bsonWriter.writeRegularExpression(new BsonRegularExpression(value.pattern(), regexFlags(value.flags()))); + bsonWriter.writeRegularExpression(new BsonRegularExpression(value.pattern(), org.bson.BSON.regexFlags(value.flags()))); } /** diff --git a/driver/src/main/org/bson/BasicBSONObject.java b/bson/src/main/org/bson/BasicBSONObject.java similarity index 96% rename from driver/src/main/org/bson/BasicBSONObject.java rename to bson/src/main/org/bson/BasicBSONObject.java index 43d14f63df3..2a354fc636c 100644 --- a/driver/src/main/org/bson/BasicBSONObject.java +++ b/bson/src/main/org/bson/BasicBSONObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.bson; -import com.mongodb.util.JSONSerializers; import org.bson.types.BasicBSONList; import org.bson.types.ObjectId; @@ -336,16 +335,6 @@ public BasicBSONObject append(final String key, final Object val) { return this; } - /** - * Returns a JSON serialization of this object - * - * @return JSON serialization - */ - @Override - public String toString() { - return JSONSerializers.getStrict().serialize(this); - } - /** * Compares two documents according to their serialized form, ignoring the order of keys. * @@ -368,7 +357,7 @@ public boolean equals(final Object o) { return false; } - return Arrays.equals(canonicalizeBSONObject(this).encode(), canonicalizeBSONObject(other).encode()); + return Arrays.equals(getEncoder().encode(canonicalizeBSONObject(this)), getEncoder().encode(canonicalizeBSONObject(other))); } @Override @@ -377,7 +366,11 @@ public int hashCode() { } private byte[] encode() { - return new BasicBSONEncoder().encode(this); + return getEncoder().encode(this); + } + + private BSONEncoder getEncoder() { + return new BasicBSONEncoder(); } // create a copy of "from", but with keys ordered alphabetically diff --git a/bson/src/main/org/bson/BsonArray.java b/bson/src/main/org/bson/BsonArray.java index 49fc08558f3..f914e05567b 100644 --- a/bson/src/main/org/bson/BsonArray.java +++ b/bson/src/main/org/bson/BsonArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,10 @@ package org.bson; +import org.bson.codecs.BsonArrayCodec; +import org.bson.codecs.DecoderContext; +import org.bson.json.JsonReader; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -38,14 +42,37 @@ public class BsonArray extends BsonValue implements List, Cloneable { * @param values the list of values, none of whose members may be null. */ public BsonArray(final List values) { - this.values = new ArrayList(values); + this(values, true); } /** - * Construct an empty B + * Construct an empty BsonArray */ public BsonArray() { - values = new ArrayList(); + this(new ArrayList(), false); + } + + @SuppressWarnings("unchecked") + BsonArray(final List values, final boolean copy) { + if (copy) { + this.values = new ArrayList(values); + } else { + this.values = (List) values; + } + } + + /** + * Parses a string in MongoDB Extended JSON format to a {@code BsonArray} + * + * @param json the JSON string + * @return a corresponding {@code BsonArray} object + * @see org.bson.json.JsonReader + * @mongodb.driver.manual reference/mongodb-extended-json/ MongoDB Extended JSON + * + * @since 3.4 + */ + public static BsonArray parse(final String json) { + return new BsonArrayCodec().decode(new JsonReader(json), DecoderContext.builder().build()); } /** @@ -182,17 +209,12 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof BsonArray)) { return false; } BsonArray that = (BsonArray) o; - - if (!values.equals(that.values)) { - return false; - } - - return true; + return getValues().equals(that.getValues()); } @Override diff --git a/bson/src/main/org/bson/BsonBinary.java b/bson/src/main/org/bson/BsonBinary.java index fb10f73fd8e..1a91c8d7f30 100644 --- a/bson/src/main/org/bson/BsonBinary.java +++ b/bson/src/main/org/bson/BsonBinary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,11 @@ package org.bson; +import org.bson.assertions.Assertions; +import org.bson.internal.UuidHelper; + import java.util.Arrays; +import java.util.UUID; /** * A representation of the BSON Binary type. Note that for performance reasons instances of this class are not immutable, @@ -75,6 +79,75 @@ public BsonBinary(final byte type, final byte[] data) { this.data = data; } + /** + * Construct a Type 4 BsonBinary from the given UUID. + * + * @param uuid the UUID + * @since 3.9 + */ + public BsonBinary(final UUID uuid) { + this(uuid, UuidRepresentation.STANDARD); + } + + /** + * Construct a new instance from the given UUID and UuidRepresentation + * + * @param uuid the UUID + * @param uuidRepresentation the UUID representation + * @since 3.9 + */ + public BsonBinary(final UUID uuid, final UuidRepresentation uuidRepresentation) { + if (uuid == null) { + throw new IllegalArgumentException("uuid may not be null"); + } + if (uuidRepresentation == null) { + throw new IllegalArgumentException("uuidRepresentation may not be null"); + } + this.data = UuidHelper.encodeUuidToBinary(uuid, uuidRepresentation); + this.type = uuidRepresentation == UuidRepresentation.STANDARD + ? BsonBinarySubType.UUID_STANDARD.getValue() + : BsonBinarySubType.UUID_LEGACY.getValue(); + } + + /** + * Returns the binary as a UUID. The binary type must be 4. + * + * @return the uuid + * @since 3.9 + */ + public UUID asUuid() { + if (!BsonBinarySubType.isUuid(type)) { + throw new BsonInvalidOperationException("type must be a UUID subtype."); + } + + if (type != BsonBinarySubType.UUID_STANDARD.getValue()) { + throw new BsonInvalidOperationException("uuidRepresentation must be set to return the correct UUID."); + } + + return UuidHelper.decodeBinaryToUuid(this.data.clone(), this.type, UuidRepresentation.STANDARD); + } + + /** + * Returns the binary as a UUID. + * + * @param uuidRepresentation the UUID representation + * @return the uuid + * @since 3.9 + */ + public UUID asUuid(final UuidRepresentation uuidRepresentation) { + Assertions.notNull("uuidRepresentation", uuidRepresentation); + + final byte uuidType = uuidRepresentation == UuidRepresentation.STANDARD + ? BsonBinarySubType.UUID_STANDARD.getValue() + : BsonBinarySubType.UUID_LEGACY.getValue(); + + if (type != uuidType) { + throw new BsonInvalidOperationException("uuidRepresentation does not match current uuidRepresentation."); + } + + return UuidHelper.decodeBinaryToUuid(data.clone(), type, uuidRepresentation); + } + @Override public BsonType getBsonType() { return BsonType.BINARY; diff --git a/bson/src/main/org/bson/BsonBinaryReader.java b/bson/src/main/org/bson/BsonBinaryReader.java index bc6939501c4..6de8f6f32be 100644 --- a/bson/src/main/org/bson/BsonBinaryReader.java +++ b/bson/src/main/org/bson/BsonBinaryReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,9 @@ package org.bson; import org.bson.io.BsonInput; +import org.bson.io.BsonInputMark; import org.bson.io.ByteBufferBsonInput; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; import java.nio.ByteBuffer; @@ -108,7 +110,7 @@ public BsonType readBsonType() { return BsonType.END_OF_DOCUMENT; default: throw new BsonSerializationException(format("BSONType EndOfDocument is not valid when ContextType is %s.", - getContext().getContextType())); + getContext().getContextType())); } } else { switch (getContext().getContextType()) { @@ -135,7 +137,10 @@ protected BsonBinary doReadBinaryData() { byte type = bsonInput.readByte(); if (type == BsonBinarySubType.OLD_BINARY.getValue()) { - bsonInput.readInt32(); + int repeatedNumBytes = bsonInput.readInt32(); + if (repeatedNumBytes != numBytes - 4) { + throw new BsonSerializationException("Binary sub type OldBinary has inconsistent sizes"); + } numBytes -= 4; } byte[] bytes = new byte[numBytes]; @@ -152,11 +157,19 @@ protected byte doPeekBinarySubType() { return type; } + @Override + protected int doPeekBinarySize() { + mark(); + int size = readSize(); + reset(); + return size; + } + @Override protected boolean doReadBoolean() { byte booleanByte = bsonInput.readByte(); if (booleanByte != 0 && booleanByte != 1) { - throw new BsonSerializationException(format("Expected a boolean value but found %d", booleanByte)); + throw new BsonSerializationException(format("Expected a boolean value but found %d", booleanByte)); } return booleanByte == 0x1; } @@ -181,6 +194,13 @@ protected long doReadInt64() { return bsonInput.readInt64(); } + @Override + public Decimal128 doReadDecimal128() { + long low = bsonInput.readInt64(); + long high = bsonInput.readInt64(); + return Decimal128.fromIEEE754BIDEncoding(high, low); + } + @Override protected String doReadJavaScript() { return bsonInput.readString(); @@ -233,9 +253,7 @@ protected String doReadSymbol() { @Override protected BsonTimestamp doReadTimestamp() { - int increment = bsonInput.readInt32(); - int time = bsonInput.readInt32(); - return new BsonTimestamp(time, increment); + return new BsonTimestamp(bsonInput.readInt64()); } @Override @@ -252,7 +270,7 @@ public void doReadStartArray() { @Override protected void doReadStartDocument() { BsonContextType contextType = (getState() == State.SCOPE_DOCUMENT) - ? BsonContextType.SCOPE_DOCUMENT : BsonContextType.DOCUMENT; + ? BsonContextType.SCOPE_DOCUMENT : BsonContextType.DOCUMENT; int startPosition = bsonInput.getPosition(); // position of size field int size = readSize(); setContext(new Context(getContext(), contextType, startPosition, size)); @@ -310,6 +328,9 @@ protected void doSkipValue() { case INT64: skip = 8; break; + case DECIMAL128: + skip = 16; + break; case JAVASCRIPT: skip = readSize(); break; @@ -345,6 +366,9 @@ protected void doSkipValue() { case UNDEFINED: skip = 0; break; + case DB_POINTER: + skip = readSize() + 12; // String followed by ObjectId + break; default: throw new BSONException("Unexpected BSON type: " + getCurrentBsonType()); } @@ -365,14 +389,22 @@ private int readSize() { protected Context getContext() { return (Context) super.getContext(); } + + @Deprecated @Override public void mark() { if (mark != null) { - throw new BSONException("A mark already exists; it needs to be reset before creating a new one"); + throw new BSONException("A mark already exists; it needs to be reset before creating a new one"); } mark = new Mark(); } + @Override + public BsonReaderMark getMark() { + return new Mark(); + } + + @Deprecated @Override public void reset() { if (mark == null) { @@ -385,17 +417,18 @@ public void reset() { protected class Mark extends AbstractBsonReader.Mark { private final int startPosition; private final int size; + private final BsonInputMark bsonInputMark; protected Mark() { super(); startPosition = BsonBinaryReader.this.getContext().startPosition; size = BsonBinaryReader.this.getContext().size; - BsonBinaryReader.this.bsonInput.mark(Integer.MAX_VALUE); + bsonInputMark = BsonBinaryReader.this.bsonInput.getMark(Integer.MAX_VALUE); } - protected void reset() { + public void reset() { super.reset(); - BsonBinaryReader.this.bsonInput.reset(); + bsonInputMark.reset(); BsonBinaryReader.this.setContext(new Context((Context) getParentContext(), getContextType(), startPosition, size)); } } diff --git a/bson/src/main/org/bson/BsonBinarySubType.java b/bson/src/main/org/bson/BsonBinarySubType.java index f9fe386f518..b43b9eb519a 100644 --- a/bson/src/main/org/bson/BsonBinarySubType.java +++ b/bson/src/main/org/bson/BsonBinarySubType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,6 +59,17 @@ public enum BsonBinarySubType { private final byte value; + /** + * Returns true if the given value is a UUID subtype + * + * @param value the subtype value as a byte + * @return true if value is a UUID subtype + * @since 3.4 + */ + public static boolean isUuid(final byte value) { + return value == UUID_LEGACY.getValue() || value == UUID_STANDARD.getValue(); + } + BsonBinarySubType(final byte value) { this.value = value; } @@ -71,5 +82,4 @@ public enum BsonBinarySubType { public byte getValue() { return value; } - } diff --git a/bson/src/main/org/bson/BsonBinaryWriter.java b/bson/src/main/org/bson/BsonBinaryWriter.java index c3a1d26f1dc..2cce2090694 100644 --- a/bson/src/main/org/bson/BsonBinaryWriter.java +++ b/bson/src/main/org/bson/BsonBinaryWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,14 @@ import org.bson.io.BsonInput; import org.bson.io.BsonOutput; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; +import java.util.List; import java.util.Stack; import static java.lang.String.format; +import static org.bson.assertions.Assertions.notNull; /** * A BsonWriter implementation that writes to a binary stream of data. This is the most commonly used implementation. @@ -97,6 +100,14 @@ public BsonOutput getBsonOutput() { return bsonOutput; } + /** + * @return the BsonBinaryWriterSettings + * @since 3.6 + */ + public BsonBinaryWriterSettings getBinaryWriterSettings() { + return binaryWriterSettings; + } + @Override public void flush() { } @@ -206,6 +217,14 @@ protected void doWriteInt64(final long value) { bsonOutput.writeInt64(value); } + @Override + protected void doWriteDecimal128(final Decimal128 value) { + bsonOutput.writeByte(BsonType.DECIMAL128.getValue()); + writeCurrentName(); + bsonOutput.writeInt64(value.getLow()); + bsonOutput.writeInt64(value.getHigh()); + } + @Override protected void doWriteJavaScript(final String value) { bsonOutput.writeByte(BsonType.JAVASCRIPT.getValue()); @@ -273,8 +292,7 @@ public void doWriteSymbol(final String value) { public void doWriteTimestamp(final BsonTimestamp value) { bsonOutput.writeByte(BsonType.TIMESTAMP.getValue()); writeCurrentName(); - bsonOutput.writeInt32(value.getInc()); - bsonOutput.writeInt32(value.getTime()); + bsonOutput.writeInt64(value.getValue()); } @Override @@ -285,6 +303,18 @@ public void doWriteUndefined() { @Override public void pipe(final BsonReader reader) { + notNull("reader", reader); + pipeDocument(reader, null); + } + + @Override + public void pipe(final BsonReader reader, final List extraElements) { + notNull("reader", reader); + notNull("extraElements", extraElements); + pipeDocument(reader, extraElements); + } + + private void pipeDocument(final BsonReader reader, final List extraElements) { if (reader instanceof BsonBinaryReader) { BsonBinaryReader binaryReader = (BsonBinaryReader) reader; if (getState() == State.VALUE) { @@ -296,12 +326,24 @@ public void pipe(final BsonReader reader) { if (size < 5) { throw new BsonSerializationException("Document size must be at least 5"); } + int pipedDocumentStartPosition = bsonOutput.getPosition(); bsonOutput.writeInt32(size); byte[] bytes = new byte[size - 4]; bsonInput.readBytes(bytes); bsonOutput.writeBytes(bytes); + binaryReader.setState(AbstractBsonReader.State.TYPE); + if (extraElements != null) { + bsonOutput.truncateToPosition(bsonOutput.getPosition() - 1); + setContext(new Context(getContext(), BsonContextType.DOCUMENT, pipedDocumentStartPosition)); + setState(State.NAME); + pipeExtraElements(extraElements); + bsonOutput.writeByte(0); + bsonOutput.writeInt32(pipedDocumentStartPosition, bsonOutput.getPosition() - pipedDocumentStartPosition); + setContext(getContext().getParentContext()); + } + if (getContext() == null) { setState(State.DONE); } else { @@ -311,6 +353,10 @@ public void pipe(final BsonReader reader) { } setState(getNextState()); } + + validateSize(bsonOutput.getPosition() - pipedDocumentStartPosition); + } else if (extraElements != null) { + super.pipe(reader, extraElements); } else { super.pipe(reader); } @@ -363,11 +409,15 @@ private void writeCurrentName() { private void backpatchSize() { int size = bsonOutput.getPosition() - getContext().startPosition; + validateSize(size); + bsonOutput.writeInt32(bsonOutput.getPosition() - size, size); + } + + private void validateSize(final int size) { if (size > maxDocumentSizeStack.peek()) { - throw new BsonSerializationException(format("Size %d is larger than MaxDocumentSize %d.", size, - binaryWriterSettings.getMaxDocumentSize())); + throw new BsonMaximumSizeExceededException(format("Document size of %d is larger than maximum of %d.", size, + maxDocumentSizeStack.peek())); } - bsonOutput.writeInt32(bsonOutput.getPosition() - size, size); } protected class Context extends AbstractBsonWriter.Context { diff --git a/bson/src/main/org/bson/BsonBinaryWriterSettings.java b/bson/src/main/org/bson/BsonBinaryWriterSettings.java index a1d4b16e9cc..4c2957987b3 100644 --- a/bson/src/main/org/bson/BsonBinaryWriterSettings.java +++ b/bson/src/main/org/bson/BsonBinaryWriterSettings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonBoolean.java b/bson/src/main/org/bson/BsonBoolean.java index 76f7cc1bb1d..fc95ad2baf7 100644 --- a/bson/src/main/org/bson/BsonBoolean.java +++ b/bson/src/main/org/bson/BsonBoolean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonContextType.java b/bson/src/main/org/bson/BsonContextType.java index 80c5ae1ba77..161f33d11b6 100644 --- a/bson/src/main/org/bson/BsonContextType.java +++ b/bson/src/main/org/bson/BsonContextType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonDateTime.java b/bson/src/main/org/bson/BsonDateTime.java index 7155bf39e8c..1bb045f4ed4 100644 --- a/bson/src/main/org/bson/BsonDateTime.java +++ b/bson/src/main/org/bson/BsonDateTime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonDbPointer.java b/bson/src/main/org/bson/BsonDbPointer.java index 92ad6ed26eb..e74ec049ce4 100644 --- a/bson/src/main/org/bson/BsonDbPointer.java +++ b/bson/src/main/org/bson/BsonDbPointer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonDecimal128.java b/bson/src/main/org/bson/BsonDecimal128.java new file mode 100644 index 00000000000..842fbb83d8c --- /dev/null +++ b/bson/src/main/org/bson/BsonDecimal128.java @@ -0,0 +1,104 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson; + +import org.bson.types.Decimal128; + +import static org.bson.assertions.Assertions.notNull; + +/** + * A representation of the BSON Decimal128 type. + * + * @since 3.4 + */ +public final class BsonDecimal128 extends BsonNumber { + private final Decimal128 value; + + /** + * Construct a new instance with the given value. + * + * @param value the value, which may not be null + */ + public BsonDecimal128(final Decimal128 value) { + notNull("value", value); + this.value = value; + } + + @Override + public BsonType getBsonType() { + return BsonType.DECIMAL128; + } + + /** + * Gets the Decimal128 value. + * + * @return the value + */ + public Decimal128 getValue() { + return value; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BsonDecimal128 that = (BsonDecimal128) o; + + if (!value.equals(that.value)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return "BsonDecimal128{" + + "value=" + value + + '}'; + } + + @Override + public int intValue() { + return value.bigDecimalValue().intValue(); + } + + @Override + public long longValue() { + return value.bigDecimalValue().longValue(); + } + + @Override + public double doubleValue() { + return value.bigDecimalValue().doubleValue(); + } + + @Override + public Decimal128 decimal128Value() { + return value; + } +} diff --git a/bson/src/main/org/bson/BsonDocument.java b/bson/src/main/org/bson/BsonDocument.java index b248463c15b..3b87487f58d 100644 --- a/bson/src/main/org/bson/BsonDocument.java +++ b/bson/src/main/org/bson/BsonDocument.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -185,6 +185,19 @@ public BsonInt64 getInt64(final Object key) { return get(key).asInt64(); } + /** + * Gets the value of the key if it is a BsonDecimal128, or throws if not. + * + * @param key the key + * @return the value of the key as a BsonDecimal128 + * @throws org.bson.BsonInvalidOperationException if the document does not contain the key or the value is not of the expected type + * @since 3.4 + */ + public BsonDecimal128 getDecimal128(final Object key) { + throwIfKeyAbsent(key); + return get(key).asDecimal128(); + } + /** * Gets the value of the key if it is a BsonDouble, or throws if not. * @@ -359,6 +372,21 @@ public boolean isInt64(final Object key) { return get(key).isInt64(); } + /** + * Returns true if the value of the key is a BsonDecimal128, returns false if the document does not contain the key. + * + * @param key the key + * @return true if the value of the key is a BsonDecimal128, returns false if the document does not contain the key. + * @since 3.4 + */ + public boolean isDecimal128(final Object key) { + if (!containsKey(key)) { + return false; + } + return get(key).isDecimal128(); + } + + /** * Returns true if the value of the key is a BsonDouble, returns false if the document does not contain the key. * @@ -542,6 +570,23 @@ public BsonInt64 getInt64(final Object key, final BsonInt64 defaultValue) { return get(key).asInt64(); } + /** + * If the document does not contain the given key, return the given default value. Otherwise, gets the value of the key as a + * BsonDecimal128. + * + * @param key the key + * @param defaultValue the default value + * @return the value of the key as a BsonDecimal128 + * @throws org.bson.BsonInvalidOperationException if the document contains the key but the value is not of the expected type + * @since 3.4 + */ + public BsonDecimal128 getDecimal128(final Object key, final BsonDecimal128 defaultValue) { + if (!containsKey(key)) { + return defaultValue; + } + return get(key).asDecimal128(); + } + /** * If the document does not contain the given key, return the given default value. Otherwise, gets the value of the key as a * BsonDouble. @@ -726,6 +771,28 @@ public BsonDocument append(final String key, final BsonValue value) { return this; } + /** + * Gets the first key in the document. + * + * @return the first key in the document + * @throws java.util.NoSuchElementException if the document is empty + * @since 3.6 + */ + public String getFirstKey() { + return keySet().iterator().next(); + } + + /** + * Gets the first value in the document + * + * @return the first value in the document + * @throws java.util.NoSuchElementException if the document is empty + * @since 3.7 + */ + public BsonReader asBsonReader() { + return new BsonDocumentReader(this); + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -746,10 +813,14 @@ public int hashCode() { } /** - * Gets a JSON representation of this document using the default settings of {@code JsonWriterSettings}. + * Gets a JSON representation of this document using the {@link org.bson.json.JsonMode#STRICT} output mode, and otherwise the default + * settings of {@link JsonWriterSettings.Builder}. + * * @return a JSON representation of this document + * @see #toJson(JsonWriterSettings) * @see JsonWriterSettings */ + @SuppressWarnings("deprecation") public String toJson() { return toJson(new JsonWriterSettings()); } @@ -815,7 +886,7 @@ private static class SerializationProxy implements Serializable { private final byte[] bytes; - public SerializationProxy(final BsonDocument document) { + SerializationProxy(final BsonDocument document) { BasicOutputBuffer buffer = new BasicOutputBuffer(); new BsonDocumentCodec().encode(new BsonBinaryWriter(buffer), document, EncoderContext.builder().build()); this.bytes = new byte[buffer.size()]; diff --git a/bson/src/main/org/bson/BsonDocumentReader.java b/bson/src/main/org/bson/BsonDocumentReader.java index 4b7ca259f88..e3d153d0e1a 100644 --- a/bson/src/main/org/bson/BsonDocumentReader.java +++ b/bson/src/main/org/bson/BsonDocumentReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; import java.util.ArrayList; @@ -57,6 +58,11 @@ protected byte doPeekBinarySubType() { return currentValue.asBinary().getType(); } + @Override + protected int doPeekBinarySize() { + return currentValue.asBinary().getData().length; + } + @Override protected boolean doReadBoolean() { return currentValue.asBoolean().getValue(); @@ -103,6 +109,11 @@ protected long doReadInt64() { return currentValue.asInt64().getValue(); } + @Override + public Decimal128 doReadDecimal128() { + return currentValue.asDecimal128().getValue(); + } + @Override protected String doReadJavaScript() { return currentValue.asJavaScript().getCode(); @@ -224,6 +235,7 @@ public BsonType readBsonType() { return getCurrentBsonType(); } + @Deprecated @Override public void mark() { if (mark != null) { @@ -232,6 +244,12 @@ public void mark() { mark = new Mark(); } + @Override + public BsonReaderMark getMark() { + return new Mark(); + } + + @Deprecated @Override public void reset() { if (mark == null) { @@ -246,8 +264,8 @@ protected Context getContext() { return (Context) super.getContext(); } protected class Mark extends AbstractBsonReader.Mark { - private BsonValue currentValue; - private Context context; + private final BsonValue currentValue; + private final Context context; protected Mark() { super(); @@ -256,7 +274,7 @@ protected Mark() { context.mark(); } - protected void reset() { + public void reset() { super.reset(); BsonDocumentReader.this.currentValue = currentValue; BsonDocumentReader.this.setContext(context); diff --git a/bson/src/main/org/bson/BsonDocumentWrapper.java b/bson/src/main/org/bson/BsonDocumentWrapper.java index 343daf75aa0..c90f3b5a931 100644 --- a/bson/src/main/org/bson/BsonDocumentWrapper.java +++ b/bson/src/main/org/bson/BsonDocumentWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonDocumentWriter.java b/bson/src/main/org/bson/BsonDocumentWriter.java index ef0112eb14b..7c36a368336 100644 --- a/bson/src/main/org/bson/BsonDocumentWriter.java +++ b/bson/src/main/org/bson/BsonDocumentWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; import static org.bson.BsonContextType.DOCUMENT; @@ -133,6 +134,11 @@ protected void doWriteInt64(final long value) { write(new BsonInt64(value)); } + @Override + protected void doWriteDecimal128(final Decimal128 value) { + write(new BsonDecimal128(value)); + } + @Override protected void doWriteJavaScript(final String value) { write(new BsonJavaScript(value)); @@ -204,12 +210,12 @@ private void write(final BsonValue value) { private class Context extends AbstractBsonWriter.Context { private BsonValue container; - public Context(final BsonValue container, final BsonContextType contextType, final Context parent) { + Context(final BsonValue container, final BsonContextType contextType, final Context parent) { super(parent, contextType); this.container = container; } - public Context() { + Context() { super(null, BsonContextType.TOP_LEVEL); } diff --git a/bson/src/main/org/bson/BsonDouble.java b/bson/src/main/org/bson/BsonDouble.java index 0535ff19519..c3f114df493 100644 --- a/bson/src/main/org/bson/BsonDouble.java +++ b/bson/src/main/org/bson/BsonDouble.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,10 @@ package org.bson; +import org.bson.types.Decimal128; + +import java.math.BigDecimal; + /** * A representation of the BSON Double type. * @@ -63,6 +67,18 @@ public long longValue() { return (long) value; } + @Override + public Decimal128 decimal128Value() { + if (Double.isNaN(value)) { + return Decimal128.NaN; + } + if (Double.isInfinite(value)) { + return value > 0 ? Decimal128.POSITIVE_INFINITY : Decimal128.NEGATIVE_INFINITY; + } + + return new Decimal128(new BigDecimal(value)); + } + @Override public double doubleValue() { return value; diff --git a/bson/src/main/org/bson/BsonElement.java b/bson/src/main/org/bson/BsonElement.java index afec63910b3..0b898fb8d8d 100644 --- a/bson/src/main/org/bson/BsonElement.java +++ b/bson/src/main/org/bson/BsonElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,4 +54,32 @@ public String getName() { public BsonValue getValue() { return value; } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BsonElement that = (BsonElement) o; + + if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { + return false; + } + if (getValue() != null ? !getValue().equals(that.getValue()) : that.getValue() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getName() != null ? getName().hashCode() : 0; + result = 31 * result + (getValue() != null ? getValue().hashCode() : 0); + return result; + } } diff --git a/bson/src/main/org/bson/BsonInt32.java b/bson/src/main/org/bson/BsonInt32.java index 21ee187463a..d89a977941a 100644 --- a/bson/src/main/org/bson/BsonInt32.java +++ b/bson/src/main/org/bson/BsonInt32.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.bson; +import org.bson.types.Decimal128; + /** * A representation of the BSON Int32 type. * @@ -63,6 +65,11 @@ public long longValue() { return value; } + @Override + public Decimal128 decimal128Value() { + return new Decimal128(value); + } + @Override public double doubleValue() { return value; diff --git a/bson/src/main/org/bson/BsonInt64.java b/bson/src/main/org/bson/BsonInt64.java index 8ab43d32e9a..8f2b4f40223 100644 --- a/bson/src/main/org/bson/BsonInt64.java +++ b/bson/src/main/org/bson/BsonInt64.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.bson; +import org.bson.types.Decimal128; + /** * A representation of the BSON Int64 type. */ @@ -67,6 +69,11 @@ public double doubleValue() { return value; } + @Override + public Decimal128 decimal128Value() { + return new Decimal128(value); + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/bson/src/main/org/bson/BsonInvalidOperationException.java b/bson/src/main/org/bson/BsonInvalidOperationException.java index 9514cb3d973..9e405ff919f 100644 --- a/bson/src/main/org/bson/BsonInvalidOperationException.java +++ b/bson/src/main/org/bson/BsonInvalidOperationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,4 +32,15 @@ public class BsonInvalidOperationException extends BSONException { public BsonInvalidOperationException(final String message) { super(message); } + + /** + * Construct a new instance. + * + * @param message the message + * @param t the throwable cause. + * @since 3.5 + */ + public BsonInvalidOperationException(final String message, final Throwable t) { + super(message, t); + } } diff --git a/bson/src/main/org/bson/BsonJavaScript.java b/bson/src/main/org/bson/BsonJavaScript.java index 1ac4789cec5..506107e0f9d 100644 --- a/bson/src/main/org/bson/BsonJavaScript.java +++ b/bson/src/main/org/bson/BsonJavaScript.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonJavaScriptWithScope.java b/bson/src/main/org/bson/BsonJavaScriptWithScope.java index 4c81f1517b1..67e172a0aad 100644 --- a/bson/src/main/org/bson/BsonJavaScriptWithScope.java +++ b/bson/src/main/org/bson/BsonJavaScriptWithScope.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonMaxKey.java b/bson/src/main/org/bson/BsonMaxKey.java index 12ef3c757c9..63ee4b48529 100644 --- a/bson/src/main/org/bson/BsonMaxKey.java +++ b/bson/src/main/org/bson/BsonMaxKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonMaximumSizeExceededException.java b/bson/src/main/org/bson/BsonMaximumSizeExceededException.java new file mode 100644 index 00000000000..aac48523e0e --- /dev/null +++ b/bson/src/main/org/bson/BsonMaximumSizeExceededException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson; + +/** + * An exception indicating a failure to serialize a BSON document due to it exceeding the maximum size. + * + * @since 3.7 + */ +public class BsonMaximumSizeExceededException extends BsonSerializationException { + private static final long serialVersionUID = 8725368828269129777L; + + /** + * Construct a new instance. + * + * @param message the message + */ + public BsonMaximumSizeExceededException(final String message) { + super(message); + } +} diff --git a/bson/src/main/org/bson/BsonMinKey.java b/bson/src/main/org/bson/BsonMinKey.java index 362c0f472e8..d9abf013e87 100644 --- a/bson/src/main/org/bson/BsonMinKey.java +++ b/bson/src/main/org/bson/BsonMinKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonNull.java b/bson/src/main/org/bson/BsonNull.java index ae446d72968..3ab61bc9cd2 100644 --- a/bson/src/main/org/bson/BsonNull.java +++ b/bson/src/main/org/bson/BsonNull.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonNumber.java b/bson/src/main/org/bson/BsonNumber.java index 1e5c8125788..68f375ec25d 100644 --- a/bson/src/main/org/bson/BsonNumber.java +++ b/bson/src/main/org/bson/BsonNumber.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.bson; +import org.bson.types.Decimal128; + /** * Base class for the three numeric BSON types. This class mirrors the functionality provided by {@code java.lang.Number}. * @@ -42,4 +44,12 @@ public abstract class BsonNumber extends BsonValue { * @return the numeric value represented by this object after conversion to type {@code double}. */ public abstract double doubleValue(); + + /** + * Returns the value of the specified number as a {@code Decimal128}, which may involve rounding. + * + * @return the numeric value represented by this object after conversion to type {@code Decimal128}. + * @since 3.4 + */ + public abstract Decimal128 decimal128Value(); } diff --git a/bson/src/main/org/bson/BsonObjectId.java b/bson/src/main/org/bson/BsonObjectId.java index ea124a9a37f..790daa006a1 100644 --- a/bson/src/main/org/bson/BsonObjectId.java +++ b/bson/src/main/org/bson/BsonObjectId.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonReader.java b/bson/src/main/org/bson/BsonReader.java index f16c68f3c10..0bd096cb477 100644 --- a/bson/src/main/org/bson/BsonReader.java +++ b/bson/src/main/org/bson/BsonReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,17 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; +import java.io.Closeable; + /** * An interface for reading a logical BSON document using a pull-oriented API. * * @since 3.0 */ -public interface BsonReader { +public interface BsonReader extends Closeable { /** * @return The current BsonType. */ @@ -51,6 +54,15 @@ public interface BsonReader { */ byte peekBinarySubType(); + /** + * Peeks the size of the binary data that the reader is positioned at. This operation is not permitted if the mark is already set. + * + * @return the size of the binary data + * @see #mark() + * @since 3.4 + */ + int peekBinarySize(); + /** * Reads a BSON Binary data element from the reader. * @@ -151,6 +163,23 @@ public interface BsonReader { */ long readInt64(String name); + /** + * Reads a BSON Decimal128 from the reader. + * + * @return A Decimal128 + * @since 3.4 + */ + Decimal128 readDecimal128(); + + /** + * Reads a BSON Decimal128 element from the reader. + * + * @param name The name of the element. + * @return A Decimal128 + * @since 3.4 + */ + Decimal128 readDecimal128(String name); + /** * Reads a BSON JavaScript from the reader. * @@ -357,13 +386,28 @@ public interface BsonReader { * Creates a bookmark in the BsonReader's input * * The previous mark must be cleared before creating a new one + * @deprecated Use {@link #getMark()} instead */ + @Deprecated void mark(); + /** + * Gets a mark representing the current state of the reader. + * + * @return the mark + * @since 3.5 + */ + BsonReaderMark getMark(); + /** * Go back to the state at the last mark and removes the mark * * @throws org.bson.BSONException if no mark has been set + * @deprecated Prefer {@link #getMark()} */ + @Deprecated void reset(); + + @Override + void close(); } diff --git a/bson/src/main/org/bson/BsonReaderMark.java b/bson/src/main/org/bson/BsonReaderMark.java new file mode 100644 index 00000000000..f24190222fa --- /dev/null +++ b/bson/src/main/org/bson/BsonReaderMark.java @@ -0,0 +1,31 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson; + +/** + * Represents a bookmark that can be used to reset a {@link BsonReader} to its state at the time the mark was created. + * + * @see BsonReader#getMark() + * + * @since 3.5 + */ +public interface BsonReaderMark { + /** + * Reset the {@link BsonReader} to its state at the time the mark was created. + */ + void reset(); +} diff --git a/bson/src/main/org/bson/BsonRegularExpression.java b/bson/src/main/org/bson/BsonRegularExpression.java index 03ba879cbd4..e9b6839be6a 100644 --- a/bson/src/main/org/bson/BsonRegularExpression.java +++ b/bson/src/main/org/bson/BsonRegularExpression.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.bson; +import java.util.Arrays; + import static org.bson.assertions.Assertions.notNull; /** @@ -36,7 +38,7 @@ public final class BsonRegularExpression extends BsonValue { */ public BsonRegularExpression(final String pattern, final String options) { this.pattern = notNull("pattern", pattern); - this.options = options == null ? "" : options; + this.options = options == null ? "" : sortOptionCharacters(options); } /** @@ -45,7 +47,7 @@ public BsonRegularExpression(final String pattern, final String options) { * @param pattern the regular expression {@link java.util.regex.Pattern} */ public BsonRegularExpression(final String pattern) { - this(pattern, ""); + this(pattern, null); } @Override @@ -106,4 +108,10 @@ public String toString() { + ", options='" + options + '\'' + '}'; } + + private String sortOptionCharacters(final String options) { + char[] chars = options.toCharArray(); + Arrays.sort(chars); + return new String(chars); + } } diff --git a/bson/src/main/org/bson/BsonSerializationException.java b/bson/src/main/org/bson/BsonSerializationException.java index ea540931bac..d2b7713e980 100644 --- a/bson/src/main/org/bson/BsonSerializationException.java +++ b/bson/src/main/org/bson/BsonSerializationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonString.java b/bson/src/main/org/bson/BsonString.java index 09d9b01996d..379aaa8ef14 100644 --- a/bson/src/main/org/bson/BsonString.java +++ b/bson/src/main/org/bson/BsonString.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonSymbol.java b/bson/src/main/org/bson/BsonSymbol.java index 1d20c896081..b4d9ded22c4 100644 --- a/bson/src/main/org/bson/BsonSymbol.java +++ b/bson/src/main/org/bson/BsonSymbol.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonTimestamp.java b/bson/src/main/org/bson/BsonTimestamp.java index 9874fc446c2..3909fbcb330 100644 --- a/bson/src/main/org/bson/BsonTimestamp.java +++ b/bson/src/main/org/bson/BsonTimestamp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.bson; +import org.bson.internal.UnsignedLongs; + /** * A value representing the BSON timestamp type. * @@ -23,26 +25,33 @@ */ public final class BsonTimestamp extends BsonValue implements Comparable { - private final int seconds; - private final int inc; + private final long value; /** * Construct a new instance with a null time and a 0 increment. */ public BsonTimestamp() { - seconds = 0; - inc = 0; + value = 0; + } + + /** + * Construct a new instance for the given value, which combines the time in seconds and the increment as a single long value. + * + * @param value the timetamp as a single long value + * @since 3.5 + */ + public BsonTimestamp(final long value) { + this.value = value; } /** * Construct a new instance for the given time and increment. * * @param seconds the number of seconds since the epoch - * @param inc the increment. + * @param increment the increment. */ - public BsonTimestamp(final int seconds, final int inc) { - this.seconds = seconds; - this.inc = inc; + public BsonTimestamp(final int seconds, final int increment) { + value = ((long) seconds << 32) | (increment & 0xFFFFFFFFL); } @Override @@ -50,13 +59,24 @@ public BsonType getBsonType() { return BsonType.TIMESTAMP; } + + /** + * Gets the value of the timestamp. + * + * @return the timestamp value + * @since 3.5 + */ + public long getValue() { + return value; + } + /** * Gets the time in seconds since epoch. * * @return an int representing time in seconds since epoch */ public int getTime() { - return seconds; + return (int) (value >> 32); } /** @@ -65,24 +85,21 @@ public int getTime() { * @return an incrementing ordinal for operations within a given second */ public int getInc() { - return inc; + return (int) value; } @Override public String toString() { return "Timestamp{" - + "seconds=" + seconds - + ", inc=" + inc + + "value=" + getValue() + + ", seconds=" + getTime() + + ", inc=" + getInc() + '}'; } @Override public int compareTo(final BsonTimestamp ts) { - if (getTime() != ts.getTime()) { - return getTime() - ts.getTime(); - } else { - return getInc() - ts.getInc(); - } + return UnsignedLongs.compare(value, ts.value); } @Override @@ -96,11 +113,7 @@ public boolean equals(final Object o) { BsonTimestamp timestamp = (BsonTimestamp) o; - if (seconds != timestamp.seconds) { - return false; - } - - if (inc != timestamp.inc) { + if (value != timestamp.value) { return false; } @@ -109,8 +122,6 @@ public boolean equals(final Object o) { @Override public int hashCode() { - int result = seconds; - result = 31 * result + inc; - return result; + return (int) (value ^ (value >>> 32)); } } diff --git a/bson/src/main/org/bson/BsonType.java b/bson/src/main/org/bson/BsonType.java index 51d92622cfc..c3f0530000e 100644 --- a/bson/src/main/org/bson/BsonType.java +++ b/bson/src/main/org/bson/BsonType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -99,6 +99,12 @@ public enum BsonType { * A BSON 64-bit integer. */ INT64(0x12), + /** + * A BSON Decimal128. + * + * @since 3.4 + */ + DECIMAL128(0x13), /** * A BSON MinKey value. */ diff --git a/bson/src/main/org/bson/BsonUndefined.java b/bson/src/main/org/bson/BsonUndefined.java index 78c5ec139a9..28f18957c7d 100644 --- a/bson/src/main/org/bson/BsonUndefined.java +++ b/bson/src/main/org/bson/BsonUndefined.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/BsonValue.java b/bson/src/main/org/bson/BsonValue.java index 39779f28add..66edb96af0a 100644 --- a/bson/src/main/org/bson/BsonValue.java +++ b/bson/src/main/org/bson/BsonValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,6 +106,18 @@ public BsonInt64 asInt64() { return (BsonInt64) this; } + /** + * Gets this value as a BsonDecimal128 if it is one, otherwise throws exception + * + * @return a BsonDecimal128 + * @throws org.bson.BsonInvalidOperationException if this value is not of the expected type + * @since 3.4 + */ + public BsonDecimal128 asDecimal128() { + throwIfInvalidType(BsonType.DECIMAL128); + return (BsonDecimal128) this; + } + /** * Gets this value as a BsonDouble if it is one, otherwise throws exception * @@ -291,6 +303,16 @@ public boolean isInt64() { return this instanceof BsonInt64; } + /** + * Returns true if this is a BsonDecimal128, false otherwise. + * + * @return true if this is a BsonDecimal128, false otherwise + * @since 3.4 + */ + public boolean isDecimal128() { + return this instanceof BsonDecimal128; + } + /** * Returns true if this is a BsonDouble, false otherwise. * diff --git a/bson/src/main/org/bson/BsonWriter.java b/bson/src/main/org/bson/BsonWriter.java index 133f25ef185..c3da5dc6059 100644 --- a/bson/src/main/org/bson/BsonWriter.java +++ b/bson/src/main/org/bson/BsonWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; /** @@ -144,6 +145,23 @@ public interface BsonWriter { */ void writeInt64(String name, long value); + /** + * Writes a BSON Decimal128 to the writer. + * + * @param value The Decimal128 value. + * @since 3.4 + */ + void writeDecimal128(Decimal128 value); + + /** + * Writes a BSON Decimal128 element to the writer. + * + * @param name The name of the element. + * @param value The Decimal128 value. + * @since 3.4 + */ + void writeDecimal128(String name, Decimal128 value); + /** * Writes a BSON JavaScript to the writer. * @@ -338,4 +356,5 @@ public interface BsonWriter { * @param reader The source. */ void pipe(BsonReader reader); + } diff --git a/bson/src/main/org/bson/BsonWriterSettings.java b/bson/src/main/org/bson/BsonWriterSettings.java index 1a4a23f0490..9e3f1243e14 100644 --- a/bson/src/main/org/bson/BsonWriterSettings.java +++ b/bson/src/main/org/bson/BsonWriterSettings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/ByteBuf.java b/bson/src/main/org/bson/ByteBuf.java index 5dfde47bb9d..4ef5b410a10 100644 --- a/bson/src/main/org/bson/ByteBuf.java +++ b/bson/src/main/org/bson/ByteBuf.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -232,18 +232,18 @@ public interface ByteBuf { *

This method transfers bytes from this buffer into the given * destination array. If there are fewer bytes remaining in the * buffer than are required to satisfy the request, that is, if - * length > remaining(), then no + * length > remaining(), then no * bytes are transferred and a {@link java.nio.BufferUnderflowException} is * thrown. * - *

Otherwise, this method copies length bytes from this + *

Otherwise, this method copies {@code length} bytes from this * buffer into the given array, starting at the current position of this * buffer and at the given offset in the array. The position of this - * buffer is then incremented by length. + * buffer is then incremented by {@code length}. * *

In other words, an invocation of this method of the form - * src.get(dst, off, len) has exactly the same effect as - * the loop + * src.get(dst, off, len) + * has exactly the same effect as the loop * *

      * {@code
@@ -261,21 +261,21 @@ public interface ByteBuf  {
      * @param  offset
      *         The offset within the array of the first byte to be
      *         written; must be non-negative and no larger than
-     *         dst.length
+     *         {@code dst.length}
      *
      * @param  length
      *         The maximum number of bytes to be written to the given
      *         array; must be non-negative and no larger than
-     *         dst.length - offset
+     *         {@code dst.length - offset}
      *
      * @return  This buffer
      *
      * @throws java.nio.BufferUnderflowException
-     *          If there are fewer than length bytes
+     *          If there are fewer than {@code length} bytes
      *          remaining in this buffer
      *
      * @throws  IndexOutOfBoundsException
-     *          If the preconditions on the offset and length
+     *          If the preconditions on the {@code offset} and {@code length}
      *          parameters do not hold
      */
     ByteBuf get(byte[] bytes, int offset, int length);
diff --git a/bson/src/main/org/bson/ByteBufNIO.java b/bson/src/main/org/bson/ByteBufNIO.java
index 14fd5a31427..83bfa7d893a 100644
--- a/bson/src/main/org/bson/ByteBufNIO.java
+++ b/bson/src/main/org/bson/ByteBufNIO.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008-2015 MongoDB, Inc.
+ * Copyright 2008-present MongoDB, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 package org.bson;
 
+import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -35,7 +36,7 @@ public class ByteBufNIO implements ByteBuf {
      * @param buf the {@code ByteBuffer} to wrap.
      */
     public ByteBufNIO(final ByteBuffer buf) {
-        this.buf = buf;
+        this.buf = buf.order(ByteOrder.LITTLE_ENDIAN);
     }
 
     @Override
@@ -98,7 +99,7 @@ public ByteBuf put(final byte b) {
 
     @Override
     public ByteBuf flip() {
-        buf.flip();
+        ((Buffer) buf).flip();
         return this;
     }
 
@@ -114,13 +115,13 @@ public int limit() {
 
     @Override
     public ByteBuf position(final int newPosition) {
-        buf.position(newPosition);
+        ((Buffer) buf).position(newPosition);
         return this;
     }
 
     @Override
     public ByteBuf clear() {
-        buf.clear();
+        ((Buffer) buf).clear();
         return this;
     }
 
@@ -202,7 +203,7 @@ public int position() {
 
     @Override
     public ByteBuf limit(final int newLimit) {
-        buf.limit(newLimit);
+        ((Buffer) buf).limit(newLimit);
         return this;
     }
 
diff --git a/bson/src/main/org/bson/Document.java b/bson/src/main/org/bson/Document.java
index bda18319152..5ba7723ace5 100644
--- a/bson/src/main/org/bson/Document.java
+++ b/bson/src/main/org/bson/Document.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008-2015 MongoDB, Inc.
+ * Copyright 2008-present MongoDB, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,10 +32,14 @@
 import java.io.StringWriter;
 import java.util.Collection;
 import java.util.Date;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static java.lang.String.format;
+import static org.bson.assertions.Assertions.isTrue;
 import static org.bson.assertions.Assertions.notNull;
 
 /**
@@ -140,6 +144,94 @@ public  T get(final Object key, final Class clazz) {
         return clazz.cast(documentAsMap.get(key));
     }
 
+    /**
+     * Gets the value of the given key, casting it to {@code Class} or returning the default value if null.
+     * This is useful to avoid having casts in client code, though the effect is the same.
+     *
+     * @param key   the key
+     * @param defaultValue what to return if the value is null
+     * @param    the type of the class
+     * @return the value of the given key, or null if the instance does not contain this key.
+     * @throws ClassCastException if the value of the given key is not of type T
+     * @since 3.5
+     */
+    @SuppressWarnings("unchecked")
+    public  T get(final Object key, final T defaultValue) {
+        notNull("defaultValue", defaultValue);
+        Object value = documentAsMap.get(key);
+        return value == null ? defaultValue : (T) value;
+    }
+
+    /**
+     * Gets the value in an embedded document, casting it to the given {@code Class}.  The list of keys represents a path to the
+     * embedded value, drilling down into an embedded document for each key. This is useful to avoid having casts in
+     * client code, though the effect is the same.
+     *
+     * The generic type of the keys list is {@code ?} to be consistent with the corresponding {@code get} methods, but in practice
+     * the actual type of the argument should be {@code List}. So to get the embedded value of a key list that is of type String,
+     * you would write {@code String name = doc.getEmbedded(List.of("employee", "manager", "name"), String.class)} instead of
+     * {@code String name = (String) doc.get("employee", Document.class).get("manager", Document.class).get("name") }.
+     *
+     * @param keys  the list of keys
+     * @param clazz the non-null class to cast the value to
+     * @param    the type of the class
+     * @return the value of the given embedded key, or null if the instance does not contain this embedded key.
+     * @throws ClassCastException if the value of the given embedded key is not of type T
+     * @since 3.10
+     */
+    public  T getEmbedded(final List keys, final Class clazz) {
+        notNull("keys", keys);
+        isTrue("keys", !keys.isEmpty());
+        notNull("clazz", clazz);
+        return getEmbeddedValue(keys, clazz, null);
+    }
+
+    /**
+     * Gets the value in an embedded document, casting it to the given {@code Class} or returning the default value if null.
+     * The list of keys represents a path to the embedded value, drilling down into an embedded document for each key.
+     * This is useful to avoid having casts in client code, though the effect is the same.
+     *
+     * The generic type of the keys list is {@code ?} to be consistent with the corresponding {@code get} methods, but in practice
+     * the actual type of the argument should be {@code List}. So to get the embedded value of a key list that is of type String,
+     * you would write {@code String name = doc.getEmbedded(List.of("employee", "manager", "name"), "John Smith")} instead of
+     * {@code String name = doc.get("employee", Document.class).get("manager", Document.class).get("name", "John Smith") }.
+     *
+     * @param keys  the list of keys
+     * @param defaultValue what to return if the value is null
+     * @param    the type of the class
+     * @return the value of the given key, or null if the instance does not contain this key.
+     * @throws ClassCastException if the value of the given key is not of type T
+     * @since 3.10
+     */
+    public  T getEmbedded(final List keys, final T defaultValue) {
+        notNull("keys", keys);
+        isTrue("keys", !keys.isEmpty());
+        notNull("defaultValue", defaultValue);
+        return getEmbeddedValue(keys, null, defaultValue);
+    }
+
+
+    // Gets the embedded value of the given list of keys, casting it to {@code Class} or returning the default value if null.
+    // Throws ClassCastException if any of the intermediate embedded values is not a Document.
+    @SuppressWarnings("unchecked")
+    private  T getEmbeddedValue(final List keys, final Class clazz, final T defaultValue) {
+        Object value = this;
+        Iterator keyIterator = keys.iterator();
+        while (keyIterator.hasNext()) {
+            Object key = keyIterator.next();
+            value = ((Document) value).get(key);
+            if (!(value instanceof Document)) {
+                if (value == null) {
+                    return defaultValue;
+                } else if (keyIterator.hasNext()) {
+                    throw new ClassCastException(format("At key %s, the value is not a Document (%s)",
+                            key, value.getClass().getName()));
+                }
+            }
+        }
+        return clazz != null ? clazz.cast(value) : (T) value;
+    }
+
     /**
      * Gets the value of the given key as an Integer.
      *
@@ -160,8 +252,7 @@ public Integer getInteger(final Object key) {
      * @throws java.lang.ClassCastException if the value is not an integer
      */
     public int getInteger(final Object key, final int defaultValue) {
-        Object value = get(key);
-        return value == null ? defaultValue : (Integer) value;
+        return get(key, defaultValue);
     }
 
     /**
@@ -201,8 +292,8 @@ public String getString(final Object key) {
      * Gets the value of the given key as a Boolean.
      *
      * @param key the key
-     * @return the value as a double, which may be null
-     * @throws java.lang.ClassCastException if the value is not an double
+     * @return the value as a Boolean, which may be null
+     * @throws java.lang.ClassCastException if the value is not an boolean
      */
     public Boolean getBoolean(final Object key) {
         return (Boolean) get(key);
@@ -213,12 +304,11 @@ public Boolean getBoolean(final Object key) {
      *
      * @param key          the key
      * @param defaultValue what to return if the value is null
-     * @return the value as a double, which may be null
-     * @throws java.lang.ClassCastException if the value is not an double
+     * @return the value as a primitive boolean
+     * @throws java.lang.ClassCastException if the value is not a boolean
      */
     public boolean getBoolean(final Object key, final boolean defaultValue) {
-        Object value = get(key);
-        return value == null ? defaultValue : (Boolean) value;
+        return get(key, defaultValue);
     }
 
     /**
@@ -244,13 +334,67 @@ public Date getDate(final Object key) {
     }
 
     /**
-     * Gets a JSON representation of this document
+     * Gets the list value of the given key, casting the list elements to the given {@code Class}.  This is useful to avoid having
+     * casts in client code, though the effect is the same.
      *
-     * 

With the default {@link JsonWriterSettings} and {@link DocumentCodec}.

+ * @param key the key + * @param clazz the non-null class to cast the list value to + * @param the type of the class + * @return the list value of the given key, or null if the instance does not contain this key. + * @throws ClassCastException if the elements in the list value of the given key is not of type T or the value is not a list + * @since 3.10 + */ + public List getList(final Object key, final Class clazz) { + notNull("clazz", clazz); + return constructValuesList(key, clazz, null); + } + + /** + * Gets the list value of the given key, casting the list elements to {@code Class} or returning the default list value if null. + * This is useful to avoid having casts in client code, though the effect is the same. + * + * @param key the key + * @param clazz the non-null class to cast the list value to + * @param defaultValue what to return if the value is null + * @param the type of the class + * @return the list value of the given key, or the default list value if the instance does not contain this key. + * @throws ClassCastException if the value of the given key is not of type T + * @since 3.10 + */ + public List getList(final Object key, final Class clazz, final List defaultValue) { + notNull("defaultValue", defaultValue); + notNull("clazz", clazz); + return constructValuesList(key, clazz, defaultValue); + } + + + // Construct the list of values for the specified key, or return the default value if the value is null. + // A ClassCastException will be thrown if an element in the list is not of type T. + @SuppressWarnings("unchecked") + private List constructValuesList(final Object key, final Class clazz, final List defaultValue) { + List value = get(key, List.class); + if (value == null) { + return defaultValue; + } + + for (Object item : value) { + if (!clazz.isAssignableFrom(item.getClass())) { + throw new ClassCastException(format("List element cannot be cast to %s", clazz.getName())); + } + } + return (List) value; + } + + /** + * Gets a JSON representation of this document using the {@link org.bson.json.JsonMode#STRICT} output mode, and otherwise the default + * settings of {@link JsonWriterSettings.Builder} and {@link DocumentCodec}. * * @return a JSON representation of this document * @throws org.bson.codecs.configuration.CodecConfigurationException if the document contains types not in the default registry + * @see #toJson(JsonWriterSettings) + * @see JsonWriterSettings */ + @SuppressWarnings("deprecation") public String toJson() { return toJson(new JsonWriterSettings()); } @@ -277,6 +421,7 @@ public String toJson(final JsonWriterSettings writerSettings) { * @return a JSON representation of this document * @throws org.bson.codecs.configuration.CodecConfigurationException if the registry does not contain a codec for the document values. */ + @SuppressWarnings("deprecation") public String toJson(final Encoder encoder) { return toJson(new JsonWriterSettings(), encoder); } @@ -291,7 +436,7 @@ public String toJson(final Encoder encoder) { */ public String toJson(final JsonWriterSettings writerSettings, final Encoder encoder) { JsonWriter writer = new JsonWriter(new StringWriter(), writerSettings); - encoder.encode(writer, this, EncoderContext.builder().isEncodingCollectibleDocument(true).build()); + encoder.encode(writer, this, EncoderContext.builder().build()); return writer.getWriter().toString(); } diff --git a/driver/src/main/org/bson/EmptyBSONCallback.java b/bson/src/main/org/bson/EmptyBSONCallback.java similarity index 95% rename from driver/src/main/org/bson/EmptyBSONCallback.java rename to bson/src/main/org/bson/EmptyBSONCallback.java index d847b7a05c0..d7714f39407 100644 --- a/driver/src/main/org/bson/EmptyBSONCallback.java +++ b/bson/src/main/org/bson/EmptyBSONCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.bson; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; /** @@ -108,6 +109,11 @@ public void gotLong(final String name, final long value) { throw new UnsupportedOperationException("Operation is not supported"); } + @Override + public void gotDecimal128(final String name, final Decimal128 value) { + throw new UnsupportedOperationException("Operation is not supported"); + } + @Override public void gotDate(final String name, final long millis) { throw new UnsupportedOperationException("Operation is not supported"); diff --git a/bson/src/main/org/bson/FieldNameValidator.java b/bson/src/main/org/bson/FieldNameValidator.java index 89d7b1e58b6..bb528da90d6 100644 --- a/bson/src/main/org/bson/FieldNameValidator.java +++ b/bson/src/main/org/bson/FieldNameValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/LazyBSONCallback.java b/bson/src/main/org/bson/LazyBSONCallback.java similarity index 98% rename from driver/src/main/org/bson/LazyBSONCallback.java rename to bson/src/main/org/bson/LazyBSONCallback.java index 060c3245538..6ae03ce8cce 100644 --- a/driver/src/main/org/bson/LazyBSONCallback.java +++ b/bson/src/main/org/bson/LazyBSONCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/LazyBSONDecoder.java b/bson/src/main/org/bson/LazyBSONDecoder.java similarity index 98% rename from driver/src/main/org/bson/LazyBSONDecoder.java rename to bson/src/main/org/bson/LazyBSONDecoder.java index db2e80e9dd3..c133482d544 100644 --- a/driver/src/main/org/bson/LazyBSONDecoder.java +++ b/bson/src/main/org/bson/LazyBSONDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/LazyBSONList.java b/bson/src/main/org/bson/LazyBSONList.java similarity index 99% rename from driver/src/main/org/bson/LazyBSONList.java rename to bson/src/main/org/bson/LazyBSONList.java index 5f2f62f4b79..2e6137171f5 100644 --- a/driver/src/main/org/bson/LazyBSONList.java +++ b/bson/src/main/org/bson/LazyBSONList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/LazyBSONObject.java b/bson/src/main/org/bson/LazyBSONObject.java similarity index 77% rename from driver/src/main/org/bson/LazyBSONObject.java rename to bson/src/main/org/bson/LazyBSONObject.java index 42a01bbe6fe..ffce92712bf 100644 --- a/driver/src/main/org/bson/LazyBSONObject.java +++ b/bson/src/main/org/bson/LazyBSONObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.bson; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.UuidCodec; import org.bson.io.ByteBufferBsonInput; import org.bson.types.BSONTimestamp; import org.bson.types.Binary; @@ -27,27 +29,32 @@ import java.io.IOException; import java.io.OutputStream; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; import java.util.AbstractMap; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.regex.Pattern; -import static org.bson.io.Bits.readLong; +import static org.bson.BsonBinarySubType.BINARY; +import static org.bson.BsonBinarySubType.OLD_BINARY; /** * An immutable {@code BSONObject} backed by a byte buffer that lazily provides keys and values on request. This is useful for transferring * BSON documents between servers when you don't want to pay the performance penalty of encoding or decoding them fully. */ +@SuppressWarnings("deprecation") public class LazyBSONObject implements BSONObject { private final byte[] bytes; private final int offset; @@ -168,12 +175,13 @@ Object readValue(final BsonBinaryReader reader) { case STRING: return reader.readString(); case BINARY: + byte binarySubType = reader.peekBinarySubType(); + if (BsonBinarySubType.isUuid(binarySubType) && reader.peekBinarySize() == 16) { + return new UuidCodec(UuidRepresentation.JAVA_LEGACY).decode(reader, DecoderContext.builder().build()); + } BsonBinary binary = reader.readBinaryData(); - byte binaryType = binary.getType(); - if (binaryType == BsonBinarySubType.BINARY.getValue() || binaryType == BsonBinarySubType.OLD_BINARY.getValue()) { + if (binarySubType == BINARY.getValue() || binarySubType == OLD_BINARY.getValue()) { return binary.getData(); - } else if (binaryType == BsonBinarySubType.UUID_LEGACY.getValue()) { - return new UUID(readLong(binary.getData(), 0), readLong(binary.getData(), 8)); } else { return new Binary(binary.getType(), binary.getData()); } @@ -203,7 +211,7 @@ Object readValue(final BsonBinaryReader reader) { case SYMBOL: return new Symbol(reader.readSymbol()); case JAVASCRIPT_WITH_SCOPE: - return new CodeWScope(reader.readJavaScriptWithScope(), (BSONObject) readDocument(reader)); + return new CodeWScope(reader.readJavaScriptWithScope(), (BSONObject) readJavaScriptWithScopeDocument(reader)); case INT32: return reader.readInt32(); case TIMESTAMP: @@ -211,6 +219,8 @@ Object readValue(final BsonBinaryReader reader) { return new BSONTimestamp(timestamp.getTime(), timestamp.getInc()); case INT64: return reader.readInt64(); + case DECIMAL128: + return reader.readDecimal128(); case MIN_KEY: reader.readMinKey(); return new MinKey(); @@ -229,6 +239,12 @@ private Object readArray(final BsonBinaryReader reader) { } private Object readDocument(final BsonBinaryReader reader) { + int position = reader.getBsonInput().getPosition(); + reader.skipValue(); + return callback.createObject(bytes, offset + position); + } + + private Object readJavaScriptWithScopeDocument(final BsonBinaryReader reader) { int position = reader.getBsonInput().getPosition(); reader.readStartDocument(); while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { @@ -247,8 +263,8 @@ BsonBinaryReader getBsonReader() { private ByteBuffer getBufferForInternalBytes() { ByteBuffer buffer = ByteBuffer.wrap(bytes, offset, bytes.length - offset).slice(); buffer.order(ByteOrder.LITTLE_ENDIAN); - buffer.limit(buffer.getInt()); - buffer.rewind(); + ((Buffer) buffer).limit(buffer.getInt()); + ((Buffer) buffer).rewind(); return buffer; } @@ -283,12 +299,12 @@ public int pipe(final OutputStream os) throws IOException { } /** - * Gets the entry set for all the key/value pairs in this {@code BSONObject}. + * Gets the entry set for all the key/value pairs in this {@code BSONObject}. The returned set is immutable. * * @return then entry set */ public Set> entrySet() { - Set> entries = new LinkedHashSet>(); + final List> entries = new ArrayList>(); BsonBinaryReader reader = getBsonReader(); try { reader.readStartDocument(); @@ -299,12 +315,82 @@ public Set> entrySet() { } finally { reader.close(); } - return Collections.unmodifiableSet(entries); + return new Set>() { + @Override + public int size() { + return entries.size(); + } + + @Override + public boolean isEmpty() { + return entries.isEmpty(); + } + + @Override + public Iterator> iterator() { + return entries.iterator(); + } + + @Override + public Object[] toArray() { + return entries.toArray(); + } + + @Override + public T[] toArray(final T[] a) { + return entries.toArray(a); + } + + @Override + public boolean contains(final Object o) { + return entries.contains(o); + } + + @Override + public boolean containsAll(final Collection c) { + return entries.containsAll(c); + } + + @Override + public boolean add(final Map.Entry stringObjectEntry) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + }; } @Override public int hashCode() { - return Arrays.hashCode(bytes); + int result = 1; + int size = getBSONSize(); + for (int i = offset; i < offset + size; i++) { + result = 31 * result + bytes[i]; + } + return result; } @Override @@ -344,15 +430,6 @@ public boolean equals(final Object o) { return true; } - /** - * Returns a JSON serialization of this object - * - * @return JSON serialization - */ - public String toString() { - return com.mongodb.util.JSON.serialize(this); - } - /* ----------------- Unsupported operations --------------------- */ diff --git a/bson/src/main/org/bson/NoOpFieldNameValidator.java b/bson/src/main/org/bson/NoOpFieldNameValidator.java index 5cbb229430d..9d47705f574 100644 --- a/bson/src/main/org/bson/NoOpFieldNameValidator.java +++ b/bson/src/main/org/bson/NoOpFieldNameValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/RawBsonArray.java b/bson/src/main/org/bson/RawBsonArray.java new file mode 100644 index 00000000000..6e5c239980a --- /dev/null +++ b/bson/src/main/org/bson/RawBsonArray.java @@ -0,0 +1,363 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson; + +import org.bson.io.ByteBufferBsonInput; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.AbstractList; +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import static org.bson.assertions.Assertions.isTrueArgument; +import static org.bson.assertions.Assertions.notNull; + +/** + * An immutable BSON array that is represented using only the raw bytes. + * + * @since 3.7 + */ +public class RawBsonArray extends BsonArray implements Serializable { + private static final long serialVersionUID = 2L; + private static final String IMMUTABLE_MSG = "RawBsonArray instances are immutable"; + + private final transient RawBsonArrayList delegate; + + /** + * Constructs a new instance with the given byte array. Note that it does not make a copy of the array, so do not modify it after + * passing it to this constructor. + * + * @param bytes the bytes representing a BSON document. Note that the byte array is NOT copied, so care must be taken not to modify it + * after passing it to this construction, unless of course that is your intention. + */ + public RawBsonArray(final byte[] bytes) { + this(notNull("bytes", bytes), 0, bytes.length); + } + + /** + * Constructs a new instance with the given byte array, offset, and length. Note that it does not make a copy of the array, so do not + * modify it after passing it to this constructor. + * + * @param bytes the bytes representing a BSON document. Note that the byte array is NOT copied, so care must be taken not to modify it + * after passing it to this construction, unless of course that is your intention. + * @param offset the offset into the byte array + * @param length the length of the subarray to use + */ + public RawBsonArray(final byte[] bytes, final int offset, final int length) { + this(new RawBsonArrayList(bytes, offset, length)); + } + + private RawBsonArray(final RawBsonArrayList values) { + super(values, false); + this.delegate = values; + } + + ByteBuf getByteBuffer() { + return delegate.getByteBuffer(); + } + + @Override + public boolean add(final BsonValue bsonValue) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public boolean remove(final Object o) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public boolean addAll(final Collection c) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public boolean addAll(final int index, final Collection c) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public boolean removeAll(final Collection c) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public boolean retainAll(final Collection c) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public BsonValue set(final int index, final BsonValue element) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public void add(final int index, final BsonValue element) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public BsonValue remove(final int index) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public BsonArray clone() { + return new RawBsonArray(delegate.bytes.clone(), delegate.offset, delegate.length); + } + + @Override + public boolean equals(final Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + // see https://docs.oracle.com/javase/6/docs/platform/serialization/spec/output.html + private Object writeReplace() { + return new SerializationProxy(delegate.bytes, delegate.offset, delegate.length); + } + + // see https://docs.oracle.com/javase/6/docs/platform/serialization/spec/input.html + private void readObject(final ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Proxy required"); + } + + private static class SerializationProxy implements Serializable { + private static final long serialVersionUID = 1L; + + private final byte[] bytes; + + SerializationProxy(final byte[] bytes, final int offset, final int length) { + if (bytes.length == length) { + this.bytes = bytes; + } else { + this.bytes = new byte[length]; + System.arraycopy(bytes, offset, this.bytes, 0, length); + } + } + + private Object readResolve() { + return new RawBsonArray(bytes); + } + } + + static class RawBsonArrayList extends AbstractList { + private static final int MIN_BSON_ARRAY_SIZE = 5; + private Integer cachedSize; + private final byte[] bytes; + private final int offset; + private final int length; + + RawBsonArrayList(final byte[] bytes, final int offset, final int length) { + notNull("bytes", bytes); + isTrueArgument("offset >= 0", offset >= 0); + isTrueArgument("offset < bytes.length", offset < bytes.length); + isTrueArgument("length <= bytes.length - offset", length <= bytes.length - offset); + isTrueArgument("length >= 5", length >= MIN_BSON_ARRAY_SIZE); + this.bytes = bytes; + this.offset = offset; + this.length = length; + } + + @Override + public BsonValue get(final int index) { + if (index < 0) { + throw new IndexOutOfBoundsException(); + } + int curIndex = 0; + BsonBinaryReader bsonReader = createReader(); + try { + bsonReader.readStartDocument(); + while (bsonReader.readBsonType() != BsonType.END_OF_DOCUMENT) { + bsonReader.skipName(); + if (curIndex == index) { + return RawBsonValueHelper.decode(bytes, bsonReader); + } + bsonReader.skipValue(); + curIndex++; + } + bsonReader.readEndDocument(); + } finally { + bsonReader.close(); + } + throw new IndexOutOfBoundsException(); + } + + @Override + public int size() { + if (cachedSize != null) { + return cachedSize; + } + int size = 0; + BsonBinaryReader bsonReader = createReader(); + try { + bsonReader.readStartDocument(); + while (bsonReader.readBsonType() != BsonType.END_OF_DOCUMENT) { + size++; + bsonReader.readName(); + bsonReader.skipValue(); + } + bsonReader.readEndDocument(); + } finally { + bsonReader.close(); + } + cachedSize = size; + return cachedSize; + } + + @Override + public Iterator iterator() { + return new Itr(); + } + + @Override + public ListIterator listIterator() { + return new ListItr(0); + } + + @Override + public ListIterator listIterator(final int index) { + return new ListItr(index); + } + + private class Itr implements Iterator { + private int cursor = 0; + private BsonBinaryReader bsonReader; + private int currentPosition = 0; + + Itr() { + this(0); + } + + Itr(final int cursorPosition) { + setIterator(cursorPosition); + } + + public boolean hasNext() { + boolean hasNext = cursor != size(); + if (!hasNext) { + bsonReader.close(); + } + return hasNext; + } + + public BsonValue next() { + while (cursor > currentPosition && bsonReader.readBsonType() != BsonType.END_OF_DOCUMENT) { + bsonReader.skipName(); + bsonReader.skipValue(); + currentPosition++; + } + + if (bsonReader.readBsonType() != BsonType.END_OF_DOCUMENT) { + bsonReader.skipName(); + cursor += 1; + currentPosition = cursor; + return RawBsonValueHelper.decode(bytes, bsonReader); + } else { + bsonReader.close(); + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + public int getCursor() { + return cursor; + } + + public void setCursor(final int cursor) { + this.cursor = cursor; + } + + void setIterator(final int cursorPosition) { + cursor = cursorPosition; + currentPosition = 0; + if (bsonReader != null) { + bsonReader.close(); + } + bsonReader = createReader(); + bsonReader.readStartDocument(); + } + } + + private class ListItr extends Itr implements ListIterator { + ListItr(final int index) { + super(index); + } + + public boolean hasPrevious() { + return getCursor() != 0; + } + + public BsonValue previous() { + try { + BsonValue previous = get(previousIndex()); + setIterator(previousIndex()); + return previous; + } catch (IndexOutOfBoundsException e) { + throw new NoSuchElementException(); + } + } + + public int nextIndex() { + return getCursor(); + } + + public int previousIndex() { + return getCursor() - 1; + } + + @Override + public void set(final BsonValue bsonValue) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public void add(final BsonValue bsonValue) { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + } + + private BsonBinaryReader createReader() { + return new BsonBinaryReader(new ByteBufferBsonInput(getByteBuffer())); + } + + ByteBuf getByteBuffer() { + ByteBuffer buffer = ByteBuffer.wrap(bytes, offset, length); + buffer.order(ByteOrder.LITTLE_ENDIAN); + return new ByteBufNIO(buffer); + } + } +} diff --git a/bson/src/main/org/bson/RawBsonDocument.java b/bson/src/main/org/bson/RawBsonDocument.java index b00e0dbacc5..b4c9112823e 100644 --- a/bson/src/main/org/bson/RawBsonDocument.java +++ b/bson/src/main/org/bson/RawBsonDocument.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,11 @@ package org.bson; import org.bson.codecs.BsonDocumentCodec; -import org.bson.codecs.BsonValueCodecProvider; import org.bson.codecs.Codec; +import org.bson.codecs.Decoder; import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; import org.bson.codecs.RawBsonDocumentCodec; -import org.bson.codecs.configuration.CodecRegistry; import org.bson.io.BasicOutputBuffer; import org.bson.io.ByteBufferBsonInput; import org.bson.json.JsonReader; @@ -37,12 +36,11 @@ import java.nio.ByteOrder; import java.util.Collection; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import static org.bson.assertions.Assertions.isTrueArgument; import static org.bson.assertions.Assertions.notNull; -import static org.bson.codecs.BsonValueCodecProvider.getClassForBsonType; -import static org.bson.codecs.configuration.CodecRegistries.fromProviders; /** * An immutable BSON document that is represented using only the raw bytes. @@ -53,8 +51,6 @@ public final class RawBsonDocument extends BsonDocument { private static final long serialVersionUID = 1L; private static final int MIN_BSON_DOCUMENT_SIZE = 5; - private static final CodecRegistry REGISTRY = fromProviders(new BsonValueCodecProvider()); - private final byte[] bytes; private final int offset; private final int length; @@ -147,9 +143,21 @@ public ByteBuf getByteBuffer() { * @return the decoded document */ public T decode(final Codec codec) { + return decode((Decoder) codec); + } + + /** + * Decode this into a document. + * + * @param decoder the decoder to facilitate the transformation + * @param the BSON type that the codec encodes/decodes + * @return the decoded document + * @since 3.6 + */ + public T decode(final Decoder decoder) { BsonBinaryReader reader = createReader(); try { - return codec.decode(reader, DecoderContext.builder().build()); + return decoder.decode(reader, DecoderContext.builder().build()); } finally { reader.close(); } @@ -230,6 +238,21 @@ public Set keySet() { return toBsonDocument().keySet(); } + @Override + public String getFirstKey() { + BsonBinaryReader bsonReader = createReader(); + try { + bsonReader.readStartDocument(); + try { + return bsonReader.readName(); + } catch (BsonInvalidOperationException e) { + throw new NoSuchElementException(); + } + } finally { + bsonReader.close(); + } + } + @Override public boolean containsKey(final Object key) { if (key == null) { @@ -260,7 +283,7 @@ public boolean containsValue(final Object value) { bsonReader.readStartDocument(); while (bsonReader.readBsonType() != BsonType.END_OF_DOCUMENT) { bsonReader.skipName(); - if (deserializeBsonValue(bsonReader).equals(value)) { + if (RawBsonValueHelper.decode(bytes, bsonReader).equals(value)) { return true; } } @@ -281,7 +304,7 @@ public BsonValue get(final Object key) { bsonReader.readStartDocument(); while (bsonReader.readBsonType() != BsonType.END_OF_DOCUMENT) { if (bsonReader.readName().equals(key)) { - return deserializeBsonValue(bsonReader); + return RawBsonValueHelper.decode(bytes, bsonReader); } bsonReader.skipValue(); } @@ -294,6 +317,7 @@ public BsonValue get(final Object key) { } @Override + @SuppressWarnings("deprecation") public String toJson() { return toJson(new JsonWriterSettings()); } @@ -320,10 +344,6 @@ public BsonDocument clone() { return new RawBsonDocument(bytes.clone(), offset, length); } - private BsonValue deserializeBsonValue(final BsonBinaryReader bsonReader) { - return REGISTRY.get(getClassForBsonType(bsonReader.getCurrentBsonType())).decode(bsonReader, DecoderContext.builder().build()); - } - private BsonBinaryReader createReader() { return new BsonBinaryReader(new ByteBufferBsonInput(getByteBuffer())); } @@ -352,7 +372,7 @@ private static class SerializationProxy implements Serializable { private final byte[] bytes; - public SerializationProxy(final byte[] bytes, final int offset, final int length) { + SerializationProxy(final byte[] bytes, final int offset, final int length) { if (bytes.length == length) { this.bytes = bytes; } else { diff --git a/bson/src/main/org/bson/RawBsonValueHelper.java b/bson/src/main/org/bson/RawBsonValueHelper.java new file mode 100644 index 00000000000..864d285a1a6 --- /dev/null +++ b/bson/src/main/org/bson/RawBsonValueHelper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson; + +import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.io.BsonInputMark; + +import static org.bson.codecs.BsonValueCodecProvider.getClassForBsonType; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; + +final class RawBsonValueHelper { + private static final CodecRegistry REGISTRY = fromProviders(new BsonValueCodecProvider()); + + static BsonValue decode(final byte[] bytes, final BsonBinaryReader bsonReader) { + if (bsonReader.getCurrentBsonType() == BsonType.DOCUMENT || bsonReader.getCurrentBsonType() == BsonType.ARRAY) { + int position = bsonReader.getBsonInput().getPosition(); + BsonInputMark mark = bsonReader.getBsonInput().getMark(4); + int size = bsonReader.getBsonInput().readInt32(); + mark.reset(); + bsonReader.skipValue(); + if (bsonReader.getCurrentBsonType() == BsonType.DOCUMENT) { + return new RawBsonDocument(bytes, position, size); + } else { + return new RawBsonArray(bytes, position, size); + } + } else { + return REGISTRY.get(getClassForBsonType(bsonReader.getCurrentBsonType())).decode(bsonReader, DecoderContext.builder().build()); + } + } + + private RawBsonValueHelper() { + } +} diff --git a/bson/src/main/org/bson/StringUtils.java b/bson/src/main/org/bson/StringUtils.java index d6fe73aa29c..a6017fc88a9 100644 --- a/bson/src/main/org/bson/StringUtils.java +++ b/bson/src/main/org/bson/StringUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/Transformer.java b/bson/src/main/org/bson/Transformer.java index 0a0e54a14ca..edf4a82b2bb 100644 --- a/bson/src/main/org/bson/Transformer.java +++ b/bson/src/main/org/bson/Transformer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/UuidRepresentation.java b/bson/src/main/org/bson/UuidRepresentation.java index d696d5c6274..58fdbe3e6ae 100644 --- a/bson/src/main/org/bson/UuidRepresentation.java +++ b/bson/src/main/org/bson/UuidRepresentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,14 @@ * @since 3.0 */ public enum UuidRepresentation { + + /** + * An unspecified representation of UUID. Essentially, this is the null representation value. + * + * @since 3.12 + */ + UNSPECIFIED, + /** * The canonical representation of UUID * diff --git a/bson/src/main/org/bson/assertions/Assertions.java b/bson/src/main/org/bson/assertions/Assertions.java index 3ca0b14862e..e3de9f1eaf1 100644 --- a/bson/src/main/org/bson/assertions/Assertions.java +++ b/bson/src/main/org/bson/assertions/Assertions.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * Copyright (c) 2008-2014 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -63,6 +63,25 @@ public static void isTrueArgument(final String name, final boolean condition) { } } + /** + * Throw IllegalArgumentException if the condition if false, otherwise return the value. This is useful when arguments must be checked + * within an expression, as when using {@code this} to call another constructor, which must be the first line of the calling + * constructor. + * + * @param the value type + * @param name the name of the state that is being checked + * @param value the value of the argument + * @param condition the condition about the parameter to check + * @return the value + * @throws java.lang.IllegalArgumentException if the condition is false + */ + public static T isTrueArgument(final String name, final T value, final boolean condition) { + if (!condition) { + throw new IllegalArgumentException("state should be: " + name); + } + return value; + } + /** * Cast an object to the given class and return it, or throw IllegalArgumentException if it's not assignable to that class. * diff --git a/bson/src/main/org/bson/assertions/package-info.java b/bson/src/main/org/bson/assertions/package-info.java index 9bd358c890a..c098c82b4be 100644 --- a/bson/src/main/org/bson/assertions/package-info.java +++ b/bson/src/main/org/bson/assertions/package-info.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/bson/src/main/org/bson/codecs/AtomicBooleanCodec.java b/bson/src/main/org/bson/codecs/AtomicBooleanCodec.java index fcde2127829..f04d286f09a 100644 --- a/bson/src/main/org/bson/codecs/AtomicBooleanCodec.java +++ b/bson/src/main/org/bson/codecs/AtomicBooleanCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/AtomicIntegerCodec.java b/bson/src/main/org/bson/codecs/AtomicIntegerCodec.java index f441ae55c9a..8fd3e55876b 100644 --- a/bson/src/main/org/bson/codecs/AtomicIntegerCodec.java +++ b/bson/src/main/org/bson/codecs/AtomicIntegerCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.concurrent.atomic.AtomicInteger; +import static org.bson.codecs.NumberCodecHelper.decodeInt; + /** * Encodes and decodes {@code AtomicInteger} objects. * @@ -28,6 +30,7 @@ */ public class AtomicIntegerCodec implements Codec { + @Override public void encode(final BsonWriter writer, final AtomicInteger value, final EncoderContext encoderContext) { writer.writeInt32(value.intValue()); @@ -35,7 +38,7 @@ public void encode(final BsonWriter writer, final AtomicInteger value, final Enc @Override public AtomicInteger decode(final BsonReader reader, final DecoderContext decoderContext) { - return new AtomicInteger(reader.readInt32()); + return new AtomicInteger(decodeInt(reader)); } @Override diff --git a/bson/src/main/org/bson/codecs/AtomicLongCodec.java b/bson/src/main/org/bson/codecs/AtomicLongCodec.java index 4efe618ef77..c6e053c6d9f 100644 --- a/bson/src/main/org/bson/codecs/AtomicLongCodec.java +++ b/bson/src/main/org/bson/codecs/AtomicLongCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.concurrent.atomic.AtomicLong; +import static org.bson.codecs.NumberCodecHelper.decodeLong; + /** * Encodes and decodes {@code AtomicLong} objects. * @@ -28,6 +30,7 @@ */ public class AtomicLongCodec implements Codec { + @Override public void encode(final BsonWriter writer, final AtomicLong value, final EncoderContext encoderContext) { writer.writeInt64(value.longValue()); @@ -35,7 +38,7 @@ public void encode(final BsonWriter writer, final AtomicLong value, final Encode @Override public AtomicLong decode(final BsonReader reader, final DecoderContext decoderContext) { - return new AtomicLong(reader.readInt64()); + return new AtomicLong(decodeLong(reader)); } @Override diff --git a/bson/src/main/org/bson/codecs/BigDecimalCodec.java b/bson/src/main/org/bson/codecs/BigDecimalCodec.java new file mode 100644 index 00000000000..8ad6d555baa --- /dev/null +++ b/bson/src/main/org/bson/codecs/BigDecimalCodec.java @@ -0,0 +1,46 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.types.Decimal128; + +import java.math.BigDecimal; + +/** + * Encodes and decodes {@code BigDecimal} objects. + * + * @since 3.5 + */ +public final class BigDecimalCodec implements Codec { + + @Override + public void encode(final BsonWriter writer, final BigDecimal value, final EncoderContext encoderContext) { + writer.writeDecimal128(new Decimal128(value)); + } + + @Override + public BigDecimal decode(final BsonReader reader, final DecoderContext decoderContext) { + return reader.readDecimal128().bigDecimalValue(); + } + + @Override + public Class getEncoderClass() { + return BigDecimal.class; + } +} diff --git a/bson/src/main/org/bson/codecs/BinaryCodec.java b/bson/src/main/org/bson/codecs/BinaryCodec.java index f2dc08e1ded..cb6c6dbac41 100644 --- a/bson/src/main/org/bson/codecs/BinaryCodec.java +++ b/bson/src/main/org/bson/codecs/BinaryCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BooleanCodec.java b/bson/src/main/org/bson/codecs/BooleanCodec.java index d32b8a2d44c..79206748527 100644 --- a/bson/src/main/org/bson/codecs/BooleanCodec.java +++ b/bson/src/main/org/bson/codecs/BooleanCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonArrayCodec.java b/bson/src/main/org/bson/codecs/BsonArrayCodec.java index 0fbc315b344..9f9c11a1859 100644 --- a/bson/src/main/org/bson/codecs/BsonArrayCodec.java +++ b/bson/src/main/org/bson/codecs/BsonArrayCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import java.util.List; import static org.bson.assertions.Assertions.notNull; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; /** * A codec for BsonArray instances. @@ -34,8 +35,20 @@ * @since 3.0 */ public class BsonArrayCodec implements Codec { + + private static final CodecRegistry DEFAULT_REGISTRY = fromProviders(new BsonValueCodecProvider()); + private final CodecRegistry codecRegistry; + /** + * Creates a new instance with a default codec registry that uses the {@link BsonValueCodecProvider}. + * + * @since 3.4 + */ + public BsonArrayCodec() { + this(DEFAULT_REGISTRY); + } + /** * Construct an instance with the given registry * diff --git a/bson/src/main/org/bson/codecs/BsonBinaryCodec.java b/bson/src/main/org/bson/codecs/BsonBinaryCodec.java index 04ece05e348..9790b972ea0 100644 --- a/bson/src/main/org/bson/codecs/BsonBinaryCodec.java +++ b/bson/src/main/org/bson/codecs/BsonBinaryCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonBooleanCodec.java b/bson/src/main/org/bson/codecs/BsonBooleanCodec.java index ae9549f017a..aabdb10df82 100644 --- a/bson/src/main/org/bson/codecs/BsonBooleanCodec.java +++ b/bson/src/main/org/bson/codecs/BsonBooleanCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonCodec.java b/bson/src/main/org/bson/codecs/BsonCodec.java new file mode 100644 index 00000000000..f7e81a0bb3f --- /dev/null +++ b/bson/src/main/org/bson/codecs/BsonCodec.java @@ -0,0 +1,65 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonDocument; +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.conversions.Bson; + +import static java.lang.String.format; + +/** + * A codec for encoding Bson Implementations + * + * @since 3.11 + */ +public class BsonCodec implements Codec { + private static final Codec BSON_DOCUMENT_CODEC = new BsonDocumentCodec(); + private final CodecRegistry registry; + + /** + * Create a new instance + * + * @param registry the codec registry + */ + public BsonCodec(final CodecRegistry registry) { + this.registry = registry; + } + + @Override + public Bson decode(final BsonReader reader, final DecoderContext decoderContext) { + throw new UnsupportedOperationException("The BsonCodec can only encode to Bson"); + } + + @Override + public void encode(final BsonWriter writer, final Bson value, final EncoderContext encoderContext) { + try { + BsonDocument bsonDocument = value.toBsonDocument(BsonDocument.class, registry); + BSON_DOCUMENT_CODEC.encode(writer, bsonDocument, encoderContext); + } catch (Exception e) { + throw new CodecConfigurationException(format("Unable to encode a Bson implementation: %s", value), e); + } + } + + @Override + public Class getEncoderClass() { + return Bson.class; + } +} diff --git a/bson/src/main/org/bson/codecs/BsonCodecProvider.java b/bson/src/main/org/bson/codecs/BsonCodecProvider.java new file mode 100644 index 00000000000..8761c617dfd --- /dev/null +++ b/bson/src/main/org/bson/codecs/BsonCodecProvider.java @@ -0,0 +1,38 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.conversions.Bson; + +/** + * A codec for encoding simple Bson interface implementations + * + * @since 3.11 + */ +public class BsonCodecProvider implements CodecProvider { + + @Override + @SuppressWarnings("unchecked") + public Codec get(final Class clazz, final CodecRegistry registry) { + if (Bson.class.isAssignableFrom(clazz)) { + return (Codec) new BsonCodec(registry); + } + return null; + } +} diff --git a/bson/src/main/org/bson/codecs/BsonDBPointerCodec.java b/bson/src/main/org/bson/codecs/BsonDBPointerCodec.java index 8d6371c5889..eabb6fce153 100644 --- a/bson/src/main/org/bson/codecs/BsonDBPointerCodec.java +++ b/bson/src/main/org/bson/codecs/BsonDBPointerCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonDateTimeCodec.java b/bson/src/main/org/bson/codecs/BsonDateTimeCodec.java index 5f1b6d32cfe..0ec7dcc20ae 100644 --- a/bson/src/main/org/bson/codecs/BsonDateTimeCodec.java +++ b/bson/src/main/org/bson/codecs/BsonDateTimeCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonDecimal128Codec.java b/bson/src/main/org/bson/codecs/BsonDecimal128Codec.java new file mode 100644 index 00000000000..576e30536fb --- /dev/null +++ b/bson/src/main/org/bson/codecs/BsonDecimal128Codec.java @@ -0,0 +1,43 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonDecimal128; +import org.bson.BsonReader; +import org.bson.BsonWriter; + +/** + * A Codec for BsonDecimal128 instances. + * + * @since 3.4 + */ +public class BsonDecimal128Codec implements Codec { + @Override + public BsonDecimal128 decode(final BsonReader reader, final DecoderContext decoderContext) { + return new BsonDecimal128(reader.readDecimal128()); + } + + @Override + public void encode(final BsonWriter writer, final BsonDecimal128 value, final EncoderContext encoderContext) { + writer.writeDecimal128(value.getValue()); + } + + @Override + public Class getEncoderClass() { + return BsonDecimal128.class; + } +} diff --git a/bson/src/main/org/bson/codecs/BsonDocumentCodec.java b/bson/src/main/org/bson/codecs/BsonDocumentCodec.java index 271b1607b25..7e45e30abcd 100644 --- a/bson/src/main/org/bson/codecs/BsonDocumentCodec.java +++ b/bson/src/main/org/bson/codecs/BsonDocumentCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonDocumentWrapperCodec.java b/bson/src/main/org/bson/codecs/BsonDocumentWrapperCodec.java index 60a0489bcce..70df3a677e1 100644 --- a/bson/src/main/org/bson/codecs/BsonDocumentWrapperCodec.java +++ b/bson/src/main/org/bson/codecs/BsonDocumentWrapperCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonDoubleCodec.java b/bson/src/main/org/bson/codecs/BsonDoubleCodec.java index ad829dd23be..ea135c3ec6b 100644 --- a/bson/src/main/org/bson/codecs/BsonDoubleCodec.java +++ b/bson/src/main/org/bson/codecs/BsonDoubleCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonInt32Codec.java b/bson/src/main/org/bson/codecs/BsonInt32Codec.java index b8328532e01..76ca33b8cb3 100644 --- a/bson/src/main/org/bson/codecs/BsonInt32Codec.java +++ b/bson/src/main/org/bson/codecs/BsonInt32Codec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonInt64Codec.java b/bson/src/main/org/bson/codecs/BsonInt64Codec.java index 147a816b3b4..df7a3e0f266 100644 --- a/bson/src/main/org/bson/codecs/BsonInt64Codec.java +++ b/bson/src/main/org/bson/codecs/BsonInt64Codec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonJavaScriptCodec.java b/bson/src/main/org/bson/codecs/BsonJavaScriptCodec.java index 9b4cdcaa459..1a0f0efe1ca 100644 --- a/bson/src/main/org/bson/codecs/BsonJavaScriptCodec.java +++ b/bson/src/main/org/bson/codecs/BsonJavaScriptCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonJavaScriptWithScopeCodec.java b/bson/src/main/org/bson/codecs/BsonJavaScriptWithScopeCodec.java index 04b349e4b15..cd55b0cf9de 100644 --- a/bson/src/main/org/bson/codecs/BsonJavaScriptWithScopeCodec.java +++ b/bson/src/main/org/bson/codecs/BsonJavaScriptWithScopeCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonMaxKeyCodec.java b/bson/src/main/org/bson/codecs/BsonMaxKeyCodec.java index 32e065db7d6..17f9ff22303 100644 --- a/bson/src/main/org/bson/codecs/BsonMaxKeyCodec.java +++ b/bson/src/main/org/bson/codecs/BsonMaxKeyCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonMinKeyCodec.java b/bson/src/main/org/bson/codecs/BsonMinKeyCodec.java index 20e13ac8cd0..fd2cefb7082 100644 --- a/bson/src/main/org/bson/codecs/BsonMinKeyCodec.java +++ b/bson/src/main/org/bson/codecs/BsonMinKeyCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonNullCodec.java b/bson/src/main/org/bson/codecs/BsonNullCodec.java index f88f186c25d..454f411e93c 100644 --- a/bson/src/main/org/bson/codecs/BsonNullCodec.java +++ b/bson/src/main/org/bson/codecs/BsonNullCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonObjectIdCodec.java b/bson/src/main/org/bson/codecs/BsonObjectIdCodec.java index abde6401b02..9044ffc9769 100644 --- a/bson/src/main/org/bson/codecs/BsonObjectIdCodec.java +++ b/bson/src/main/org/bson/codecs/BsonObjectIdCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonRegularExpressionCodec.java b/bson/src/main/org/bson/codecs/BsonRegularExpressionCodec.java index 6b34220b2af..e9efde5fadc 100644 --- a/bson/src/main/org/bson/codecs/BsonRegularExpressionCodec.java +++ b/bson/src/main/org/bson/codecs/BsonRegularExpressionCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonStringCodec.java b/bson/src/main/org/bson/codecs/BsonStringCodec.java index 0222e6d13e7..837bd7ee1a2 100644 --- a/bson/src/main/org/bson/codecs/BsonStringCodec.java +++ b/bson/src/main/org/bson/codecs/BsonStringCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonSymbolCodec.java b/bson/src/main/org/bson/codecs/BsonSymbolCodec.java index 561dfdc8687..b68f85e3262 100644 --- a/bson/src/main/org/bson/codecs/BsonSymbolCodec.java +++ b/bson/src/main/org/bson/codecs/BsonSymbolCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonTimestampCodec.java b/bson/src/main/org/bson/codecs/BsonTimestampCodec.java index 1171e8ca19e..95d64346275 100644 --- a/bson/src/main/org/bson/codecs/BsonTimestampCodec.java +++ b/bson/src/main/org/bson/codecs/BsonTimestampCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonTypeClassMap.java b/bson/src/main/org/bson/codecs/BsonTypeClassMap.java index ad33e2d844d..3f441a557a7 100644 --- a/bson/src/main/org/bson/codecs/BsonTypeClassMap.java +++ b/bson/src/main/org/bson/codecs/BsonTypeClassMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.CodeWithScope; +import org.bson.types.Decimal128; import org.bson.types.MaxKey; import org.bson.types.MinKey; import org.bson.types.ObjectId; @@ -33,6 +34,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -50,6 +52,7 @@ *
  • DOUBLE: {@code java.lang.Double.class}
  • *
  • INT32: {@code java.lang.Integer.class}
  • *
  • INT64: {@code java.lang.Long.class}
  • + *
  • DECIMAL128: {@code org.bson.types.Decimal128.class}
  • *
  • STRING: {@code java.lang.String.class}
  • *
  • BINARY: {@code org.bson.types.Binary.class}
  • *
  • OBJECT_ID: {@code org.bson.types.ObjectId.class}
  • @@ -102,6 +105,7 @@ public Class get(final BsonType bsonType) { } private void addDefaults() { + map.put(BsonType.ARRAY, List.class); map.put(BsonType.BINARY, Binary.class); map.put(BsonType.BOOLEAN, Boolean.class); map.put(BsonType.DATE_TIME, Date.class); @@ -110,6 +114,7 @@ private void addDefaults() { map.put(BsonType.DOUBLE, Double.class); map.put(BsonType.INT32, Integer.class); map.put(BsonType.INT64, Long.class); + map.put(BsonType.DECIMAL128, Decimal128.class); map.put(BsonType.MAX_KEY, MaxKey.class); map.put(BsonType.MIN_KEY, MinKey.class); map.put(BsonType.JAVASCRIPT, Code.class); diff --git a/bson/src/main/org/bson/codecs/BsonTypeCodecMap.java b/bson/src/main/org/bson/codecs/BsonTypeCodecMap.java index cdc669db799..510a6041a0b 100644 --- a/bson/src/main/org/bson/codecs/BsonTypeCodecMap.java +++ b/bson/src/main/org/bson/codecs/BsonTypeCodecMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -7,19 +7,20 @@ * * 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. - * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.bson.codecs; import org.bson.BsonType; +import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; +import static java.lang.String.format; import static org.bson.assertions.Assertions.notNull; /** @@ -28,6 +29,7 @@ * @since 3.3 */ public class BsonTypeCodecMap { + private final BsonTypeClassMap bsonTypeClassMap; private final Codec[] codecs = new Codec[256]; /** @@ -36,12 +38,16 @@ public class BsonTypeCodecMap { * @param codecRegistry the non-null CodecRegistry */ public BsonTypeCodecMap(final BsonTypeClassMap bsonTypeClassMap, final CodecRegistry codecRegistry) { - notNull("bsonTypeClassMap", bsonTypeClassMap); + this.bsonTypeClassMap = notNull("bsonTypeClassMap", bsonTypeClassMap); notNull("codecRegistry", codecRegistry); for (BsonType cur : bsonTypeClassMap.keys()) { Class clazz = bsonTypeClassMap.get(cur); if (clazz != null) { - codecs[cur.getValue()] = codecRegistry.get(clazz); + try { + codecs[cur.getValue()] = codecRegistry.get(clazz); + } catch (CodecConfigurationException e) { + // delay reporting this until the codec is actually requested + } } } } @@ -53,6 +59,15 @@ public BsonTypeCodecMap(final BsonTypeClassMap bsonTypeClassMap, final CodecRegi * @return the non-null Codec */ public Codec get(final BsonType bsonType) { - return codecs[bsonType.getValue()]; + Codec codec = codecs[bsonType.getValue()]; + if (codec == null) { + Class clazz = bsonTypeClassMap.get(bsonType); + if (clazz == null) { + throw new CodecConfigurationException(format("No class mapped for BSON type %s.", bsonType)); + } else { + throw new CodecConfigurationException(format("Can't find a codec for %s.", clazz)); + } + } + return codec; } } diff --git a/bson/src/main/org/bson/codecs/BsonUndefinedCodec.java b/bson/src/main/org/bson/codecs/BsonUndefinedCodec.java index 52b0c7db312..7cebe50ad76 100644 --- a/bson/src/main/org/bson/codecs/BsonUndefinedCodec.java +++ b/bson/src/main/org/bson/codecs/BsonUndefinedCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/BsonValueCodec.java b/bson/src/main/org/bson/codecs/BsonValueCodec.java index 56fe7ff7463..d8ecfd9fb28 100644 --- a/bson/src/main/org/bson/codecs/BsonValueCodec.java +++ b/bson/src/main/org/bson/codecs/BsonValueCodec.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/bson/src/main/org/bson/codecs/BsonValueCodecProvider.java b/bson/src/main/org/bson/codecs/BsonValueCodecProvider.java index a4e7792420f..312e72246db 100644 --- a/bson/src/main/org/bson/codecs/BsonValueCodecProvider.java +++ b/bson/src/main/org/bson/codecs/BsonValueCodecProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import org.bson.BsonBoolean; import org.bson.BsonDateTime; import org.bson.BsonDbPointer; +import org.bson.BsonDecimal128; import org.bson.BsonDocument; import org.bson.BsonDocumentWrapper; import org.bson.BsonDouble; @@ -90,10 +91,6 @@ public Codec get(final Class clazz, final CodecRegistry registry) { return (Codec) codecs.get(clazz); } - if (clazz == BsonArray.class) { - return (Codec) new BsonArrayCodec(registry); - } - if (clazz == BsonJavaScriptWithScope.class) { return (Codec) new BsonJavaScriptWithScopeCodec(registry.get(BsonDocument.class)); } @@ -114,6 +111,10 @@ public Codec get(final Class clazz, final CodecRegistry registry) { return (Codec) new BsonDocumentCodec(registry); } + if (BsonArray.class.isAssignableFrom(clazz)) { + return (Codec) new BsonArrayCodec(registry); + } + return null; } @@ -126,6 +127,7 @@ private void addCodecs() { addCodec(new BsonDoubleCodec()); addCodec(new BsonInt32Codec()); addCodec(new BsonInt64Codec()); + addCodec(new BsonDecimal128Codec()); addCodec(new BsonMinKeyCodec()); addCodec(new BsonMaxKeyCodec()); addCodec(new BsonJavaScriptCodec()); @@ -154,6 +156,7 @@ private void addCodec(final Codec codec) { map.put(BsonType.DOUBLE, BsonDouble.class); map.put(BsonType.INT32, BsonInt32.class); map.put(BsonType.INT64, BsonInt64.class); + map.put(BsonType.DECIMAL128, BsonDecimal128.class); map.put(BsonType.MAX_KEY, BsonMaxKey.class); map.put(BsonType.MIN_KEY, BsonMinKey.class); map.put(BsonType.JAVASCRIPT, BsonJavaScript.class); diff --git a/bson/src/main/org/bson/codecs/ByteArrayCodec.java b/bson/src/main/org/bson/codecs/ByteArrayCodec.java index 670e7af7fdd..99b07efa9ab 100644 --- a/bson/src/main/org/bson/codecs/ByteArrayCodec.java +++ b/bson/src/main/org/bson/codecs/ByteArrayCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/ByteCodec.java b/bson/src/main/org/bson/codecs/ByteCodec.java index 16d5443e873..26b5005ea66 100644 --- a/bson/src/main/org/bson/codecs/ByteCodec.java +++ b/bson/src/main/org/bson/codecs/ByteCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,20 @@ package org.bson.codecs; +import org.bson.BsonInvalidOperationException; import org.bson.BsonReader; import org.bson.BsonWriter; +import static java.lang.String.format; +import static org.bson.codecs.NumberCodecHelper.decodeInt; + /** * Encodes and decodes {@code Byte} objects. * * @since 3.0 */ public class ByteCodec implements Codec { + @Override public void encode(final BsonWriter writer, final Byte value, final EncoderContext encoderContext) { writer.writeInt32(value); @@ -32,7 +37,11 @@ public void encode(final BsonWriter writer, final Byte value, final EncoderConte @Override public Byte decode(final BsonReader reader, final DecoderContext decoderContext) { - throw new UnsupportedOperationException(); + int value = decodeInt(reader); + if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { + throw new BsonInvalidOperationException(format("%s can not be converted into a Byte.", value)); + } + return (byte) value; } @Override diff --git a/bson/src/main/org/bson/codecs/CharacterCodec.java b/bson/src/main/org/bson/codecs/CharacterCodec.java index 42b3d589302..0a9e6252056 100644 --- a/bson/src/main/org/bson/codecs/CharacterCodec.java +++ b/bson/src/main/org/bson/codecs/CharacterCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/CodeCodec.java b/bson/src/main/org/bson/codecs/CodeCodec.java index 8febfa504b3..0b5fafd6c8e 100644 --- a/bson/src/main/org/bson/codecs/CodeCodec.java +++ b/bson/src/main/org/bson/codecs/CodeCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/CodeWithScopeCodec.java b/bson/src/main/org/bson/codecs/CodeWithScopeCodec.java index 8e7b295df5a..33cb8efafc5 100644 --- a/bson/src/main/org/bson/codecs/CodeWithScopeCodec.java +++ b/bson/src/main/org/bson/codecs/CodeWithScopeCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/Codec.java b/bson/src/main/org/bson/codecs/Codec.java index 7848398fabc..6e9dfc2f8dc 100644 --- a/bson/src/main/org/bson/codecs/Codec.java +++ b/bson/src/main/org/bson/codecs/Codec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/CollectibleCodec.java b/bson/src/main/org/bson/codecs/CollectibleCodec.java index c1fdf2208c2..27d8ee9c324 100644 --- a/bson/src/main/org/bson/codecs/CollectibleCodec.java +++ b/bson/src/main/org/bson/codecs/CollectibleCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/DateCodec.java b/bson/src/main/org/bson/codecs/DateCodec.java index 86a370b9e4c..cf0cc7cb621 100644 --- a/bson/src/main/org/bson/codecs/DateCodec.java +++ b/bson/src/main/org/bson/codecs/DateCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/Decimal128Codec.java b/bson/src/main/org/bson/codecs/Decimal128Codec.java new file mode 100644 index 00000000000..d82bb39288c --- /dev/null +++ b/bson/src/main/org/bson/codecs/Decimal128Codec.java @@ -0,0 +1,43 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.types.Decimal128; + +/** + * Encodes and decodes {@code Decimal128} objects. + * + * @since 3.4 + */ +public final class Decimal128Codec implements Codec { + @Override + public Decimal128 decode(final BsonReader reader, final DecoderContext decoderContext) { + return reader.readDecimal128(); + } + + @Override + public void encode(final BsonWriter writer, final Decimal128 value, final EncoderContext encoderContext) { + writer.writeDecimal128(value); + } + + @Override + public Class getEncoderClass() { + return Decimal128.class; + } +} diff --git a/bson/src/main/org/bson/codecs/Decoder.java b/bson/src/main/org/bson/codecs/Decoder.java index 0b85ca7b5fb..051b102c92d 100644 --- a/bson/src/main/org/bson/codecs/Decoder.java +++ b/bson/src/main/org/bson/codecs/Decoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/DecoderContext.java b/bson/src/main/org/bson/codecs/DecoderContext.java index 0c89547daf2..30c0b700316 100644 --- a/bson/src/main/org/bson/codecs/DecoderContext.java +++ b/bson/src/main/org/bson/codecs/DecoderContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,25 @@ package org.bson.codecs; +import org.bson.BsonReader; + /** - * The context for decoding values to BSON. Currently this is a placeholder, as there is nothing needed yet. + * The context for decoding values to BSON. * * @see org.bson.codecs.Decoder * @since 3.0 */ public final class DecoderContext { + private static final DecoderContext DEFAULT_CONTEXT = DecoderContext.builder().build(); + private final boolean checkedDiscriminator; + + /** + * @return true if the discriminator has been checked + */ + public boolean hasCheckedDiscriminator() { + return checkedDiscriminator; + } + /** * Create a builder. * @@ -39,6 +51,26 @@ public static final class Builder { private Builder() { } + private boolean checkedDiscriminator; + + /** + * @return true if the discriminator has been checked + */ + public boolean hasCheckedDiscriminator() { + return checkedDiscriminator; + } + + /** + * Sets the checkedDiscriminator + * + * @param checkedDiscriminator the checkedDiscriminator + * @return this + */ + public Builder checkedDiscriminator(final boolean checkedDiscriminator) { + this.checkedDiscriminator = checkedDiscriminator; + return this; + } + /** * Build an instance of {@code DecoderContext}. * @return the decoder context @@ -48,6 +80,20 @@ public DecoderContext build() { } } + /** + * Creates a child context and then deserializes using the reader. + * + * @param decoder the decoder to decode with + * @param reader the reader to decode to + * @param the type of the decoder + * @return the decoded value + * @since 3.5 + */ + public T decodeWithChildContext(final Decoder decoder, final BsonReader reader) { + return decoder.decode(reader, DEFAULT_CONTEXT); + } + private DecoderContext(final Builder builder) { + this.checkedDiscriminator = builder.hasCheckedDiscriminator(); } } diff --git a/bson/src/main/org/bson/codecs/DocumentCodec.java b/bson/src/main/org/bson/codecs/DocumentCodec.java index 4f657260a60..e5224c395ca 100644 --- a/bson/src/main/org/bson/codecs/DocumentCodec.java +++ b/bson/src/main/org/bson/codecs/DocumentCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.bson.codecs; -import org.bson.BsonBinarySubType; import org.bson.BsonDocument; import org.bson.BsonDocumentWriter; import org.bson.BsonReader; @@ -25,6 +24,7 @@ import org.bson.BsonWriter; import org.bson.Document; import org.bson.Transformer; +import org.bson.UuidRepresentation; import org.bson.codecs.configuration.CodecRegistry; import java.util.ArrayList; @@ -42,7 +42,7 @@ * @see org.bson.Document * @since 3.0 */ -public class DocumentCodec implements CollectibleCodec { +public class DocumentCodec implements CollectibleCodec, OverridableUuidRepresentationCodec { private static final String ID_FIELD_NAME = "_id"; private static final CodecRegistry DEFAULT_REGISTRY = fromProviders(asList(new ValueCodecProvider(), @@ -54,12 +54,23 @@ public class DocumentCodec implements CollectibleCodec { private final CodecRegistry registry; private final IdGenerator idGenerator; private final Transformer valueTransformer; + private final UuidRepresentation uuidRepresentation; /** - * Construct a new instance with a default {@code CodecRegistry} and + * Construct a new instance with a default {@code CodecRegistry}. */ public DocumentCodec() { - this(DEFAULT_REGISTRY, DEFAULT_BSON_TYPE_CLASS_MAP); + this(DEFAULT_REGISTRY); + } + + /** + * Construct a new instance with the given registry. + * + * @param registry the registry + * @since 3.5 + */ + public DocumentCodec(final CodecRegistry registry) { + this(registry, DEFAULT_BSON_TYPE_CLASS_MAP); } /** @@ -82,15 +93,27 @@ public DocumentCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTy * @param valueTransformer the value transformer to use as a final step when decoding the value of any field in the document */ public DocumentCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTypeClassMap, final Transformer valueTransformer) { + this(registry, new BsonTypeCodecMap(notNull("bsonTypeClassMap", bsonTypeClassMap), registry), + new ObjectIdGenerator(), valueTransformer, UuidRepresentation.JAVA_LEGACY); + } + + private DocumentCodec(final CodecRegistry registry, final BsonTypeCodecMap bsonTypeCodecMap, final IdGenerator idGenerator, + final Transformer valueTransformer, final UuidRepresentation uuidRepresentation) { this.registry = notNull("registry", registry); - this.bsonTypeCodecMap = new BsonTypeCodecMap(notNull("bsonTypeClassMap", bsonTypeClassMap), registry); - this.idGenerator = new ObjectIdGenerator(); + this.bsonTypeCodecMap = bsonTypeCodecMap; + this.idGenerator = idGenerator; this.valueTransformer = valueTransformer != null ? valueTransformer : new Transformer() { @Override public Object transform(final Object value) { return value; } }; + this.uuidRepresentation = uuidRepresentation; + } + + @Override + public Codec withUuidRepresentation(final UuidRepresentation uuidRepresentation) { + return new DocumentCodec(registry, bsonTypeCodecMap, idGenerator, valueTransformer, uuidRepresentation); } @Override @@ -205,14 +228,30 @@ private Object readValue(final BsonReader reader, final DecoderContext decoderCo reader.readNull(); return null; } else if (bsonType == BsonType.ARRAY) { - return readList(reader, decoderContext); - } else if (bsonType == BsonType.BINARY) { - byte bsonSubType = reader.peekBinarySubType(); - if (bsonSubType == BsonBinarySubType.UUID_STANDARD.getValue() || bsonSubType == BsonBinarySubType.UUID_LEGACY.getValue()) { - return registry.get(UUID.class).decode(reader, decoderContext); + return readList(reader, decoderContext); + } else { + Codec codec = bsonTypeCodecMap.get(bsonType); + + if (bsonType == BsonType.BINARY && reader.peekBinarySize() == 16) { + switch (reader.peekBinarySubType()) { + case 3: + if (uuidRepresentation == UuidRepresentation.JAVA_LEGACY + || uuidRepresentation == UuidRepresentation.C_SHARP_LEGACY + || uuidRepresentation == UuidRepresentation.PYTHON_LEGACY) { + codec = registry.get(UUID.class); + } + break; + case 4: + if (uuidRepresentation == UuidRepresentation.JAVA_LEGACY || uuidRepresentation == UuidRepresentation.STANDARD) { + codec = registry.get(UUID.class); + } + break; + default: + break; + } } + return valueTransformer.transform(codec.decode(reader, decoderContext)); } - return valueTransformer.transform(bsonTypeCodecMap.get(bsonType).decode(reader, decoderContext)); } private List readList(final BsonReader reader, final DecoderContext decoderContext) { diff --git a/bson/src/main/org/bson/codecs/DocumentCodecProvider.java b/bson/src/main/org/bson/codecs/DocumentCodecProvider.java index 78c3e2f9530..a2e08c99ede 100644 --- a/bson/src/main/org/bson/codecs/DocumentCodecProvider.java +++ b/bson/src/main/org/bson/codecs/DocumentCodecProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/DoubleCodec.java b/bson/src/main/org/bson/codecs/DoubleCodec.java index 7dded30fa0d..523042bb163 100644 --- a/bson/src/main/org/bson/codecs/DoubleCodec.java +++ b/bson/src/main/org/bson/codecs/DoubleCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import org.bson.BsonReader; import org.bson.BsonWriter; +import static org.bson.codecs.NumberCodecHelper.decodeDouble; + /** * Encodes and decodes {@code Double} objects. * @@ -32,7 +34,7 @@ public void encode(final BsonWriter writer, final Double value, final EncoderCon @Override public Double decode(final BsonReader reader, final DecoderContext decoderContext) { - return reader.readDouble(); + return decodeDouble(reader); } @Override diff --git a/bson/src/main/org/bson/codecs/Encoder.java b/bson/src/main/org/bson/codecs/Encoder.java index 018bec15cd7..4b06894c71d 100644 --- a/bson/src/main/org/bson/codecs/Encoder.java +++ b/bson/src/main/org/bson/codecs/Encoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/EncoderContext.java b/bson/src/main/org/bson/codecs/EncoderContext.java index 85f5eede8d7..cad35fce3e0 100644 --- a/bson/src/main/org/bson/codecs/EncoderContext.java +++ b/bson/src/main/org/bson/codecs/EncoderContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/FloatCodec.java b/bson/src/main/org/bson/codecs/FloatCodec.java index ccd502e24eb..84b85c5aa1b 100644 --- a/bson/src/main/org/bson/codecs/FloatCodec.java +++ b/bson/src/main/org/bson/codecs/FloatCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,20 @@ package org.bson.codecs; +import org.bson.BsonInvalidOperationException; import org.bson.BsonReader; import org.bson.BsonWriter; +import static java.lang.String.format; +import static org.bson.codecs.NumberCodecHelper.decodeDouble; + /** * Encodes and decodes {@code Float} objects. * * @since 3.0 */ public class FloatCodec implements Codec { + @Override public void encode(final BsonWriter writer, final Float value, final EncoderContext encoderContext) { writer.writeDouble(value); @@ -32,7 +37,11 @@ public void encode(final BsonWriter writer, final Float value, final EncoderCont @Override public Float decode(final BsonReader reader, final DecoderContext decoderContext) { - throw new UnsupportedOperationException(); + double value = decodeDouble(reader); + if (value < -Float.MAX_VALUE || value > Float.MAX_VALUE) { + throw new BsonInvalidOperationException(format("%s can not be converted into a Float.", value)); + } + return (float) value; } @Override diff --git a/bson/src/main/org/bson/codecs/IdGenerator.java b/bson/src/main/org/bson/codecs/IdGenerator.java index 5e05be4e6ee..790737542c1 100644 --- a/bson/src/main/org/bson/codecs/IdGenerator.java +++ b/bson/src/main/org/bson/codecs/IdGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/IntegerCodec.java b/bson/src/main/org/bson/codecs/IntegerCodec.java index 2a6edc31298..dee6e2512fb 100644 --- a/bson/src/main/org/bson/codecs/IntegerCodec.java +++ b/bson/src/main/org/bson/codecs/IntegerCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,15 @@ import org.bson.BsonReader; import org.bson.BsonWriter; +import static org.bson.codecs.NumberCodecHelper.decodeInt; + /** * Encodes and decodes {@code Integer} objects. * * @since 3.0 */ public class IntegerCodec implements Codec { + @Override public void encode(final BsonWriter writer, final Integer value, final EncoderContext encoderContext) { writer.writeInt32(value); @@ -32,7 +35,7 @@ public void encode(final BsonWriter writer, final Integer value, final EncoderCo @Override public Integer decode(final BsonReader reader, final DecoderContext decoderContext) { - return reader.readInt32(); + return decodeInt(reader); } @Override diff --git a/bson/src/main/org/bson/codecs/IterableCodec.java b/bson/src/main/org/bson/codecs/IterableCodec.java index b7fd2170bdb..c15e13e207b 100644 --- a/bson/src/main/org/bson/codecs/IterableCodec.java +++ b/bson/src/main/org/bson/codecs/IterableCodec.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ package org.bson.codecs; -import org.bson.BsonBinarySubType; import org.bson.BsonReader; import org.bson.BsonType; import org.bson.BsonWriter; import org.bson.Transformer; +import org.bson.UuidRepresentation; import org.bson.codecs.configuration.CodecRegistry; import java.util.ArrayList; @@ -35,11 +35,12 @@ * @since 3.3 */ @SuppressWarnings("rawtypes") -public class IterableCodec implements Codec { +public class IterableCodec implements Codec, OverridableUuidRepresentationCodec { private final CodecRegistry registry; private final BsonTypeCodecMap bsonTypeCodecMap; private final Transformer valueTransformer; + private final UuidRepresentation uuidRepresentation; /** * Construct a new instance with the given {@code CodecRegistry} and {@code BsonTypeClassMap}. @@ -59,14 +60,27 @@ public IterableCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTy * @param valueTransformer the value Transformer */ public IterableCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTypeClassMap, final Transformer valueTransformer) { + this(registry, new BsonTypeCodecMap(notNull("bsonTypeClassMap", bsonTypeClassMap), registry), valueTransformer, + UuidRepresentation.JAVA_LEGACY); + } + + private IterableCodec(final CodecRegistry registry, final BsonTypeCodecMap bsonTypeCodecMap, final Transformer valueTransformer, + final UuidRepresentation uuidRepresentation) { this.registry = notNull("registry", registry); - this.bsonTypeCodecMap = new BsonTypeCodecMap(notNull("bsonTypeClassMap", bsonTypeClassMap), registry); + this.bsonTypeCodecMap = bsonTypeCodecMap; this.valueTransformer = valueTransformer != null ? valueTransformer : new Transformer() { @Override public Object transform(final Object objectToTransform) { return objectToTransform; } }; + this.uuidRepresentation = uuidRepresentation; + } + + + @Override + public Codec withUuidRepresentation(final UuidRepresentation uuidRepresentation) { + return new IterableCodec(registry, bsonTypeCodecMap, valueTransformer, uuidRepresentation); } @Override @@ -112,12 +126,27 @@ private Object readValue(final BsonReader reader, final DecoderContext decoderCo if (bsonType == BsonType.NULL) { reader.readNull(); return null; - } else if (bsonType == BsonType.BINARY) { - byte bsonSubType = reader.peekBinarySubType(); - if (bsonSubType == BsonBinarySubType.UUID_STANDARD.getValue() || bsonSubType == BsonBinarySubType.UUID_LEGACY.getValue()) { - return registry.get(UUID.class).decode(reader, decoderContext); + } else { + Codec codec = bsonTypeCodecMap.get(bsonType); + if (bsonType == BsonType.BINARY && reader.peekBinarySize() == 16) { + switch (reader.peekBinarySubType()) { + case 3: + if (uuidRepresentation == UuidRepresentation.JAVA_LEGACY + || uuidRepresentation == UuidRepresentation.C_SHARP_LEGACY + || uuidRepresentation == UuidRepresentation.PYTHON_LEGACY) { + codec = registry.get(UUID.class); + } + break; + case 4: + if (uuidRepresentation == UuidRepresentation.JAVA_LEGACY || uuidRepresentation == UuidRepresentation.STANDARD) { + codec = registry.get(UUID.class); + } + break; + default: + break; + } } + return valueTransformer.transform(codec.decode(reader, decoderContext)); } - return valueTransformer.transform(bsonTypeCodecMap.get(bsonType).decode(reader, decoderContext)); } } diff --git a/bson/src/main/org/bson/codecs/IterableCodecProvider.java b/bson/src/main/org/bson/codecs/IterableCodecProvider.java index 254650f4362..8a0cd4cc757 100644 --- a/bson/src/main/org/bson/codecs/IterableCodecProvider.java +++ b/bson/src/main/org/bson/codecs/IterableCodecProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,7 +12,6 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package org.bson.codecs; @@ -44,7 +43,6 @@ public IterableCodecProvider() { * IterableCodec as a last step when decoding values. * * @param valueTransformer the value transformer for decoded values - * @see org.bson.codecs.DocumentCodec#DocumentCodec(org.bson.codecs.configuration.CodecRegistry, BsonTypeClassMap, org.bson.Transformer) */ public IterableCodecProvider(final Transformer valueTransformer) { this(new BsonTypeClassMap(), valueTransformer); diff --git a/bson/src/main/org/bson/codecs/LongCodec.java b/bson/src/main/org/bson/codecs/LongCodec.java index 30547c1d6ef..29adc373488 100644 --- a/bson/src/main/org/bson/codecs/LongCodec.java +++ b/bson/src/main/org/bson/codecs/LongCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import org.bson.BsonReader; import org.bson.BsonWriter; +import static org.bson.codecs.NumberCodecHelper.decodeLong; + /** * Encodes and decodes {@code Long} objects. * @@ -26,6 +28,7 @@ */ public class LongCodec implements Codec { + @Override public void encode(final BsonWriter writer, final Long value, final EncoderContext encoderContext) { writer.writeInt64(value); @@ -33,7 +36,7 @@ public void encode(final BsonWriter writer, final Long value, final EncoderConte @Override public Long decode(final BsonReader reader, final DecoderContext decoderContext) { - return reader.readInt64(); + return decodeLong(reader); } @Override diff --git a/bson/src/main/org/bson/codecs/MapCodec.java b/bson/src/main/org/bson/codecs/MapCodec.java new file mode 100644 index 00000000000..c0b6b7e6d41 --- /dev/null +++ b/bson/src/main/org/bson/codecs/MapCodec.java @@ -0,0 +1,178 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonReader; +import org.bson.BsonType; +import org.bson.BsonWriter; +import org.bson.Transformer; +import org.bson.UuidRepresentation; +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static java.util.Arrays.asList; +import static org.bson.assertions.Assertions.notNull; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; + +/** + * A Codec for Map instances. + * + * @since 3.5 + */ +public class MapCodec implements Codec>, OverridableUuidRepresentationCodec> { + + private static final CodecRegistry DEFAULT_REGISTRY = fromProviders(asList(new ValueCodecProvider(), new BsonValueCodecProvider(), + new DocumentCodecProvider(), new IterableCodecProvider(), new MapCodecProvider())); + private static final BsonTypeClassMap DEFAULT_BSON_TYPE_CLASS_MAP = new BsonTypeClassMap(); + private final BsonTypeCodecMap bsonTypeCodecMap; + private final CodecRegistry registry; + private final Transformer valueTransformer; + private final UuidRepresentation uuidRepresentation; + + /** + * Construct a new instance with a default {@code CodecRegistry} + */ + public MapCodec() { + this(DEFAULT_REGISTRY); + } + + /** + Construct a new instance with the given registry + * + * @param registry the registry + */ + public MapCodec(final CodecRegistry registry) { + this(registry, DEFAULT_BSON_TYPE_CLASS_MAP); + } + + /** + * Construct a new instance with the given registry and BSON type class map. + * + * @param registry the registry + * @param bsonTypeClassMap the BSON type class map + */ + public MapCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTypeClassMap) { + this(registry, bsonTypeClassMap, null); + } + + /** + * Construct a new instance with the given registry and BSON type class map. The transformer is applied as a last step when decoding + * values, which allows users of this codec to control the decoding process. For example, a user of this class could substitute a + * value decoded as a Document with an instance of a special purpose class (e.g., one representing a DBRef in MongoDB). + * + * @param registry the registry + * @param bsonTypeClassMap the BSON type class map + * @param valueTransformer the value transformer to use as a final step when decoding the value of any field in the map + */ + public MapCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTypeClassMap, final Transformer valueTransformer) { + this(registry, new BsonTypeCodecMap(notNull("bsonTypeClassMap", bsonTypeClassMap), registry), valueTransformer, + UuidRepresentation.JAVA_LEGACY); + } + + private MapCodec(final CodecRegistry registry, final BsonTypeCodecMap bsonTypeCodecMap, final Transformer valueTransformer, + final UuidRepresentation uuidRepresentation) { + this.registry = notNull("registry", registry); + this.bsonTypeCodecMap = bsonTypeCodecMap; + this.valueTransformer = valueTransformer != null ? valueTransformer : new Transformer() { + @Override + public Object transform(final Object value) { + return value; + } + }; + this.uuidRepresentation = uuidRepresentation; + } + + @Override + public Codec> withUuidRepresentation(final UuidRepresentation uuidRepresentation) { + return new MapCodec(registry, bsonTypeCodecMap, valueTransformer, uuidRepresentation); + } + + @Override + public void encode(final BsonWriter writer, final Map map, final EncoderContext encoderContext) { + writer.writeStartDocument(); + for (final Map.Entry entry : map.entrySet()) { + writer.writeName(entry.getKey()); + writeValue(writer, encoderContext, entry.getValue()); + } + writer.writeEndDocument(); + } + + @Override + public Map decode(final BsonReader reader, final DecoderContext decoderContext) { + Map map = new HashMap(); + + reader.readStartDocument(); + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + String fieldName = reader.readName(); + map.put(fieldName, readValue(reader, decoderContext)); + } + + reader.readEndDocument(); + return map; + } + + @SuppressWarnings("unchecked") + @Override + public Class> getEncoderClass() { + return (Class>) ((Class) Map.class); + } + + private Object readValue(final BsonReader reader, final DecoderContext decoderContext) { + BsonType bsonType = reader.getCurrentBsonType(); + if (bsonType == BsonType.NULL) { + reader.readNull(); + return null; + } else if (bsonType == BsonType.ARRAY) { + return decoderContext.decodeWithChildContext(registry.get(List.class), reader); + } else if (bsonType == BsonType.BINARY && reader.peekBinarySize() == 16) { + Codec codec = bsonTypeCodecMap.get(bsonType); + switch (reader.peekBinarySubType()) { + case 3: + if (uuidRepresentation == UuidRepresentation.JAVA_LEGACY + || uuidRepresentation == UuidRepresentation.C_SHARP_LEGACY + || uuidRepresentation == UuidRepresentation.PYTHON_LEGACY) { + codec = registry.get(UUID.class); + } + break; + case 4: + if (uuidRepresentation == UuidRepresentation.JAVA_LEGACY || uuidRepresentation == UuidRepresentation.STANDARD) { + codec = registry.get(UUID.class); + } + break; + default: + break; + } + + return decoderContext.decodeWithChildContext(codec, reader); + } + return valueTransformer.transform(bsonTypeCodecMap.get(bsonType).decode(reader, decoderContext)); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void writeValue(final BsonWriter writer, final EncoderContext encoderContext, final Object value) { + if (value == null) { + writer.writeNull(); + } else { + Codec codec = registry.get(value.getClass()); + encoderContext.encodeWithChildContext(codec, writer, value); + } + } +} diff --git a/bson/src/main/org/bson/codecs/MapCodecProvider.java b/bson/src/main/org/bson/codecs/MapCodecProvider.java new file mode 100644 index 00000000000..348908eb6b5 --- /dev/null +++ b/bson/src/main/org/bson/codecs/MapCodecProvider.java @@ -0,0 +1,110 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.Transformer; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.Map; + +import static org.bson.assertions.Assertions.notNull; + +/** + * A {@code CodecProvider} for the Map class and all the default Codec implementations on which it depends. + * + * @since 3.5 + */ +public class MapCodecProvider implements CodecProvider { + private final BsonTypeClassMap bsonTypeClassMap; + private final Transformer valueTransformer; + + /** + * Construct a new instance with a default {@code BsonTypeClassMap}. + */ + public MapCodecProvider() { + this(new BsonTypeClassMap()); + } + + /** + * Construct a new instance with the given instance of {@code BsonTypeClassMap}. + * + * @param bsonTypeClassMap the non-null {@code BsonTypeClassMap} with which to construct instances of {@code DocumentCodec} and {@code + * ListCodec} + */ + public MapCodecProvider(final BsonTypeClassMap bsonTypeClassMap) { + this(bsonTypeClassMap, null); + } + + /** + * Construct a new instance with a default {@code BsonTypeClassMap} and the given {@code Transformer}. The transformer is used by the + * MapCodec as a last step when decoding values. + * + * @param valueTransformer the value transformer for decoded values + */ + public MapCodecProvider(final Transformer valueTransformer) { + this(new BsonTypeClassMap(), valueTransformer); + } + + /** + * Construct a new instance with the given instance of {@code BsonTypeClassMap}. + * + * @param bsonTypeClassMap the non-null {@code BsonTypeClassMap} with which to construct instances of {@code MapCodec}. + * @param valueTransformer the value transformer for decoded values + */ + public MapCodecProvider(final BsonTypeClassMap bsonTypeClassMap, final Transformer valueTransformer) { + this.bsonTypeClassMap = notNull("bsonTypeClassMap", bsonTypeClassMap); + this.valueTransformer = valueTransformer; + } + + @Override + @SuppressWarnings("unchecked") + public Codec get(final Class clazz, final CodecRegistry registry) { + if (Map.class.isAssignableFrom(clazz)) { + return (Codec) new MapCodec(registry, bsonTypeClassMap, valueTransformer); + } + + return null; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MapCodecProvider that = (MapCodecProvider) o; + if (!bsonTypeClassMap.equals(that.bsonTypeClassMap)) { + return false; + } + if (valueTransformer != null ? !valueTransformer.equals(that.valueTransformer) : that.valueTransformer != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = bsonTypeClassMap.hashCode(); + result = 31 * result + (valueTransformer != null ? valueTransformer.hashCode() : 0); + return result; + } +} diff --git a/bson/src/main/org/bson/codecs/MaxKeyCodec.java b/bson/src/main/org/bson/codecs/MaxKeyCodec.java index 954c0d27476..a8907e5a9a4 100644 --- a/bson/src/main/org/bson/codecs/MaxKeyCodec.java +++ b/bson/src/main/org/bson/codecs/MaxKeyCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/MinKeyCodec.java b/bson/src/main/org/bson/codecs/MinKeyCodec.java index beb26a5e724..c3bb34efaa9 100644 --- a/bson/src/main/org/bson/codecs/MinKeyCodec.java +++ b/bson/src/main/org/bson/codecs/MinKeyCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/NumberCodecHelper.java b/bson/src/main/org/bson/codecs/NumberCodecHelper.java new file mode 100644 index 00000000000..69dfe29ac7e --- /dev/null +++ b/bson/src/main/org/bson/codecs/NumberCodecHelper.java @@ -0,0 +1,134 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.BsonReader; +import org.bson.BsonType; +import org.bson.types.Decimal128; + +import java.math.BigDecimal; + +import static java.lang.String.format; + +final class NumberCodecHelper { + + static int decodeInt(final BsonReader reader) { + int intValue; + BsonType bsonType = reader.getCurrentBsonType(); + switch (bsonType) { + case INT32: + intValue = reader.readInt32(); + break; + case INT64: + long longValue = reader.readInt64(); + intValue = (int) longValue; + if (longValue != (long) intValue) { + throw invalidConversion(Integer.class, longValue); + } + break; + case DOUBLE: + double doubleValue = reader.readDouble(); + intValue = (int) doubleValue; + if (doubleValue != (double) intValue) { + throw invalidConversion(Integer.class, doubleValue); + } + break; + case DECIMAL128: + Decimal128 decimal128 = reader.readDecimal128(); + intValue = decimal128.intValue(); + if (!decimal128.equals(new Decimal128(intValue))) { + throw invalidConversion(Integer.class, decimal128); + } + break; + default: + throw new BsonInvalidOperationException(format("Invalid numeric type, found: %s", bsonType)); + } + return intValue; + } + + static long decodeLong(final BsonReader reader) { + long longValue; + BsonType bsonType = reader.getCurrentBsonType(); + switch (bsonType) { + case INT32: + longValue = reader.readInt32(); + break; + case INT64: + longValue = reader.readInt64(); + break; + case DOUBLE: + double doubleValue = reader.readDouble(); + longValue = (long) doubleValue; + if (doubleValue != (double) longValue) { + throw invalidConversion(Long.class, doubleValue); + } + break; + case DECIMAL128: + Decimal128 decimal128 = reader.readDecimal128(); + longValue = decimal128.longValue(); + if (!decimal128.equals(new Decimal128(longValue))) { + throw invalidConversion(Long.class, decimal128); + } + break; + default: + throw new BsonInvalidOperationException(format("Invalid numeric type, found: %s", bsonType)); + } + return longValue; + } + + static double decodeDouble(final BsonReader reader) { + double doubleValue; + BsonType bsonType = reader.getCurrentBsonType(); + switch (bsonType) { + case INT32: + doubleValue = reader.readInt32(); + break; + case INT64: + long longValue = reader.readInt64(); + doubleValue = longValue; + if (longValue != (long) doubleValue) { + throw invalidConversion(Double.class, longValue); + } + break; + case DOUBLE: + doubleValue = reader.readDouble(); + break; + case DECIMAL128: + Decimal128 decimal128 = reader.readDecimal128(); + try { + doubleValue = decimal128.doubleValue(); + if (!decimal128.equals(new Decimal128(new BigDecimal(doubleValue)))) { + throw invalidConversion(Double.class, decimal128); + } + } catch (NumberFormatException e) { + throw invalidConversion(Double.class, decimal128); + } + break; + default: + throw new BsonInvalidOperationException(format("Invalid numeric type, found: %s", bsonType)); + } + return doubleValue; + } + + private static BsonInvalidOperationException invalidConversion(final Class clazz, final Number value) { + return new BsonInvalidOperationException(format("Could not convert `%s` to a %s without losing precision", value, clazz)); + } + + private NumberCodecHelper() { + } +} diff --git a/bson/src/main/org/bson/codecs/ObjectIdCodec.java b/bson/src/main/org/bson/codecs/ObjectIdCodec.java index 884ea567c48..d688bf69087 100644 --- a/bson/src/main/org/bson/codecs/ObjectIdCodec.java +++ b/bson/src/main/org/bson/codecs/ObjectIdCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/ObjectIdGenerator.java b/bson/src/main/org/bson/codecs/ObjectIdGenerator.java index ead66748685..9191ebb1d60 100644 --- a/bson/src/main/org/bson/codecs/ObjectIdGenerator.java +++ b/bson/src/main/org/bson/codecs/ObjectIdGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/OverridableUuidRepresentationCodec.java b/bson/src/main/org/bson/codecs/OverridableUuidRepresentationCodec.java new file mode 100644 index 00000000000..f0f392dd140 --- /dev/null +++ b/bson/src/main/org/bson/codecs/OverridableUuidRepresentationCodec.java @@ -0,0 +1,34 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.UuidRepresentation; + +/** + * A marker interface for {@code Codec} implementations that can derive a new instance that overrides the {@code UuidRepresentation}. + * @param the value type + * @since 3.12 + */ +public interface OverridableUuidRepresentationCodec { + /** + * Implementations must return a new instance with the {@code UuidRepresentation} overridden with the given value. + * + * @param uuidRepresentation the UuidRepresentation + * @return a new instance equivalent to this but with the given UuidRepresentation + */ + Codec withUuidRepresentation(UuidRepresentation uuidRepresentation); +} diff --git a/bson/src/main/org/bson/codecs/OverridableUuidRepresentationUuidCodec.java b/bson/src/main/org/bson/codecs/OverridableUuidRepresentationUuidCodec.java new file mode 100644 index 00000000000..b86be1db320 --- /dev/null +++ b/bson/src/main/org/bson/codecs/OverridableUuidRepresentationUuidCodec.java @@ -0,0 +1,50 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.UuidRepresentation; + +import java.util.UUID; + +/** + * An extension of {@code UuidCodec} that allows its configured {@code UuidRepresentation} to be overridden by an externally configured + * {@code UuidRepresentation}, most likely configured on {@code MongoClientSettings} or {@code MongoClientOptions}. + * + * @since 3.12 + */ +public class OverridableUuidRepresentationUuidCodec extends UuidCodec implements OverridableUuidRepresentationCodec { + + /** + * Construct an instance with the default UUID representation. + */ + public OverridableUuidRepresentationUuidCodec() { + } + + /** + * Construct an instance with the given UUID representation. + * + * @param uuidRepresentation the UUID representation + */ + public OverridableUuidRepresentationUuidCodec(final UuidRepresentation uuidRepresentation) { + super(uuidRepresentation); + } + + @Override + public Codec withUuidRepresentation(final UuidRepresentation uuidRepresentation) { + return new OverridableUuidRepresentationUuidCodec(uuidRepresentation); + } +} diff --git a/bson/src/main/org/bson/codecs/PatternCodec.java b/bson/src/main/org/bson/codecs/PatternCodec.java index 28a5f70468b..8df0f603c5a 100644 --- a/bson/src/main/org/bson/codecs/PatternCodec.java +++ b/bson/src/main/org/bson/codecs/PatternCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,7 +94,7 @@ private static int getOptionsAsInt(final BsonRegularExpression regularExpression private static final int GLOBAL_FLAG = 256; - private static enum RegexFlag { + private enum RegexFlag { CANON_EQ(Pattern.CANON_EQ, 'c', "Pattern.CANON_EQ"), UNIX_LINES(Pattern.UNIX_LINES, 'd', "Pattern.UNIX_LINES"), GLOBAL(GLOBAL_FLAG, 'g', null), diff --git a/bson/src/main/org/bson/codecs/RawBsonDocumentCodec.java b/bson/src/main/org/bson/codecs/RawBsonDocumentCodec.java index 24ad9b5691d..4d7b46fc291 100644 --- a/bson/src/main/org/bson/codecs/RawBsonDocumentCodec.java +++ b/bson/src/main/org/bson/codecs/RawBsonDocumentCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/ShortCodec.java b/bson/src/main/org/bson/codecs/ShortCodec.java index bdc07ee2118..e5aaf8f9acb 100644 --- a/bson/src/main/org/bson/codecs/ShortCodec.java +++ b/bson/src/main/org/bson/codecs/ShortCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,20 @@ package org.bson.codecs; +import org.bson.BsonInvalidOperationException; import org.bson.BsonReader; import org.bson.BsonWriter; +import static java.lang.String.format; +import static org.bson.codecs.NumberCodecHelper.decodeInt; + /** * Encodes and decodes {@code Short} objects. * * @since 3.0 */ public class ShortCodec implements Codec { + @Override public void encode(final BsonWriter writer, final Short value, final EncoderContext encoderContext) { writer.writeInt32(value); @@ -32,7 +37,11 @@ public void encode(final BsonWriter writer, final Short value, final EncoderCont @Override public Short decode(final BsonReader reader, final DecoderContext decoderContext) { - throw new UnsupportedOperationException(); + int value = decodeInt(reader); + if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { + throw new BsonInvalidOperationException(format("%s can not be converted into a Short.", value)); + } + return (short) value; } @Override diff --git a/bson/src/main/org/bson/codecs/StringCodec.java b/bson/src/main/org/bson/codecs/StringCodec.java index 2673de062ca..f08508341a2 100644 --- a/bson/src/main/org/bson/codecs/StringCodec.java +++ b/bson/src/main/org/bson/codecs/StringCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/SymbolCodec.java b/bson/src/main/org/bson/codecs/SymbolCodec.java index ff3c5f66326..3b1d5a1c086 100644 --- a/bson/src/main/org/bson/codecs/SymbolCodec.java +++ b/bson/src/main/org/bson/codecs/SymbolCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/UuidCodec.java b/bson/src/main/org/bson/codecs/UuidCodec.java index 7ac576ce0b8..dc30ebeb2e9 100644 --- a/bson/src/main/org/bson/codecs/UuidCodec.java +++ b/bson/src/main/org/bson/codecs/UuidCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,14 @@ import org.bson.BsonBinary; import org.bson.BsonBinarySubType; import org.bson.BsonReader; -import org.bson.BsonSerializationException; import org.bson.BsonWriter; import org.bson.UuidRepresentation; +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.internal.UuidHelper; import java.util.UUID; -import static org.bson.codecs.UuidCodecHelper.reverseByteArray; +import static org.bson.assertions.Assertions.notNull; /** * Encodes and decodes {@code UUID} objects. @@ -35,8 +36,7 @@ */ public class UuidCodec implements Codec { - private final UuidRepresentation encoderUuidRepresentation; - private final UuidRepresentation decoderUuidRepresentation; + private final UuidRepresentation uuidRepresentation; /** * The default UUIDRepresentation is JAVA_LEGACY to be compatible with existing documents @@ -45,41 +45,35 @@ public class UuidCodec implements Codec { * @see org.bson.UuidRepresentation */ public UuidCodec(final UuidRepresentation uuidRepresentation) { - this.encoderUuidRepresentation = uuidRepresentation; - this.decoderUuidRepresentation = uuidRepresentation; + notNull("uuidRepresentation", uuidRepresentation); + this.uuidRepresentation = uuidRepresentation; } /** * The constructor for UUIDCodec, default is JAVA_LEGACY */ public UuidCodec() { - this.encoderUuidRepresentation = UuidRepresentation.JAVA_LEGACY; - this.decoderUuidRepresentation = UuidRepresentation.JAVA_LEGACY; + this.uuidRepresentation = UuidRepresentation.JAVA_LEGACY; + } + + /** + * The {@code UuidRepresentation} with which this instance is configured + * + * @return the uuid representation + * @since 3.12 + */ + public UuidRepresentation getUuidRepresentation() { + return uuidRepresentation; } @Override public void encode(final BsonWriter writer, final UUID value, final EncoderContext encoderContext) { - byte[] binaryData = new byte[16]; - writeLongToArrayBigEndian(binaryData, 0, value.getMostSignificantBits()); - writeLongToArrayBigEndian(binaryData, 8, value.getLeastSignificantBits()); - switch (encoderUuidRepresentation) { - case C_SHARP_LEGACY: - UuidCodecHelper.reverseByteArray(binaryData, 0, 4); - UuidCodecHelper.reverseByteArray(binaryData, 4, 2); - UuidCodecHelper.reverseByteArray(binaryData, 6, 2); - break; - case JAVA_LEGACY: - UuidCodecHelper.reverseByteArray(binaryData, 0, 8); - UuidCodecHelper.reverseByteArray(binaryData, 8, 8); - break; - case PYTHON_LEGACY: - case STANDARD: - break; - default: - throw new BSONException("Unexpected UUID representation"); + if (uuidRepresentation == UuidRepresentation.UNSPECIFIED) { + throw new CodecConfigurationException("The uuidRepresentation has not been specified, so the UUID cannot be encoded."); } + byte[] binaryData = UuidHelper.encodeUuidToBinary(value, uuidRepresentation); // changed the default subtype to STANDARD since 3.0 - if (encoderUuidRepresentation == UuidRepresentation.STANDARD) { + if (uuidRepresentation == UuidRepresentation.STANDARD) { writer.writeBinaryData(new BsonBinary(BsonBinarySubType.UUID_STANDARD, binaryData)); } else { writer.writeBinaryData(new BsonBinary(BsonBinarySubType.UUID_LEGACY, binaryData)); @@ -96,30 +90,7 @@ public UUID decode(final BsonReader reader, final DecoderContext decoderContext) byte[] bytes = reader.readBinaryData().getData(); - if (bytes.length != 16) { - throw new BsonSerializationException(String.format("Expected length to be 16, not %d.", bytes.length)); - } - - if (subType == BsonBinarySubType.UUID_LEGACY.getValue()) { - switch (decoderUuidRepresentation) { - case C_SHARP_LEGACY: - reverseByteArray(bytes, 0, 4); - reverseByteArray(bytes, 4, 2); - reverseByteArray(bytes, 6, 2); - break; - case JAVA_LEGACY: - reverseByteArray(bytes, 0, 8); - reverseByteArray(bytes, 8, 8); - break; - case PYTHON_LEGACY: - case STANDARD: - break; - default: - throw new BSONException("Unexpected UUID representation"); - } - } - - return new UUID(readLongFromArrayBigEndian(bytes, 0), readLongFromArrayBigEndian(bytes, 8)); + return UuidHelper.decodeBinaryToUuid(bytes, subType, uuidRepresentation); } @Override @@ -127,28 +98,10 @@ public Class getEncoderClass() { return UUID.class; } - private static void writeLongToArrayBigEndian(final byte[] bytes, final int offset, final long x) { - bytes[offset + 7] = (byte) (0xFFL & (x)); - bytes[offset + 6] = (byte) (0xFFL & (x >> 8)); - bytes[offset + 5] = (byte) (0xFFL & (x >> 16)); - bytes[offset + 4] = (byte) (0xFFL & (x >> 24)); - bytes[offset + 3] = (byte) (0xFFL & (x >> 32)); - bytes[offset + 2] = (byte) (0xFFL & (x >> 40)); - bytes[offset + 1] = (byte) (0xFFL & (x >> 48)); - bytes[offset] = (byte) (0xFFL & (x >> 56)); - } - - private static long readLongFromArrayBigEndian(final byte[] bytes, final int offset) { - long x = 0; - x |= (0xFFL & bytes[offset + 7]); - x |= (0xFFL & bytes[offset + 6]) << 8; - x |= (0xFFL & bytes[offset + 5]) << 16; - x |= (0xFFL & bytes[offset + 4]) << 24; - x |= (0xFFL & bytes[offset + 3]) << 32; - x |= (0xFFL & bytes[offset + 2]) << 40; - x |= (0xFFL & bytes[offset + 1]) << 48; - x |= (0xFFL & bytes[offset]) << 56; - return x; + @Override + public String toString() { + return "UuidCodec{" + + "uuidRepresentation=" + uuidRepresentation + + '}'; } - } diff --git a/bson/src/main/org/bson/codecs/UuidCodecHelper.java b/bson/src/main/org/bson/codecs/UuidCodecHelper.java index 30f6215f7e8..5087c212dee 100644 --- a/bson/src/main/org/bson/codecs/UuidCodecHelper.java +++ b/bson/src/main/org/bson/codecs/UuidCodecHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/UuidCodecProvider.java b/bson/src/main/org/bson/codecs/UuidCodecProvider.java index 9f2d507d167..25de388c3d2 100644 --- a/bson/src/main/org/bson/codecs/UuidCodecProvider.java +++ b/bson/src/main/org/bson/codecs/UuidCodecProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/ValueCodecProvider.java b/bson/src/main/org/bson/codecs/ValueCodecProvider.java index facf3106dcd..9420b50568a 100644 --- a/bson/src/main/org/bson/codecs/ValueCodecProvider.java +++ b/bson/src/main/org/bson/codecs/ValueCodecProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ *
  • {@link org.bson.codecs.DoubleCodec}
  • *
  • {@link org.bson.codecs.IntegerCodec}
  • *
  • {@link org.bson.codecs.LongCodec}
  • + *
  • {@link org.bson.codecs.Decimal128Codec}
  • *
  • {@link org.bson.codecs.MinKeyCodec}
  • *
  • {@link org.bson.codecs.MaxKeyCodec}
  • *
  • {@link org.bson.codecs.CodeCodec}
  • @@ -78,11 +79,13 @@ private void addCodecs() { addCodec(new MinKeyCodec()); addCodec(new MaxKeyCodec()); addCodec(new CodeCodec()); + addCodec(new Decimal128Codec()); + addCodec(new BigDecimalCodec()); addCodec(new ObjectIdCodec()); addCodec(new CharacterCodec()); addCodec(new StringCodec()); addCodec(new SymbolCodec()); - addCodec(new UuidCodec()); + addCodec(new OverridableUuidRepresentationUuidCodec()); addCodec(new ByteCodec()); addCodec(new PatternCodec()); diff --git a/bson/src/main/org/bson/codecs/configuration/CodecConfigurationException.java b/bson/src/main/org/bson/codecs/configuration/CodecConfigurationException.java index a5f5997fbe4..13d5c79dfa5 100644 --- a/bson/src/main/org/bson/codecs/configuration/CodecConfigurationException.java +++ b/bson/src/main/org/bson/codecs/configuration/CodecConfigurationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,4 +34,15 @@ public class CodecConfigurationException extends RuntimeException { public CodecConfigurationException(final String msg) { super(msg); } + + /** + * Construct a new instance and wraps a cause + * + * @param message the message + * @param cause the underlying cause + * @since 3.5 + */ + public CodecConfigurationException(final String message, final Throwable cause) { + super(message, cause); + } } diff --git a/bson/src/main/org/bson/codecs/configuration/CodecProvider.java b/bson/src/main/org/bson/codecs/configuration/CodecProvider.java index 699cced1f82..b667067d4ff 100644 --- a/bson/src/main/org/bson/codecs/configuration/CodecProvider.java +++ b/bson/src/main/org/bson/codecs/configuration/CodecProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/configuration/CodecRegistries.java b/bson/src/main/org/bson/codecs/configuration/CodecRegistries.java index e692a9e5cd0..f4821e9ea3c 100644 --- a/bson/src/main/org/bson/codecs/configuration/CodecRegistries.java +++ b/bson/src/main/org/bson/codecs/configuration/CodecRegistries.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -17,6 +17,7 @@ package org.bson.codecs.configuration; import org.bson.codecs.Codec; +import org.bson.internal.ProvidersCodecRegistry; import java.util.ArrayList; import java.util.List; diff --git a/bson/src/main/org/bson/codecs/configuration/CodecRegistry.java b/bson/src/main/org/bson/codecs/configuration/CodecRegistry.java index 31fb577b91f..0ccad168756 100644 --- a/bson/src/main/org/bson/codecs/configuration/CodecRegistry.java +++ b/bson/src/main/org/bson/codecs/configuration/CodecRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/configuration/MapOfCodecsProvider.java b/bson/src/main/org/bson/codecs/configuration/MapOfCodecsProvider.java index acf22aa94fd..e98ff3b7729 100644 --- a/bson/src/main/org/bson/codecs/configuration/MapOfCodecsProvider.java +++ b/bson/src/main/org/bson/codecs/configuration/MapOfCodecsProvider.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -25,7 +25,7 @@ final class MapOfCodecsProvider implements CodecProvider { private final Map, Codec> codecsMap = new HashMap, Codec>(); - public MapOfCodecsProvider(final List> codecsList) { + MapOfCodecsProvider(final List> codecsList) { for (Codec codec : codecsList) { codecsMap.put(codec.getEncoderClass(), codec); } diff --git a/bson/src/main/org/bson/codecs/configuration/Optional.java b/bson/src/main/org/bson/codecs/configuration/Optional.java deleted file mode 100644 index 312554be785..00000000000 --- a/bson/src/main/org/bson/codecs/configuration/Optional.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015 MongoDB, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bson.codecs.configuration; - -import java.util.NoSuchElementException; - - -abstract class Optional { - - private static final Optional NONE = new Optional() { - @Override - public Object get() { - throw new NoSuchElementException(".get call on None!"); - } - - @Override - public boolean isEmpty() { - return true; - } - }; - - @SuppressWarnings("unchecked") - public static Optional empty() { - return (Optional) NONE; - } - - @SuppressWarnings("unchecked") - public static Optional of(final T it) { - if (it == null) { - return (Optional) Optional.NONE; - } else { - return new Some(it); - } - } - - public abstract T get(); - - public abstract boolean isEmpty(); - - @Override - public String toString() { - return "None"; - } - - public boolean isDefined() { - return !isEmpty(); - } - - public static class Some extends Optional { - private final T value; - - Some(final T value) { - this.value = value; - } - - @Override - public T get() { - return value; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public String toString() { - return String.format("Some(%s)", value); - } - } -} diff --git a/bson/src/main/org/bson/codecs/configuration/package-info.java b/bson/src/main/org/bson/codecs/configuration/package-info.java index 04c1566d9be..a4c3e3847ea 100644 --- a/bson/src/main/org/bson/codecs/configuration/package-info.java +++ b/bson/src/main/org/bson/codecs/configuration/package-info.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/bson/src/main/org/bson/codecs/jsr310/DateTimeBasedCodec.java b/bson/src/main/org/bson/codecs/jsr310/DateTimeBasedCodec.java new file mode 100644 index 00000000000..b956d022198 --- /dev/null +++ b/bson/src/main/org/bson/codecs/jsr310/DateTimeBasedCodec.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310; + +import org.bson.BsonReader; +import org.bson.BsonType; +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecConfigurationException; + +import static java.lang.String.format; + +abstract class DateTimeBasedCodec implements Codec { + + long validateAndReadDateTime(final BsonReader reader) { + BsonType currentType = reader.getCurrentBsonType(); + if (!currentType.equals(BsonType.DATE_TIME)) { + throw new CodecConfigurationException(format("Could not decode into %s, expected '%s' BsonType but got '%s'.", + getEncoderClass().getSimpleName(), BsonType.DATE_TIME, currentType)); + } + return reader.readDateTime(); + } + +} diff --git a/bson/src/main/org/bson/codecs/jsr310/InstantCodec.java b/bson/src/main/org/bson/codecs/jsr310/InstantCodec.java new file mode 100644 index 00000000000..79134024b7a --- /dev/null +++ b/bson/src/main/org/bson/codecs/jsr310/InstantCodec.java @@ -0,0 +1,66 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2018 Cezary Bartosiak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.time.Instant; + +import static java.lang.String.format; + +/** + * Instant Codec. + * + *

    + * Encodes and decodes {@code Instant} objects to and from {@code DateTime}. Data is stored to millisecond accuracy. + *

    + *

    Note: Requires Java 8 or greater.

    + * + * @mongodb.driver.manual reference/bson-types + * @since 3.7 + */ +public class InstantCodec extends DateTimeBasedCodec { + + @Override + public Instant decode(final BsonReader reader, final DecoderContext decoderContext) { + return Instant.ofEpochMilli(validateAndReadDateTime(reader)); + } + + /** + * {@inheritDoc} + * @throws CodecConfigurationException if the Instant cannot be converted to a valid Bson DateTime. + */ + @Override + public void encode(final BsonWriter writer, final Instant value, final EncoderContext encoderContext) { + try { + writer.writeDateTime(value.toEpochMilli()); + } catch (ArithmeticException e) { + throw new CodecConfigurationException(format("Unsupported Instant value '%s' could not be converted to milliseconds: %s", + value, e.getMessage()), e); + } + } + + @Override + public Class getEncoderClass() { + return Instant.class; + } +} diff --git a/bson/src/main/org/bson/codecs/jsr310/Jsr310CodecProvider.java b/bson/src/main/org/bson/codecs/jsr310/Jsr310CodecProvider.java new file mode 100644 index 00000000000..941fc15cfff --- /dev/null +++ b/bson/src/main/org/bson/codecs/jsr310/Jsr310CodecProvider.java @@ -0,0 +1,65 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2018 Cezary Bartosiak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.HashMap; +import java.util.Map; + +/** + * A CodecProvider for JSR-310 Date and Time API classes. + * + *

    + * Supplies the following JSR-310 based Codecs: + *

      + *
    • {@link InstantCodec} + *
    • {@link LocalDateCodec} + *
    • {@link LocalDateTimeCodec} + *
    • {@link LocalTimeCodec} + *
    + *

    Requires Java 8 or greater.

    + * + * @since 3.7 + */ +public class Jsr310CodecProvider implements CodecProvider { + private static final Map, Codec> JSR310_CODEC_MAP = new HashMap, Codec>(); + static { + try { + Class.forName("java.time.Instant"); // JSR-310 support canary test. + putCodec(new InstantCodec()); + putCodec(new LocalDateCodec()); + putCodec(new LocalDateTimeCodec()); + putCodec(new LocalTimeCodec()); + } catch (ClassNotFoundException e) { + // No JSR-310 support + } + } + + private static void putCodec(final Codec codec) { + JSR310_CODEC_MAP.put(codec.getEncoderClass(), codec); + } + + @SuppressWarnings("unchecked") + @Override + public Codec get(final Class clazz, final CodecRegistry registry) { + return (Codec) JSR310_CODEC_MAP.get(clazz); + } +} diff --git a/bson/src/main/org/bson/codecs/jsr310/LocalDateCodec.java b/bson/src/main/org/bson/codecs/jsr310/LocalDateCodec.java new file mode 100644 index 00000000000..3b273df3455 --- /dev/null +++ b/bson/src/main/org/bson/codecs/jsr310/LocalDateCodec.java @@ -0,0 +1,69 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2018 Cezary Bartosiak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZoneOffset; + +import static java.lang.String.format; + +/** + * LocalDate Codec. + * + *

    Encodes and decodes {@code LocalDate} objects to and from {@code DateTime}.

    + *

    Converts the {@code LocalDate} values to and from {@link ZoneOffset#UTC}.

    + *

    Note: Requires Java 8 or greater.

    + * + * @mongodb.driver.manual reference/bson-types + * @since 3.7 + */ +public class LocalDateCodec extends DateTimeBasedCodec { + + @Override + public LocalDate decode(final BsonReader reader, final DecoderContext decoderContext) { + return Instant.ofEpochMilli(validateAndReadDateTime(reader)).atZone(ZoneOffset.UTC).toLocalDate(); + } + + /** + * {@inheritDoc} + *

    Converts the {@code LocalDate} to {@link ZoneOffset#UTC} via {@link LocalDate#atStartOfDay(ZoneId)}.

    + * @throws CodecConfigurationException if the LocalDate cannot be converted to a valid Bson DateTime. + */ + @Override + public void encode(final BsonWriter writer, final LocalDate value, final EncoderContext encoderContext) { + try { + writer.writeDateTime(value.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()); + } catch (ArithmeticException e) { + throw new CodecConfigurationException(format("Unsupported LocalDate '%s' could not be converted to milliseconds: %s", + value, e.getMessage()), e); + } + } + + @Override + public Class getEncoderClass() { + return LocalDate.class; + } +} diff --git a/bson/src/main/org/bson/codecs/jsr310/LocalDateTimeCodec.java b/bson/src/main/org/bson/codecs/jsr310/LocalDateTimeCodec.java new file mode 100644 index 00000000000..a1cf068aecf --- /dev/null +++ b/bson/src/main/org/bson/codecs/jsr310/LocalDateTimeCodec.java @@ -0,0 +1,68 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2018 Cezary Bartosiak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import static java.lang.String.format; + +/** + * LocalDateTime Codec. + * + *

    Encodes and decodes {@code LocalDateTime} objects to and from {@code DateTime}. Data is stored to millisecond accuracy.

    + *

    Converts the {@code LocalDateTime} values to and from {@link ZoneOffset#UTC}.

    + *

    Note: Requires Java 8 or greater.

    + * + * @mongodb.driver.manual reference/bson-types + * @since 3.7 + */ +public class LocalDateTimeCodec extends DateTimeBasedCodec { + + @Override + public LocalDateTime decode(final BsonReader reader, final DecoderContext decoderContext) { + return Instant.ofEpochMilli(validateAndReadDateTime(reader)).atZone(ZoneOffset.UTC).toLocalDateTime(); + } + + /** + * {@inheritDoc} + *

    Converts the {@code LocalDateTime} to {@link ZoneOffset#UTC} via {@link LocalDateTime#toInstant(ZoneOffset)}.

    + * @throws CodecConfigurationException if the LocalDateTime cannot be converted to a valid Bson DateTime. + */ + @Override + public void encode(final BsonWriter writer, final LocalDateTime value, final EncoderContext encoderContext) { + try { + writer.writeDateTime(value.toInstant(ZoneOffset.UTC).toEpochMilli()); + } catch (ArithmeticException e) { + throw new CodecConfigurationException(format("Unsupported LocalDateTime value '%s' could not be converted to milliseconds: %s", + value, e.getMessage()), e); + } + } + + @Override + public Class getEncoderClass() { + return LocalDateTime.class; + } +} diff --git a/bson/src/main/org/bson/codecs/jsr310/LocalTimeCodec.java b/bson/src/main/org/bson/codecs/jsr310/LocalTimeCodec.java new file mode 100644 index 00000000000..8aa7081b765 --- /dev/null +++ b/bson/src/main/org/bson/codecs/jsr310/LocalTimeCodec.java @@ -0,0 +1,61 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2018 Cezary Bartosiak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; + +/** + * LocalTime Codec. + * + *

    Encodes and decodes {@code LocalTime} objects to and from {@code DateTime}. Data is stored to millisecond accuracy.

    + *

    Converts the {@code LocalTime} values to and from EpochDay at {@link ZoneOffset#UTC}.

    + *

    Note: Requires Java 8 or greater.

    + * + * @mongodb.driver.manual reference/bson-types + * @since 3.7 + */ +public class LocalTimeCodec extends DateTimeBasedCodec { + + @Override + public LocalTime decode(final BsonReader reader, final DecoderContext decoderContext) { + return Instant.ofEpochMilli(validateAndReadDateTime(reader)).atOffset(ZoneOffset.UTC).toLocalTime(); + } + + /** + * {@inheritDoc} + *

    Converts the {@code LocalTime} to {@link ZoneOffset#UTC} at EpochDay via {@link LocalTime#atDate(LocalDate)} and + * {@link java.time.LocalDateTime#toInstant(ZoneOffset)}.

    + */ + @Override + public void encode(final BsonWriter writer, final LocalTime value, final EncoderContext encoderContext) { + writer.writeDateTime(value.atDate(LocalDate.ofEpochDay(0L)).toInstant(ZoneOffset.UTC).toEpochMilli()); + } + + @Override + public Class getEncoderClass() { + return LocalTime.class; + } +} diff --git a/bson/src/main/org/bson/codecs/jsr310/package-info.java b/bson/src/main/org/bson/codecs/jsr310/package-info.java new file mode 100644 index 00000000000..6541a66af34 --- /dev/null +++ b/bson/src/main/org/bson/codecs/jsr310/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains classes specific to the JSR-310 Date and Time API + */ +package org.bson.codecs.jsr310; diff --git a/bson/src/main/org/bson/codecs/package-info.java b/bson/src/main/org/bson/codecs/package-info.java index b0aac1d1b3a..74000679ccd 100644 --- a/bson/src/main/org/bson/codecs/package-info.java +++ b/bson/src/main/org/bson/codecs/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/codecs/pojo/AutomaticPojoCodec.java b/bson/src/main/org/bson/codecs/pojo/AutomaticPojoCodec.java new file mode 100644 index 00000000000..3b001ee047c --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/AutomaticPojoCodec.java @@ -0,0 +1,69 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; + +import static java.lang.String.format; + +final class AutomaticPojoCodec extends PojoCodec { + private final PojoCodec pojoCodec; + + AutomaticPojoCodec(final PojoCodec pojoCodec) { + this.pojoCodec = pojoCodec; + } + + @Override + public T decode(final BsonReader reader, final DecoderContext decoderContext) { + try { + return pojoCodec.decode(reader, decoderContext); + } catch (CodecConfigurationException e) { + throw new CodecConfigurationException( + format("An exception occurred when decoding using the AutomaticPojoCodec.%n" + + "Decoding into a '%s' failed with the following exception:%n%n%s%n%n" + + "A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.", + pojoCodec.getEncoderClass().getSimpleName(), e.getMessage()), e); + } + } + + @Override + public void encode(final BsonWriter writer, final T value, final EncoderContext encoderContext) { + try { + pojoCodec.encode(writer, value, encoderContext); + } catch (CodecConfigurationException e) { + throw new CodecConfigurationException( + format("An exception occurred when encoding using the AutomaticPojoCodec.%n" + + "Encoding a %s: '%s' failed with the following exception:%n%n%s%n%n" + + "A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.", + getEncoderClass().getSimpleName(), value, e.getMessage()), e); + } + } + + @Override + public Class getEncoderClass() { + return pojoCodec.getEncoderClass(); + } + + @Override + ClassModel getClassModel() { + return pojoCodec.getClassModel(); + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/ClassModel.java b/bson/src/main/org/bson/codecs/pojo/ClassModel.java new file mode 100644 index 00000000000..3c039a178c3 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/ClassModel.java @@ -0,0 +1,232 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This model represents the metadata for a class and all its properties. + * + * @param The type of the class the ClassModel represents + * @since 3.5 + */ +public final class ClassModel { + private final String name; + private final Class type; + private final boolean hasTypeParameters; + private final InstanceCreatorFactory instanceCreatorFactory; + private final boolean discriminatorEnabled; + private final String discriminatorKey; + private final String discriminator; + private final IdPropertyModelHolder idPropertyModelHolder; + private final List> propertyModels; + private final Map propertyNameToTypeParameterMap; + + ClassModel(final Class clazz, final Map propertyNameToTypeParameterMap, + final InstanceCreatorFactory instanceCreatorFactory, final Boolean discriminatorEnabled, final String discriminatorKey, + final String discriminator, final IdPropertyModelHolder idPropertyModelHolder, + final List> propertyModels) { + this.name = clazz.getSimpleName(); + this.type = clazz; + this.hasTypeParameters = clazz.getTypeParameters().length > 0; + this.propertyNameToTypeParameterMap = Collections.unmodifiableMap( + new HashMap(propertyNameToTypeParameterMap)); + this.instanceCreatorFactory = instanceCreatorFactory; + this.discriminatorEnabled = discriminatorEnabled; + this.discriminatorKey = discriminatorKey; + this.discriminator = discriminator; + this.idPropertyModelHolder = idPropertyModelHolder; + this.propertyModels = Collections.unmodifiableList(new ArrayList>(propertyModels)); + } + + /** + * Creates a new Class Model builder instance using reflection. + * + * @param type the POJO class to reflect and configure the builder with. + * @param the type of the class + * @return a new Class Model builder instance using reflection on the {@code clazz}. + */ + public static ClassModelBuilder builder(final Class type) { + return new ClassModelBuilder(type); + } + + /** + * @return a new InstanceCreator instance for the ClassModel + */ + InstanceCreator getInstanceCreator() { + return instanceCreatorFactory.create(); + } + + /** + * @return the backing class for the ClassModel + */ + public Class getType() { + return type; + } + + /** + * @return true if the underlying type has type parameters. + */ + public boolean hasTypeParameters() { + return hasTypeParameters; + } + + /** + * @return true if a discriminator should be used when storing the data. + */ + public boolean useDiscriminator() { + return discriminatorEnabled; + } + + /** + * Gets the value for the discriminator. + * + * @return the discriminator value or null if not set + */ + public String getDiscriminatorKey() { + return discriminatorKey; + } + + /** + * Returns the discriminator key. + * + * @return the discriminator key or null if not set + */ + public String getDiscriminator() { + return discriminator; + } + + /** + * Gets a {@link PropertyModel} by the property name. + * + * @param propertyName the PropertyModel's property name + * @return the PropertyModel or null if the property is not found + */ + public PropertyModel getPropertyModel(final String propertyName) { + for (PropertyModel propertyModel : propertyModels) { + if (propertyModel.getName().equals(propertyName)) { + return propertyModel; + } + } + return null; + } + + /** + * Returns all the properties on this model + * + * @return the list of properties + */ + public List> getPropertyModels() { + return propertyModels; + } + + /** + * Returns the {@link PropertyModel} mapped as the id property for this ClassModel + * + * @return the PropertyModel for the id + */ + public PropertyModel getIdPropertyModel() { + return idPropertyModelHolder != null ? idPropertyModelHolder.getPropertyModel() : null; + } + + IdPropertyModelHolder getIdPropertyModelHolder() { + return idPropertyModelHolder; + } + + /** + * Returns the name of the class represented by this ClassModel + * + * @return the name + */ + public String getName() { + return name; + } + + @Override + public String toString() { + return "ClassModel{" + + "type=" + type + + "}"; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ClassModel that = (ClassModel) o; + + if (discriminatorEnabled != that.discriminatorEnabled) { + return false; + } + if (!getType().equals(that.getType())) { + return false; + } + if (!getInstanceCreatorFactory().equals(that.getInstanceCreatorFactory())) { + return false; + } + if (getDiscriminatorKey() != null ? !getDiscriminatorKey().equals(that.getDiscriminatorKey()) + : that.getDiscriminatorKey() != null) { + return false; + } + if (getDiscriminator() != null ? !getDiscriminator().equals(that.getDiscriminator()) : that.getDiscriminator() != null) { + return false; + } + if (idPropertyModelHolder != null ? !idPropertyModelHolder.equals(that.idPropertyModelHolder) + : that.idPropertyModelHolder != null) { + return false; + } + if (!getPropertyModels().equals(that.getPropertyModels())) { + return false; + } + if (!getPropertyNameToTypeParameterMap().equals(that.getPropertyNameToTypeParameterMap())) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getType().hashCode(); + result = 31 * result + getInstanceCreatorFactory().hashCode(); + result = 31 * result + (discriminatorEnabled ? 1 : 0); + result = 31 * result + (getDiscriminatorKey() != null ? getDiscriminatorKey().hashCode() : 0); + result = 31 * result + (getDiscriminator() != null ? getDiscriminator().hashCode() : 0); + result = 31 * result + (getIdPropertyModelHolder() != null ? getIdPropertyModelHolder().hashCode() : 0); + result = 31 * result + getPropertyModels().hashCode(); + result = 31 * result + getPropertyNameToTypeParameterMap().hashCode(); + return result; + } + + InstanceCreatorFactory getInstanceCreatorFactory() { + return instanceCreatorFactory; + } + + Map getPropertyNameToTypeParameterMap() { + return propertyNameToTypeParameterMap; + } + +} diff --git a/bson/src/main/org/bson/codecs/pojo/ClassModelBuilder.java b/bson/src/main/org/bson/codecs/pojo/ClassModelBuilder.java new file mode 100644 index 00000000000..6783ef815df --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/ClassModelBuilder.java @@ -0,0 +1,350 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; +import static org.bson.assertions.Assertions.notNull; +import static org.bson.codecs.pojo.Conventions.DEFAULT_CONVENTIONS; +import static org.bson.codecs.pojo.PojoBuilderHelper.configureClassModelBuilder; +import static org.bson.codecs.pojo.PojoBuilderHelper.stateNotNull; + +/** + * A builder for programmatically creating {@code ClassModels}. + * + * @param The type of the class the ClassModel represents + * @since 3.5 + * @see ClassModel + */ +public class ClassModelBuilder { + static final String ID_PROPERTY_NAME = "_id"; + private final List> propertyModelBuilders = new ArrayList>(); + private IdGenerator idGenerator; + private InstanceCreatorFactory instanceCreatorFactory; + private Class type; + private Map propertyNameToTypeParameterMap = emptyMap(); + private List conventions = DEFAULT_CONVENTIONS; + private List annotations = emptyList(); + private boolean discriminatorEnabled; + private String discriminator; + private String discriminatorKey; + private String idPropertyName; + + ClassModelBuilder(final Class type) { + configureClassModelBuilder(this, notNull("type", type)); + } + + /** + * Sets the IdGenerator for the ClassModel + * + * @param idGenerator the IdGenerator + * @return this + * @since 3.10 + */ + public ClassModelBuilder idGenerator(final IdGenerator idGenerator) { + this.idGenerator = idGenerator; + return this; + } + + /** + * @return the IdGenerator for the ClassModel, or null if not set + * @since 3.10 + */ + public IdGenerator getIdGenerator() { + return idGenerator; + } + + /** + * Sets the InstanceCreatorFactory for the ClassModel + * + * @param instanceCreatorFactory the InstanceCreatorFactory + * @return this + */ + public ClassModelBuilder instanceCreatorFactory(final InstanceCreatorFactory instanceCreatorFactory) { + this.instanceCreatorFactory = notNull("instanceCreatorFactory", instanceCreatorFactory); + return this; + } + + /** + * @return the InstanceCreatorFactory for the ClassModel + */ + public InstanceCreatorFactory getInstanceCreatorFactory() { + return instanceCreatorFactory; + } + + /** + * Sets the type of the model + * + * @param type the type of the class + * @return the builder to configure the class being modeled + */ + public ClassModelBuilder type(final Class type) { + this.type = notNull("type", type); + return this; + } + + /** + * @return the type if set or null + */ + public Class getType() { + return type; + } + + /** + * Sets the conventions to apply to the model + * + * @param conventions a list of conventions + * @return this + */ + public ClassModelBuilder conventions(final List conventions) { + this.conventions = notNull("conventions", conventions); + return this; + } + + /** + * @return the conventions o apply to the model + */ + public List getConventions() { + return conventions; + } + + /** + * Sets the annotations for the model + * + * @param annotations a list of annotations + * @return this + */ + public ClassModelBuilder annotations(final List annotations) { + this.annotations = notNull("annotations", annotations); + return this; + } + + /** + * @return the annotations on the modeled type if set or null + */ + public List getAnnotations() { + return annotations; + } + + /** + * Sets the discriminator to be used when storing instances of the modeled type + * + * @param discriminator the discriminator value + * @return this + */ + public ClassModelBuilder discriminator(final String discriminator) { + this.discriminator = discriminator; + return this; + } + + /** + * @return the discriminator to be used when storing instances of the modeled type or null if not set + */ + public String getDiscriminator() { + return discriminator; + } + + /** + * Sets the discriminator key to be used when storing instances of the modeled type + * + * @param discriminatorKey the discriminator key value + * @return this + */ + public ClassModelBuilder discriminatorKey(final String discriminatorKey) { + this.discriminatorKey = discriminatorKey; + return this; + } + + /** + * @return the discriminator key to be used when storing instances of the modeled type or null if not set + */ + public String getDiscriminatorKey() { + return discriminatorKey; + } + + /** + * Enables or disables the use of a discriminator when serializing + * + * @param discriminatorEnabled true to enable the use of a discriminator + * @return this + */ + public ClassModelBuilder enableDiscriminator(final boolean discriminatorEnabled) { + this.discriminatorEnabled = discriminatorEnabled; + return this; + } + + /** + * @return true if a discriminator should be used when serializing, otherwise false + */ + public Boolean useDiscriminator() { + return discriminatorEnabled; + } + + /** + * Designates a property as the {@code _id} property for this type. If another property is currently marked as the {@code _id} + * property, that setting is cleared in favor of the named property. + * + * @param idPropertyName the property name to use for the {@code _id} property, a null value removes the set idPropertyName. + * + * @return this + */ + public ClassModelBuilder idPropertyName(final String idPropertyName) { + this.idPropertyName = idPropertyName; + return this; + } + + /** + * @return the designated {@code _id} property name for this type or null if not set + */ + public String getIdPropertyName() { + return idPropertyName; + } + + /** + * Remove a property from the builder + * + * @param propertyName the actual property name in the POJO and not the {@code documentPropertyName}. + * @return returns true if the property matched and was removed + */ + public boolean removeProperty(final String propertyName) { + return propertyModelBuilders.remove(getProperty(notNull("propertyName", propertyName))); + } + + /** + * Gets a property by the property name. + * + * @param propertyName the name of the property to find. + * @return the property or null if the property is not found + */ + public PropertyModelBuilder getProperty(final String propertyName) { + notNull("propertyName", propertyName); + for (PropertyModelBuilder propertyModelBuilder : propertyModelBuilders) { + if (propertyModelBuilder.getName().equals(propertyName)) { + return propertyModelBuilder; + } + } + return null; + } + + /** + * @return the properties on the modeled type + */ + public List> getPropertyModelBuilders() { + return Collections.unmodifiableList(propertyModelBuilders); + } + + /** + * Creates a new ClassModel instance based on the mapping data provided. + * + * @return the new instance + */ + public ClassModel build() { + List> propertyModels = new ArrayList>(); + PropertyModel idPropertyModel = null; + + stateNotNull("type", type); + for (Convention convention : conventions) { + convention.apply(this); + } + + stateNotNull("instanceCreatorFactory", instanceCreatorFactory); + if (discriminatorEnabled) { + stateNotNull("discriminatorKey", discriminatorKey); + stateNotNull("discriminator", discriminator); + } + + for (PropertyModelBuilder propertyModelBuilder : propertyModelBuilders) { + boolean isIdProperty = propertyModelBuilder.getName().equals(idPropertyName); + if (isIdProperty) { + propertyModelBuilder.readName(ID_PROPERTY_NAME).writeName(ID_PROPERTY_NAME); + } + + PropertyModel model = propertyModelBuilder.build(); + propertyModels.add(model); + if (isIdProperty) { + idPropertyModel = model; + } + } + validatePropertyModels(type.getSimpleName(), propertyModels); + return new ClassModel(type, propertyNameToTypeParameterMap, instanceCreatorFactory, discriminatorEnabled, discriminatorKey, + discriminator, IdPropertyModelHolder.create(type, idPropertyModel, idGenerator), unmodifiableList(propertyModels)); + } + + @Override + public String toString() { + return format("ClassModelBuilder{type=%s}", type); + } + + Map getPropertyNameToTypeParameterMap() { + return propertyNameToTypeParameterMap; + } + + ClassModelBuilder propertyNameToTypeParameterMap(final Map propertyNameToTypeParameterMap) { + this.propertyNameToTypeParameterMap = unmodifiableMap(new HashMap(propertyNameToTypeParameterMap)); + return this; + } + + ClassModelBuilder addProperty(final PropertyModelBuilder propertyModelBuilder) { + propertyModelBuilders.add(notNull("propertyModelBuilder", propertyModelBuilder)); + return this; + } + + private void validatePropertyModels(final String declaringClass, final List> propertyModels) { + Map propertyNameMap = new HashMap(); + Map propertyReadNameMap = new HashMap(); + Map propertyWriteNameMap = new HashMap(); + + for (PropertyModel propertyModel : propertyModels) { + if (propertyModel.hasError()) { + throw new CodecConfigurationException(propertyModel.getError()); + } + checkForDuplicates("property", propertyModel.getName(), propertyNameMap, declaringClass); + if (propertyModel.isReadable()) { + checkForDuplicates("read property", propertyModel.getReadName(), propertyReadNameMap, declaringClass); + } + if (propertyModel.isWritable()) { + checkForDuplicates("write property", propertyModel.getWriteName(), propertyWriteNameMap, declaringClass); + } + } + + if (idPropertyName != null && !propertyNameMap.containsKey(idPropertyName)) { + throw new CodecConfigurationException(format("Invalid id property, property named '%s' can not be found.", idPropertyName)); + } + } + + private void checkForDuplicates(final String propertyType, final String propertyName, final Map propertyNameMap, + final String declaringClass) { + if (propertyNameMap.containsKey(propertyName)) { + throw new CodecConfigurationException(format("Duplicate %s named '%s' found in %s.", propertyType, propertyName, + declaringClass)); + } + propertyNameMap.put(propertyName, 1); + } + +} diff --git a/bson/src/main/org/bson/codecs/pojo/CollectionPropertyCodecProvider.java b/bson/src/main/org/bson/codecs/pojo/CollectionPropertyCodecProvider.java new file mode 100644 index 00000000000..e7440cfeb68 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/CollectionPropertyCodecProvider.java @@ -0,0 +1,104 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo; + +import org.bson.BsonReader; +import org.bson.BsonType; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +import static java.lang.String.format; + +final class CollectionPropertyCodecProvider implements PropertyCodecProvider { + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public Codec get(final TypeWithTypeParameters type, final PropertyCodecRegistry registry) { + if (Collection.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 1) { + return new CollectionCodec(type.getType(), registry.get(type.getTypeParameters().get(0))); + } else { + return null; + } + } + + private static class CollectionCodec implements Codec> { + private final Class> encoderClass; + private final Codec codec; + + CollectionCodec(final Class> encoderClass, final Codec codec) { + this.encoderClass = encoderClass; + this.codec = codec; + } + + @Override + public void encode(final BsonWriter writer, final Collection collection, final EncoderContext encoderContext) { + writer.writeStartArray(); + for (final T value : collection) { + if (value == null) { + writer.writeNull(); + } else { + codec.encode(writer, value, encoderContext); + } + } + writer.writeEndArray(); + } + + @Override + public Collection decode(final BsonReader reader, final DecoderContext context) { + Collection collection = getInstance(); + reader.readStartArray(); + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + if (reader.getCurrentBsonType() == BsonType.NULL) { + collection.add(null); + reader.readNull(); + } else { + collection.add(codec.decode(reader, context)); + } + } + reader.readEndArray(); + return collection; + } + + @Override + public Class> getEncoderClass() { + return encoderClass; + } + + private Collection getInstance() { + if (encoderClass.isInterface()) { + if (encoderClass.isAssignableFrom(ArrayList.class)) { + return new ArrayList(); + } else if (encoderClass.isAssignableFrom(HashSet.class)) { + return new HashSet(); + } else { + throw new CodecConfigurationException(format("Unsupported Collection interface of %s!", encoderClass.getName())); + } + } + + try { + return encoderClass.getDeclaredConstructor().newInstance(); + } catch (final Exception e) { + throw new CodecConfigurationException(e.getMessage(), e); + } + } + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/Convention.java b/bson/src/main/org/bson/codecs/pojo/Convention.java new file mode 100644 index 00000000000..f2101822200 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/Convention.java @@ -0,0 +1,33 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +/** + * Defines a convention to be applied when mapping a class. + * + * @since 3.5 + */ +public interface Convention { + + /** + * This method applies this Convention to the given ClassModelBuilder + * + * @param classModelBuilder the ClassModelBuilder to apply the convention to + */ + void apply(ClassModelBuilder classModelBuilder); + +} diff --git a/bson/src/main/org/bson/codecs/pojo/ConventionAnnotationImpl.java b/bson/src/main/org/bson/codecs/pojo/ConventionAnnotationImpl.java new file mode 100644 index 00000000000..776b0767301 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/ConventionAnnotationImpl.java @@ -0,0 +1,233 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + + +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import static java.lang.String.format; +import static java.lang.reflect.Modifier.isPublic; +import static java.lang.reflect.Modifier.isStatic; +import static org.bson.codecs.pojo.PojoBuilderHelper.createPropertyModelBuilder; + +final class ConventionAnnotationImpl implements Convention { + + @Override + public void apply(final ClassModelBuilder classModelBuilder) { + for (final Annotation annotation : classModelBuilder.getAnnotations()) { + processClassAnnotation(classModelBuilder, annotation); + } + + for (PropertyModelBuilder propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) { + processPropertyAnnotations(classModelBuilder, propertyModelBuilder); + } + + processCreatorAnnotation(classModelBuilder); + + cleanPropertyBuilders(classModelBuilder); + } + + private void processClassAnnotation(final ClassModelBuilder classModelBuilder, final Annotation annotation) { + if (annotation instanceof BsonDiscriminator) { + BsonDiscriminator discriminator = (BsonDiscriminator) annotation; + String key = discriminator.key(); + if (!key.equals("")) { + classModelBuilder.discriminatorKey(key); + } + + String name = discriminator.value(); + if (!name.equals("")) { + classModelBuilder.discriminator(name); + } + classModelBuilder.enableDiscriminator(true); + } + } + + private void processPropertyAnnotations(final ClassModelBuilder classModelBuilder, + final PropertyModelBuilder propertyModelBuilder) { + for (Annotation annotation : propertyModelBuilder.getReadAnnotations()) { + if (annotation instanceof BsonProperty) { + BsonProperty bsonProperty = (BsonProperty) annotation; + if (!"".equals(bsonProperty.value())) { + propertyModelBuilder.readName(bsonProperty.value()); + } + propertyModelBuilder.discriminatorEnabled(bsonProperty.useDiscriminator()); + if (propertyModelBuilder.getName().equals(classModelBuilder.getIdPropertyName())) { + classModelBuilder.idPropertyName(null); + } + } else if (annotation instanceof BsonId) { + classModelBuilder.idPropertyName(propertyModelBuilder.getName()); + } else if (annotation instanceof BsonIgnore) { + propertyModelBuilder.readName(null); + } + } + + for (Annotation annotation : propertyModelBuilder.getWriteAnnotations()) { + if (annotation instanceof BsonProperty) { + BsonProperty bsonProperty = (BsonProperty) annotation; + if (!"".equals(bsonProperty.value())) { + propertyModelBuilder.writeName(bsonProperty.value()); + } + } else if (annotation instanceof BsonIgnore) { + propertyModelBuilder.writeName(null); + } + } + } + + @SuppressWarnings("unchecked") + private void processCreatorAnnotation(final ClassModelBuilder classModelBuilder) { + Class clazz = classModelBuilder.getType(); + CreatorExecutable creatorExecutable = null; + for (Constructor constructor : clazz.getDeclaredConstructors()) { + if (isPublic(constructor.getModifiers()) && !constructor.isSynthetic()) { + for (Annotation annotation : constructor.getDeclaredAnnotations()) { + if (annotation.annotationType().equals(BsonCreator.class)) { + if (creatorExecutable != null) { + throw new CodecConfigurationException("Found multiple constructors annotated with @BsonCreator"); + } + creatorExecutable = new CreatorExecutable(clazz, (Constructor) constructor); + } + } + } + } + + Class bsonCreatorClass = clazz; + boolean foundStaticBsonCreatorMethod = false; + while (bsonCreatorClass != null && !foundStaticBsonCreatorMethod) { + for (Method method : bsonCreatorClass.getDeclaredMethods()) { + if (isStatic(method.getModifiers()) && !method.isSynthetic() && !method.isBridge()) { + for (Annotation annotation : method.getDeclaredAnnotations()) { + if (annotation.annotationType().equals(BsonCreator.class)) { + if (creatorExecutable != null) { + throw new CodecConfigurationException("Found multiple constructors / methods annotated with @BsonCreator"); + } else if (!bsonCreatorClass.isAssignableFrom(method.getReturnType())) { + throw new CodecConfigurationException( + format("Invalid method annotated with @BsonCreator. Returns '%s', expected %s", + method.getReturnType(), bsonCreatorClass)); + } + creatorExecutable = new CreatorExecutable(clazz, method); + foundStaticBsonCreatorMethod = true; + } + } + } + } + + bsonCreatorClass = bsonCreatorClass.getSuperclass(); + } + + if (creatorExecutable != null) { + List properties = creatorExecutable.getProperties(); + List> parameterTypes = creatorExecutable.getParameterTypes(); + List parameterGenericTypes = creatorExecutable.getParameterGenericTypes(); + + if (properties.size() != parameterTypes.size()) { + throw creatorExecutable.getError(clazz, "All parameters in the @BsonCreator method / constructor must be annotated " + + "with a @BsonProperty."); + } + for (int i = 0; i < properties.size(); i++) { + boolean isIdProperty = creatorExecutable.getIdPropertyIndex() != null && creatorExecutable.getIdPropertyIndex().equals(i); + Class parameterType = parameterTypes.get(i); + Type genericType = parameterGenericTypes.get(i); + PropertyModelBuilder propertyModelBuilder = null; + + if (isIdProperty) { + propertyModelBuilder = classModelBuilder.getProperty(classModelBuilder.getIdPropertyName()); + } else { + BsonProperty bsonProperty = properties.get(i); + + // Find the property using write name and falls back to read name + for (PropertyModelBuilder builder : classModelBuilder.getPropertyModelBuilders()) { + if (bsonProperty.value().equals(builder.getWriteName())) { + propertyModelBuilder = builder; + break; + } else if (bsonProperty.value().equals(builder.getReadName())) { + // When there is a property that matches the read name of the parameter, save it but continue to look + // This is just in case there is another property that matches the write name. + propertyModelBuilder = builder; + } + } + + // Support legacy options, when BsonProperty matches the actual POJO property name (e.g. method name or field name). + if (propertyModelBuilder == null) { + propertyModelBuilder = classModelBuilder.getProperty(bsonProperty.value()); + } + + if (propertyModelBuilder == null) { + propertyModelBuilder = addCreatorPropertyToClassModelBuilder(classModelBuilder, bsonProperty.value(), + parameterType); + } else { + // If not using a legacy BsonProperty reference to the property set the write name to be the annotated name. + if (!bsonProperty.value().equals(propertyModelBuilder.getName())) { + propertyModelBuilder.writeName(bsonProperty.value()); + } + tryToExpandToGenericType(parameterType, propertyModelBuilder, genericType); + } + } + + if (!propertyModelBuilder.getTypeData().isAssignableFrom(parameterType)) { + throw creatorExecutable.getError(clazz, format("Invalid Property type for '%s'. Expected %s, found %s.", + propertyModelBuilder.getWriteName(), propertyModelBuilder.getTypeData().getType(), parameterType)); + } + } + classModelBuilder.instanceCreatorFactory(new InstanceCreatorFactoryImpl(creatorExecutable)); + } + } + + @SuppressWarnings("unchecked") + private static void tryToExpandToGenericType(final Class parameterType, final PropertyModelBuilder propertyModelBuilder, + final Type genericType) { + if (parameterType.isAssignableFrom(propertyModelBuilder.getTypeData().getType())) { + // The existing getter for this field returns a more specific type than what the constructor accepts + // This is typical when the getter returns a specific subtype, but the constructor accepts a more + // general one (e.g.: getter returns ImmutableList, while constructor just accepts List) + propertyModelBuilder.typeData(TypeData.newInstance(genericType, (Class) parameterType)); + } + } + + private PropertyModelBuilder addCreatorPropertyToClassModelBuilder(final ClassModelBuilder classModelBuilder, + final String name, + final Class clazz) { + PropertyModelBuilder propertyModelBuilder = createPropertyModelBuilder(new PropertyMetadata(name, + classModelBuilder.getType().getSimpleName(), TypeData.builder(clazz).build())).readName(null).writeName(name); + classModelBuilder.addProperty(propertyModelBuilder); + return propertyModelBuilder; + } + + private void cleanPropertyBuilders(final ClassModelBuilder classModelBuilder) { + List propertiesToRemove = new ArrayList(); + for (PropertyModelBuilder propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) { + if (!propertyModelBuilder.isReadable() && !propertyModelBuilder.isWritable()) { + propertiesToRemove.add(propertyModelBuilder.getName()); + } + } + for (String propertyName : propertiesToRemove) { + classModelBuilder.removeProperty(propertyName); + } + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/ConventionDefaultsImpl.java b/bson/src/main/org/bson/codecs/pojo/ConventionDefaultsImpl.java new file mode 100644 index 00000000000..c7628f84248 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/ConventionDefaultsImpl.java @@ -0,0 +1,38 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +final class ConventionDefaultsImpl implements Convention { + @Override + public void apply(final ClassModelBuilder classModelBuilder) { + if (classModelBuilder.getDiscriminatorKey() == null) { + classModelBuilder.discriminatorKey("_t"); + } + if (classModelBuilder.getDiscriminator() == null && classModelBuilder.getType() != null) { + classModelBuilder.discriminator(classModelBuilder.getType().getName()); + } + + for (final PropertyModelBuilder propertyModel : classModelBuilder.getPropertyModelBuilders()) { + if (classModelBuilder.getIdPropertyName() == null) { + String propertyName = propertyModel.getName(); + if (propertyName.equals("_id") || propertyName.equals("id")) { + classModelBuilder.idPropertyName(propertyName); + } + } + } + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/ConventionObjectIdGeneratorsImpl.java b/bson/src/main/org/bson/codecs/pojo/ConventionObjectIdGeneratorsImpl.java new file mode 100644 index 00000000000..ed783835254 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/ConventionObjectIdGeneratorsImpl.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonObjectId; +import org.bson.types.ObjectId; + +final class ConventionObjectIdGeneratorsImpl implements Convention { + @Override + public void apply(final ClassModelBuilder classModelBuilder) { + if (classModelBuilder.getIdGenerator() == null && classModelBuilder.getIdPropertyName() != null) { + PropertyModelBuilder idProperty = classModelBuilder.getProperty(classModelBuilder.getIdPropertyName()); + if (idProperty != null) { + Class idType = idProperty.getTypeData().getType(); + if (classModelBuilder.getIdGenerator() == null && idType.equals(ObjectId.class)) { + classModelBuilder.idGenerator(IdGenerators.OBJECT_ID_GENERATOR); + } else if (classModelBuilder.getIdGenerator() == null && idType.equals(BsonObjectId.class)) { + classModelBuilder.idGenerator(IdGenerators.BSON_OBJECT_ID_GENERATOR); + } + } + } + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/ConventionSetPrivateFieldImpl.java b/bson/src/main/org/bson/codecs/pojo/ConventionSetPrivateFieldImpl.java new file mode 100644 index 00000000000..0ff5721ea4a --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/ConventionSetPrivateFieldImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; + +import static java.lang.String.format; +import static java.lang.reflect.Modifier.isPrivate; + +final class ConventionSetPrivateFieldImpl implements Convention { + + @Override + public void apply(final ClassModelBuilder classModelBuilder) { + for (PropertyModelBuilder propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) { + if (!(propertyModelBuilder.getPropertyAccessor() instanceof PropertyAccessorImpl)) { + throw new CodecConfigurationException(format("The SET_PRIVATE_FIELDS_CONVENTION is not compatible with " + + "propertyModelBuilder instance that have custom implementations of org.bson.codecs.pojo.PropertyAccessor: %s", + propertyModelBuilder.getPropertyAccessor().getClass().getName())); + } + PropertyAccessorImpl defaultAccessor = (PropertyAccessorImpl) propertyModelBuilder.getPropertyAccessor(); + PropertyMetadata propertyMetaData = defaultAccessor.getPropertyMetadata(); + if (!propertyMetaData.isDeserializable() && propertyMetaData.getField() != null + && isPrivate(propertyMetaData.getField().getModifiers())) { + setPropertyAccessor(propertyModelBuilder); + } + } + } + + @SuppressWarnings("unchecked") + private void setPropertyAccessor(final PropertyModelBuilder propertyModelBuilder) { + propertyModelBuilder.propertyAccessor(new PrivatePropertyAccessor( + (PropertyAccessorImpl) propertyModelBuilder.getPropertyAccessor())); + } + + private static final class PrivatePropertyAccessor implements PropertyAccessor { + private final PropertyAccessorImpl wrapped; + + private PrivatePropertyAccessor(final PropertyAccessorImpl wrapped) { + this.wrapped = wrapped; + try { + wrapped.getPropertyMetadata().getField().setAccessible(true); + } catch (Exception e) { + throw new CodecConfigurationException(format("Unable to make private field accessible '%s' in %s", + wrapped.getPropertyMetadata().getName(), wrapped.getPropertyMetadata().getDeclaringClassName()), e); + } + } + + @Override + public T get(final S instance) { + return wrapped.get(instance); + } + + @Override + public void set(final S instance, final T value) { + try { + wrapped.getPropertyMetadata().getField().set(instance, value); + } catch (Exception e) { + throw new CodecConfigurationException(format("Unable to set value for property '%s' in %s", + wrapped.getPropertyMetadata().getName(), wrapped.getPropertyMetadata().getDeclaringClassName()), e); + } + } + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/ConventionUseGettersAsSettersImpl.java b/bson/src/main/org/bson/codecs/pojo/ConventionUseGettersAsSettersImpl.java new file mode 100644 index 00000000000..07dbec9ad15 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/ConventionUseGettersAsSettersImpl.java @@ -0,0 +1,115 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.util.Collection; +import java.util.Map; + +import static java.lang.String.format; + +final class ConventionUseGettersAsSettersImpl implements Convention { + + @Override + public void apply(final ClassModelBuilder classModelBuilder) { + for (PropertyModelBuilder propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) { + if (!(propertyModelBuilder.getPropertyAccessor() instanceof PropertyAccessorImpl)) { + throw new CodecConfigurationException(format("The USE_GETTER_AS_SETTER_CONVENTION is not compatible with " + + "propertyModelBuilder instance that have custom implementations of org.bson.codecs.pojo.PropertyAccessor: %s", + propertyModelBuilder.getPropertyAccessor().getClass().getName())); + } + PropertyAccessorImpl defaultAccessor = (PropertyAccessorImpl) propertyModelBuilder.getPropertyAccessor(); + PropertyMetadata propertyMetaData = defaultAccessor.getPropertyMetadata(); + if (!propertyMetaData.isDeserializable() && propertyMetaData.isSerializable() + && isMapOrCollection(propertyMetaData.getTypeData().getType())) { + setPropertyAccessor(propertyModelBuilder); + } + } + } + + private boolean isMapOrCollection(final Class clazz) { + return Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz); + } + + @SuppressWarnings("unchecked") + private void setPropertyAccessor(final PropertyModelBuilder propertyModelBuilder) { + propertyModelBuilder.propertyAccessor(new PrivatePropertyAccessor( + (PropertyAccessorImpl) propertyModelBuilder.getPropertyAccessor())); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static final class PrivatePropertyAccessor implements PropertyAccessor { + private final PropertyAccessorImpl wrapped; + + private PrivatePropertyAccessor(final PropertyAccessorImpl wrapped) { + this.wrapped = wrapped; + } + + @Override + public T get(final S instance) { + return wrapped.get(instance); + } + + @Override + public void set(final S instance, final T value) { + if (value instanceof Collection) { + mutateCollection(instance, (Collection) value); + } else if (value instanceof Map) { + mutateMap(instance, (Map) value); + } else { + throwCodecConfigurationException(format("Unexpected type: '%s'", value.getClass()), null); + } + } + + private void mutateCollection(final S instance, final Collection value) { + T originalCollection = get(instance); + Collection collection = ((Collection) originalCollection); + if (collection == null) { + throwCodecConfigurationException("The getter returned null.", null); + } else if (!collection.isEmpty()) { + throwCodecConfigurationException("The getter returned a non empty collection.", null); + } else { + try { + collection.addAll(value); + } catch (Exception e) { + throwCodecConfigurationException("collection#addAll failed.", e); + } + } + } + + private void mutateMap(final S instance, final Map value) { + T originalMap = get(instance); + Map map = ((Map) originalMap); + if (map == null) { + throwCodecConfigurationException("The getter returned null.", null); + } else if (!map.isEmpty()) { + throwCodecConfigurationException("The getter returned a non empty map.", null); + } else { + try { + map.putAll(value); + } catch (Exception e) { + throwCodecConfigurationException("map#putAll failed.", e); + } + } + } + private void throwCodecConfigurationException(final String reason, final Exception cause) { + throw new CodecConfigurationException(format("Cannot use getter in '%s' to set '%s'. %s", + wrapped.getPropertyMetadata().getDeclaringClassName(), wrapped.getPropertyMetadata().getName(), reason), cause); + } + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/Conventions.java b/bson/src/main/org/bson/codecs/pojo/Conventions.java new file mode 100644 index 00000000000..0f54c13815d --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/Conventions.java @@ -0,0 +1,94 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import java.util.Collections; +import java.util.List; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; + +/** + * The default Conventions + * + * @since 3.5 + * @see Convention + */ +public final class Conventions { + + /** + * The default class and property conventions + * + *
      + *
    • Sets the discriminator key if not set to {@code _t} and the discriminator value if not set to the + * ClassModels simple type name.
    • + *
    • Configures the PropertyModels. If the {@code idProperty} isn't set and there is a + * property named {@code getId()}, {@code id} or {@code _id} it will be marked as the idProperty.
    • + *
    + */ + public static final Convention CLASS_AND_PROPERTY_CONVENTION = new ConventionDefaultsImpl(); + + /** + * The annotation convention. + * + *

    Applies all the conventions related to the default {@link org.bson.codecs.pojo.annotations}.

    + */ + public static final Convention ANNOTATION_CONVENTION = new ConventionAnnotationImpl(); + + /** + * A convention that enables private fields to be set using reflection. + * + *

    This convention mimics how some other JSON libraries directly set a private field when there is no setter.

    + *

    Note: This convention is not part of the {@code DEFAULT_CONVENTIONS} list and must explicitly be set.

    + * + * @since 3.6 + */ + public static final Convention SET_PRIVATE_FIELDS_CONVENTION = new ConventionSetPrivateFieldImpl(); + + /** + * A convention that uses getter methods as setters for collections and maps if there is no setter. + * + *

    This convention mimics how JAXB mutate collections and maps.

    + *

    Note: This convention is not part of the {@code DEFAULT_CONVENTIONS} list and must explicitly be set.

    + * + * @since 3.6 + */ + public static final Convention USE_GETTERS_FOR_SETTERS = new ConventionUseGettersAsSettersImpl(); + + + /** + * A convention that sets the IdGenerator if the id property is either a {@link org.bson.types.ObjectId} or + * {@link org.bson.BsonObjectId}. + * + * @since 3.10 + */ + public static final Convention OBJECT_ID_GENERATORS = new ConventionObjectIdGeneratorsImpl(); + + /** + * The default conventions list + */ + public static final List DEFAULT_CONVENTIONS = + unmodifiableList(asList(CLASS_AND_PROPERTY_CONVENTION, ANNOTATION_CONVENTION, OBJECT_ID_GENERATORS)); + + /** + * An empty conventions list + */ + public static final List NO_CONVENTIONS = Collections.emptyList(); + + private Conventions() { + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/CreatorExecutable.java b/bson/src/main/org/bson/codecs/pojo/CreatorExecutable.java new file mode 100644 index 00000000000..c212dd2e000 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/CreatorExecutable.java @@ -0,0 +1,149 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Arrays.asList; + +final class CreatorExecutable { + private final Class clazz; + private final Constructor constructor; + private final Method method; + private final List properties = new ArrayList(); + private final Integer idPropertyIndex; + private final List> parameterTypes = new ArrayList>(); + private final List parameterGenericTypes = new ArrayList(); + + CreatorExecutable(final Class clazz, final Constructor constructor) { + this(clazz, constructor, null); + } + + CreatorExecutable(final Class clazz, final Method method) { + this(clazz, null, method); + } + + private CreatorExecutable(final Class clazz, final Constructor constructor, final Method method) { + this.clazz = clazz; + this.constructor = constructor; + this.method = method; + Integer idPropertyIndex = null; + + if (constructor != null || method != null) { + Class[] paramTypes = constructor != null ? constructor.getParameterTypes() : method.getParameterTypes(); + Type[] genericParamTypes = constructor != null ? constructor.getGenericParameterTypes() : method.getGenericParameterTypes(); + parameterTypes.addAll(asList(paramTypes)); + parameterGenericTypes.addAll(asList(genericParamTypes)); + Annotation[][] parameterAnnotations = constructor != null ? constructor.getParameterAnnotations() + : method.getParameterAnnotations(); + + for (int i = 0; i < parameterAnnotations.length; ++i) { + Annotation[] parameterAnnotation = parameterAnnotations[i]; + + for (Annotation annotation : parameterAnnotation) { + if (annotation.annotationType().equals(BsonProperty.class)) { + properties.add((BsonProperty) annotation); + break; + } + + if (annotation.annotationType().equals(BsonId.class)) { + properties.add(null); + idPropertyIndex = i; + break; + } + } + } + } + + this.idPropertyIndex = idPropertyIndex; + } + + Class getType() { + return clazz; + } + + List getProperties() { + return properties; + } + + Integer getIdPropertyIndex() { + return idPropertyIndex; + } + + List> getParameterTypes() { + return parameterTypes; + } + + List getParameterGenericTypes() { + return parameterGenericTypes; + } + + @SuppressWarnings("unchecked") + T getInstance() { + checkHasAnExecutable(); + try { + if (constructor != null) { + return constructor.newInstance(); + } else { + return (T) method.invoke(clazz); + } + } catch (Exception e) { + throw new CodecConfigurationException(e.getMessage(), e); + } + } + + @SuppressWarnings("unchecked") + T getInstance(final Object[] params) { + checkHasAnExecutable(); + try { + if (constructor != null) { + return constructor.newInstance(params); + } else { + return (T) method.invoke(clazz, params); + } + } catch (Exception e) { + throw new CodecConfigurationException(e.getMessage(), e); + } + } + + + CodecConfigurationException getError(final Class clazz, final String msg) { + return getError(clazz, constructor != null, msg); + } + + private void checkHasAnExecutable() { + if (constructor == null && method == null) { + throw new CodecConfigurationException(format("Cannot find a public constructor for '%s'.", clazz.getSimpleName())); + } + } + + private static CodecConfigurationException getError(final Class clazz, final boolean isConstructor, final String msg) { + return new CodecConfigurationException(format("Invalid @BsonCreator %s in %s. %s", isConstructor ? "constructor" : "method", + clazz.getSimpleName(), msg)); + } + +} diff --git a/bson/src/main/org/bson/codecs/pojo/DiscriminatorLookup.java b/bson/src/main/org/bson/codecs/pojo/DiscriminatorLookup.java new file mode 100644 index 00000000000..18fb59d8fef --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/DiscriminatorLookup.java @@ -0,0 +1,84 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static java.lang.String.format; + +final class DiscriminatorLookup { + private final Map> discriminatorClassMap = new ConcurrentHashMap>(); + private final Set packages; + + DiscriminatorLookup(final Map, ClassModel> classModels, final Set packages) { + for (ClassModel classModel : classModels.values()) { + if (classModel.getDiscriminator() != null) { + discriminatorClassMap.put(classModel.getDiscriminator(), classModel.getType()); + } + } + this.packages = packages; + } + + public Class lookup(final String discriminator) { + if (discriminatorClassMap.containsKey(discriminator)) { + return discriminatorClassMap.get(discriminator); + } + + Class clazz = getClassForName(discriminator); + if (clazz == null) { + clazz = searchPackages(discriminator); + } + + if (clazz == null) { + throw new CodecConfigurationException(format("A class could not be found for the discriminator: '%s'.", discriminator)); + } else { + discriminatorClassMap.put(discriminator, clazz); + } + return clazz; + } + + void addClassModel(final ClassModel classModel) { + if (classModel.getDiscriminator() != null) { + discriminatorClassMap.put(classModel.getDiscriminator(), classModel.getType()); + } + } + + private Class getClassForName(final String discriminator) { + Class clazz = null; + try { + clazz = Class.forName(discriminator); + } catch (final ClassNotFoundException e) { + // Ignore + } + return clazz; + } + + private Class searchPackages(final String discriminator) { + Class clazz = null; + for (String packageName : packages) { + clazz = getClassForName(packageName + "." + discriminator); + if (clazz != null) { + return clazz; + } + } + return clazz; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/EnumPropertyCodecProvider.java b/bson/src/main/org/bson/codecs/pojo/EnumPropertyCodecProvider.java new file mode 100644 index 00000000000..fba5f2c99ff --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/EnumPropertyCodecProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.configuration.CodecRegistry; + + +final class EnumPropertyCodecProvider implements PropertyCodecProvider { + private final CodecRegistry codecRegistry; + + EnumPropertyCodecProvider(final CodecRegistry codecRegistry) { + this.codecRegistry = codecRegistry; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public Codec get(final TypeWithTypeParameters type, final PropertyCodecRegistry propertyCodecRegistry) { + Class clazz = type.getType(); + if (Enum.class.isAssignableFrom(clazz)) { + try { + return codecRegistry.get(clazz); + } catch (CodecConfigurationException e) { + return (Codec) new EnumCodec(clazz); + } + } + return null; + } + + private static class EnumCodec> implements Codec { + private final Class clazz; + + EnumCodec(final Class clazz) { + this.clazz = clazz; + } + + @Override + public void encode(final BsonWriter writer, final T value, final EncoderContext encoderContext) { + writer.writeString(value.name()); + } + + @Override + public Class getEncoderClass() { + return clazz; + } + + @Override + public T decode(final BsonReader reader, final DecoderContext decoderContext) { + return Enum.valueOf(clazz, reader.readString()); + } + } + +} diff --git a/bson/src/main/org/bson/codecs/pojo/FallbackPropertyCodecProvider.java b/bson/src/main/org/bson/codecs/pojo/FallbackPropertyCodecProvider.java new file mode 100644 index 00000000000..8c5fd4e9cf3 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/FallbackPropertyCodecProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecRegistry; + +final class FallbackPropertyCodecProvider implements PropertyCodecProvider { + private final CodecRegistry codecRegistry; + private final PojoCodec pojoCodec; + + FallbackPropertyCodecProvider(final PojoCodec pojoCodec, final CodecRegistry codecRegistry) { + this.pojoCodec = pojoCodec; + this.codecRegistry = codecRegistry; + } + + @SuppressWarnings("unchecked") + @Override + public Codec get(final TypeWithTypeParameters type, final PropertyCodecRegistry propertyCodecRegistry) { + Class clazz = type.getType(); + if (clazz == pojoCodec.getEncoderClass()) { + return (Codec) pojoCodec; + } + return codecRegistry.get(type.getType()); + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/IdGenerator.java b/bson/src/main/org/bson/codecs/pojo/IdGenerator.java new file mode 100644 index 00000000000..e794c8841ef --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/IdGenerator.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +/** + * Classes that implement this interface define a way to create Ids for Pojo's. + * + * @param the type of the id value. + * @since 3.10 + */ +public interface IdGenerator { + /** + * Generates an id for a Pojo. + * + * @return the generated id value + */ + T generate(); + + /** + * @return the type of the generated id. + */ + Class getType(); +} diff --git a/bson/src/main/org/bson/codecs/pojo/IdGenerators.java b/bson/src/main/org/bson/codecs/pojo/IdGenerators.java new file mode 100644 index 00000000000..20f9e3f7dd6 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/IdGenerators.java @@ -0,0 +1,64 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonObjectId; +import org.bson.types.ObjectId; + +/** + * The default IdGenerators + * + * @see IdGenerator + * @since 3.10 + */ +public final class IdGenerators { + + /** + * A IdGenerator for {@code ObjectId} + */ + public static final IdGenerator OBJECT_ID_GENERATOR = new IdGenerator() { + + @Override + public ObjectId generate() { + return new ObjectId(); + } + + @Override + public Class getType() { + return ObjectId.class; + } + }; + + /** + * A IdGenerator for {@code BsonObjectId} + */ + public static final IdGenerator BSON_OBJECT_ID_GENERATOR = new IdGenerator() { + + @Override + public BsonObjectId generate() { + return new BsonObjectId(); + } + + @Override + public Class getType() { + return BsonObjectId.class; + } + }; + + private IdGenerators(){ + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/IdPropertyModelHolder.java b/bson/src/main/org/bson/codecs/pojo/IdPropertyModelHolder.java new file mode 100644 index 00000000000..fe11097679c --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/IdPropertyModelHolder.java @@ -0,0 +1,79 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; + +import static java.lang.String.format; + +final class IdPropertyModelHolder { + private final PropertyModel propertyModel; + private final IdGenerator idGenerator; + + static IdPropertyModelHolder create(final ClassModel classModel, final PropertyModel idPropertyModel) { + return create(classModel.getType(), idPropertyModel, classModel.getIdPropertyModelHolder().getIdGenerator()); + } + + @SuppressWarnings("unchecked") + static IdPropertyModelHolder create(final Class type, final PropertyModel idProperty, + final IdGenerator idGenerator) { + if (idProperty == null && idGenerator != null) { + throw new CodecConfigurationException(format("Invalid IdGenerator. There is no IdProperty set for: %s", type)); + } else if (idGenerator != null && !idProperty.getTypeData().getType().isAssignableFrom(idGenerator.getType())) { + throw new CodecConfigurationException(format("Invalid IdGenerator. Mismatching types, the IdProperty type is: %s but" + + " the IdGenerator type is: %s", idProperty.getTypeData().getType(), idGenerator.getType())); + } + return new IdPropertyModelHolder(idProperty, (IdGenerator) idGenerator); + } + + private IdPropertyModelHolder(final PropertyModel propertyModel, final IdGenerator idGenerator) { + this.propertyModel = propertyModel; + this.idGenerator = idGenerator; + } + + PropertyModel getPropertyModel() { + return propertyModel; + } + + IdGenerator getIdGenerator() { + return idGenerator; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + IdPropertyModelHolder that = (IdPropertyModelHolder) o; + + if (propertyModel != null ? !propertyModel.equals(that.propertyModel) : that.propertyModel != null) { + return false; + } + return idGenerator != null ? idGenerator.equals(that.idGenerator) : that.idGenerator == null; + } + + @Override + public int hashCode() { + int result = propertyModel != null ? propertyModel.hashCode() : 0; + result = 31 * result + (idGenerator != null ? idGenerator.hashCode() : 0); + return result; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/InstanceCreator.java b/bson/src/main/org/bson/codecs/pojo/InstanceCreator.java new file mode 100644 index 00000000000..8fca88f9dba --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/InstanceCreator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +/** + * Provides access for setting data and the creation of a class instances. + * + * @param the type of the class + * @since 3.5 + */ +public interface InstanceCreator { + + /** + * Sets a value for the given PropertyModel + * + * @param value the new value for the property + * @param propertyModel the PropertyModel representing the property to set the value for. + * @param the PropertyModel's type + */ + void set(S value, PropertyModel propertyModel); + + /** + * Returns the new instance of the class. + *

    Note: This will be called after all the values have been set.

    + * + * @return the new class instance. + */ + T getInstance(); + +} diff --git a/bson/src/main/org/bson/codecs/pojo/InstanceCreatorFactory.java b/bson/src/main/org/bson/codecs/pojo/InstanceCreatorFactory.java new file mode 100644 index 00000000000..14dc42825c6 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/InstanceCreatorFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +/** + * The factory for creating {@link InstanceCreator} instances + * + * @param the type of the ClassAccessor + * @since 3.5 + */ +public interface InstanceCreatorFactory { + + /** + * @return a new ClassAccessor instance + */ + InstanceCreator create(); +} diff --git a/bson/src/main/org/bson/codecs/pojo/InstanceCreatorFactoryImpl.java b/bson/src/main/org/bson/codecs/pojo/InstanceCreatorFactoryImpl.java new file mode 100644 index 00000000000..294e7f85142 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/InstanceCreatorFactoryImpl.java @@ -0,0 +1,30 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +final class InstanceCreatorFactoryImpl implements InstanceCreatorFactory { + private final CreatorExecutable creatorExecutable; + + InstanceCreatorFactoryImpl(final CreatorExecutable creatorExecutable) { + this.creatorExecutable = creatorExecutable; + } + + @Override + public InstanceCreator create() { + return new InstanceCreatorImpl(creatorExecutable); + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/InstanceCreatorImpl.java b/bson/src/main/org/bson/codecs/pojo/InstanceCreatorImpl.java new file mode 100644 index 00000000000..04fc86bd1f6 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/InstanceCreatorImpl.java @@ -0,0 +1,118 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.util.HashMap; +import java.util.Map; + +import static java.lang.String.format; + +final class InstanceCreatorImpl implements InstanceCreator { + private final CreatorExecutable creatorExecutable; + private final Map, Object> cachedValues; + private final Map properties; + private final Object[] params; + + private T newInstance; + + InstanceCreatorImpl(final CreatorExecutable creatorExecutable) { + this.creatorExecutable = creatorExecutable; + if (creatorExecutable.getProperties().isEmpty()) { + this.cachedValues = null; + this.properties = null; + this.params = null; + this.newInstance = creatorExecutable.getInstance(); + } else { + this.cachedValues = new HashMap, Object>(); + this.properties = new HashMap(); + + for (int i = 0; i < creatorExecutable.getProperties().size(); i++) { + if (creatorExecutable.getIdPropertyIndex() != null && creatorExecutable.getIdPropertyIndex() == i) { + this.properties.put(ClassModelBuilder.ID_PROPERTY_NAME, creatorExecutable.getIdPropertyIndex()); + } else { + this.properties.put(creatorExecutable.getProperties().get(i).value(), i); + } + } + + this.params = new Object[properties.size()]; + } + } + + @Override + public void set(final S value, final PropertyModel propertyModel) { + if (newInstance != null) { + propertyModel.getPropertyAccessor().set(newInstance, value); + } else { + if (!properties.isEmpty()) { + String propertyName = propertyModel.getWriteName(); + + if (!properties.containsKey(propertyName)) { + // Support legacy BsonProperty settings where the property name was used instead of the write name. + propertyName = propertyModel.getName(); + } + + Integer index = properties.get(propertyName); + if (index != null) { + params[index] = value; + } + properties.remove(propertyName); + } + + if (properties.isEmpty()) { + constructInstanceAndProcessCachedValues(); + } else { + cachedValues.put(propertyModel, value); + } + } + } + + @Override + public T getInstance() { + if (newInstance == null) { + try { + for (Map.Entry entry : properties.entrySet()) { + params[entry.getValue()] = null; + } + constructInstanceAndProcessCachedValues(); + } catch (CodecConfigurationException e) { + throw new CodecConfigurationException(format("Could not construct new instance of: %s. " + + "Missing the following properties: %s", + creatorExecutable.getType().getSimpleName(), properties.keySet()), e); + } + } + return newInstance; + } + + private void constructInstanceAndProcessCachedValues() { + try { + newInstance = creatorExecutable.getInstance(params); + } catch (Exception e) { + throw new CodecConfigurationException(e.getMessage(), e); + } + + for (Map.Entry, Object> entry : cachedValues.entrySet()) { + setPropertyValue(entry.getKey(), entry.getValue()); + } + } + + @SuppressWarnings("unchecked") + private void setPropertyValue(final PropertyModel propertyModel, final Object value) { + set((S) value, propertyModel); + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/LazyMissingCodec.java b/bson/src/main/org/bson/codecs/pojo/LazyMissingCodec.java new file mode 100644 index 00000000000..b5e24292be1 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/LazyMissingCodec.java @@ -0,0 +1,50 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; + + +class LazyMissingCodec implements Codec { + private final Class clazz; + private final CodecConfigurationException exception; + + LazyMissingCodec(final Class clazz, final CodecConfigurationException exception) { + this.clazz = clazz; + this.exception = exception; + } + + @Override + public S decode(final BsonReader reader, final DecoderContext decoderContext) { + throw exception; + } + + @Override + public void encode(final BsonWriter writer, final S value, final EncoderContext encoderContext) { + throw exception; + } + + @Override + public Class getEncoderClass() { + return clazz; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/LazyPojoCodec.java b/bson/src/main/org/bson/codecs/pojo/LazyPojoCodec.java new file mode 100644 index 00000000000..ec3d9469b39 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/LazyPojoCodec.java @@ -0,0 +1,71 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.concurrent.ConcurrentMap; + +class LazyPojoCodec extends PojoCodec { + private final ClassModel classModel; + private final CodecRegistry registry; + private final PropertyCodecRegistry propertyCodecRegistry; + private final DiscriminatorLookup discriminatorLookup; + private final ConcurrentMap, Codec> codecCache; + private volatile PojoCodecImpl pojoCodec; + + LazyPojoCodec(final ClassModel classModel, final CodecRegistry registry, final PropertyCodecRegistry propertyCodecRegistry, + final DiscriminatorLookup discriminatorLookup, final ConcurrentMap, Codec> codecCache) { + this.classModel = classModel; + this.registry = registry; + this.propertyCodecRegistry = propertyCodecRegistry; + this.discriminatorLookup = discriminatorLookup; + this.codecCache = codecCache; + } + + @Override + public void encode(final BsonWriter writer, final T value, final EncoderContext encoderContext) { + getPojoCodec().encode(writer, value, encoderContext); + } + + @Override + public Class getEncoderClass() { + return classModel.getType(); + } + + @Override + public T decode(final BsonReader reader, final DecoderContext decoderContext) { + return getPojoCodec().decode(reader, decoderContext); + } + + private Codec getPojoCodec() { + if (pojoCodec == null) { + pojoCodec = new PojoCodecImpl(classModel, registry, propertyCodecRegistry, discriminatorLookup, codecCache, true); + } + return pojoCodec; + } + + @Override + ClassModel getClassModel() { + return classModel; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/MapPropertyCodecProvider.java b/bson/src/main/org/bson/codecs/pojo/MapPropertyCodecProvider.java new file mode 100644 index 00000000000..50e665b3974 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/MapPropertyCodecProvider.java @@ -0,0 +1,115 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo; + +import org.bson.BsonReader; +import org.bson.BsonType; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import static java.lang.String.format; + +final class MapPropertyCodecProvider implements PropertyCodecProvider { + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public Codec get(final TypeWithTypeParameters type, final PropertyCodecRegistry registry) { + if (Map.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 2) { + Class keyType = type.getTypeParameters().get(0).getType(); + if (!keyType.equals(String.class)) { + throw new CodecConfigurationException(format("Invalid Map type. Maps MUST have string keys, found %s instead.", keyType)); + } + + try { + return new MapCodec(type.getType(), registry.get(type.getTypeParameters().get(1))); + } catch (CodecConfigurationException e) { + if (type.getTypeParameters().get(1).getType() == Object.class) { + try { + return (Codec) registry.get(TypeData.builder(Map.class).build()); + } catch (CodecConfigurationException e1) { + // Ignore and return original exception + } + } + throw e; + } + } else { + return null; + } + } + + private static class MapCodec implements Codec> { + private final Class> encoderClass; + private final Codec codec; + + MapCodec(final Class> encoderClass, final Codec codec) { + this.encoderClass = encoderClass; + this.codec = codec; + } + + @Override + public void encode(final BsonWriter writer, final Map map, final EncoderContext encoderContext) { + writer.writeStartDocument(); + for (final Entry entry : map.entrySet()) { + writer.writeName(entry.getKey()); + if (entry.getValue() == null) { + writer.writeNull(); + } else { + codec.encode(writer, entry.getValue(), encoderContext); + } + } + writer.writeEndDocument(); + } + + @Override + public Map decode(final BsonReader reader, final DecoderContext context) { + reader.readStartDocument(); + Map map = getInstance(); + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + if (reader.getCurrentBsonType() == BsonType.NULL) { + map.put(reader.readName(), null); + reader.readNull(); + } else { + map.put(reader.readName(), codec.decode(reader, context)); + } + } + reader.readEndDocument(); + return map; + } + + @Override + public Class> getEncoderClass() { + return encoderClass; + } + + private Map getInstance() { + if (encoderClass.isInterface()) { + return new HashMap(); + } + try { + return encoderClass.getDeclaredConstructor().newInstance(); + } catch (final Exception e) { + throw new CodecConfigurationException(e.getMessage(), e); + } + } + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PojoBuilderHelper.java b/bson/src/main/org/bson/codecs/pojo/PojoBuilderHelper.java new file mode 100644 index 00000000000..f86fb5e3c1c --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PojoBuilderHelper.java @@ -0,0 +1,287 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import static java.lang.String.format; +import static java.lang.reflect.Modifier.isProtected; +import static java.lang.reflect.Modifier.isPublic; +import static java.util.Arrays.asList; +import static java.util.Collections.reverse; +import static org.bson.assertions.Assertions.notNull; +import static org.bson.codecs.pojo.PropertyReflectionUtils.getPropertyMethods; +import static org.bson.codecs.pojo.PropertyReflectionUtils.isGetter; +import static org.bson.codecs.pojo.PropertyReflectionUtils.toPropertyName; + +final class PojoBuilderHelper { + + @SuppressWarnings("unchecked") + static void configureClassModelBuilder(final ClassModelBuilder classModelBuilder, final Class clazz) { + classModelBuilder.type(notNull("clazz", clazz)); + + ArrayList annotations = new ArrayList(); + Set propertyNames = new TreeSet(); + Map propertyTypeParameterMap = new HashMap(); + Class currentClass = clazz; + String declaringClassName = clazz.getSimpleName(); + TypeData parentClassTypeData = null; + + Map> propertyNameMap = new HashMap>(); + while (!currentClass.isEnum() && currentClass.getSuperclass() != null) { + annotations.addAll(asList(currentClass.getDeclaredAnnotations())); + List genericTypeNames = new ArrayList(); + for (TypeVariable> classTypeVariable : currentClass.getTypeParameters()) { + genericTypeNames.add(classTypeVariable.getName()); + } + + PropertyReflectionUtils.PropertyMethods propertyMethods = getPropertyMethods(currentClass); + + // Note that we're processing setters before getters. It's typical for setters to have more general types + // than getters (e.g.: getter returning ImmutableList, but setter accepting Collection), so by evaluating + // setters first, we'll initialize the PropertyMetadata with the more general type + for (Method method : propertyMethods.getSetterMethods()) { + String propertyName = toPropertyName(method); + propertyNames.add(propertyName); + PropertyMetadata propertyMetadata = getOrCreateMethodPropertyMetadata(propertyName, declaringClassName, propertyNameMap, + TypeData.newInstance(method), propertyTypeParameterMap, parentClassTypeData, genericTypeNames, + getGenericType(method)); + + if (propertyMetadata.getSetter() == null) { + propertyMetadata.setSetter(method); + for (Annotation annotation : method.getDeclaredAnnotations()) { + propertyMetadata.addWriteAnnotation(annotation); + } + } + } + + for (Method method : propertyMethods.getGetterMethods()) { + String propertyName = toPropertyName(method); + propertyNames.add(propertyName); + // If the getter is overridden in a subclass, we only want to process that property, and ignore + // potentially less specific methods from super classes + PropertyMetadata propertyMetadata = propertyNameMap.get(propertyName); + if (propertyMetadata != null && propertyMetadata.getGetter() != null) { + continue; + } + propertyMetadata = getOrCreateMethodPropertyMetadata(propertyName, declaringClassName, propertyNameMap, + TypeData.newInstance(method), propertyTypeParameterMap, parentClassTypeData, genericTypeNames, + getGenericType(method)); + if (propertyMetadata.getGetter() == null) { + propertyMetadata.setGetter(method); + for (Annotation annotation : method.getDeclaredAnnotations()) { + propertyMetadata.addReadAnnotation(annotation); + } + } + } + + for (Field field : currentClass.getDeclaredFields()) { + propertyNames.add(field.getName()); + // Note if properties are present and types don't match, the underlying field is treated as an implementation detail. + PropertyMetadata propertyMetadata = getOrCreateFieldPropertyMetadata(field.getName(), declaringClassName, + propertyNameMap, TypeData.newInstance(field), propertyTypeParameterMap, parentClassTypeData, genericTypeNames, + field.getGenericType()); + if (propertyMetadata != null && propertyMetadata.getField() == null) { + propertyMetadata.field(field); + for (Annotation annotation : field.getDeclaredAnnotations()) { + propertyMetadata.addReadAnnotation(annotation); + propertyMetadata.addWriteAnnotation(annotation); + } + } + } + + parentClassTypeData = TypeData.newInstance(currentClass.getGenericSuperclass(), currentClass); + currentClass = currentClass.getSuperclass(); + } + + if (currentClass.isInterface()) { + annotations.addAll(asList(currentClass.getDeclaredAnnotations())); + } + + for (String propertyName : propertyNames) { + PropertyMetadata propertyMetadata = propertyNameMap.get(propertyName); + if (propertyMetadata.isSerializable() || propertyMetadata.isDeserializable()) { + classModelBuilder.addProperty(createPropertyModelBuilder(propertyMetadata)); + } + } + + reverse(annotations); + classModelBuilder.annotations(annotations); + classModelBuilder.propertyNameToTypeParameterMap(propertyTypeParameterMap); + + Constructor noArgsConstructor = null; + for (Constructor constructor : clazz.getDeclaredConstructors()) { + if (constructor.getParameterTypes().length == 0 + && (isPublic(constructor.getModifiers()) || isProtected(constructor.getModifiers()))) { + noArgsConstructor = (Constructor) constructor; + noArgsConstructor.setAccessible(true); + } + } + + classModelBuilder.instanceCreatorFactory(new InstanceCreatorFactoryImpl(new CreatorExecutable(clazz, noArgsConstructor))); + } + + private static PropertyMetadata getOrCreateMethodPropertyMetadata(final String propertyName, + final String declaringClassName, + final Map> propertyNameMap, + final TypeData typeData, + final Map propertyTypeParameterMap, + final TypeData parentClassTypeData, + final List genericTypeNames, + final Type genericType) { + PropertyMetadata propertyMetadata = getOrCreatePropertyMetadata(propertyName, declaringClassName, propertyNameMap, typeData); + if (!isAssignableClass(propertyMetadata.getTypeData().getType(), typeData.getType())) { + propertyMetadata.setError(format("Property '%s' in %s, has differing data types: %s and %s.", propertyName, + declaringClassName, propertyMetadata.getTypeData(), typeData)); + } + cachePropertyTypeData(propertyMetadata, propertyTypeParameterMap, parentClassTypeData, genericTypeNames, genericType); + return propertyMetadata; + } + + private static boolean isAssignableClass(final Class propertyTypeClass, final Class typeDataClass) { + return propertyTypeClass.isAssignableFrom(typeDataClass) || typeDataClass.isAssignableFrom(propertyTypeClass); + } + + private static PropertyMetadata getOrCreateFieldPropertyMetadata(final String propertyName, + final String declaringClassName, + final Map> propertyNameMap, + final TypeData typeData, + final Map propertyTypeParameterMap, + final TypeData parentClassTypeData, + final List genericTypeNames, + final Type genericType) { + PropertyMetadata propertyMetadata = getOrCreatePropertyMetadata(propertyName, declaringClassName, propertyNameMap, typeData); + if (!propertyMetadata.getTypeData().getType().isAssignableFrom(typeData.getType())) { + return null; + } + cachePropertyTypeData(propertyMetadata, propertyTypeParameterMap, parentClassTypeData, genericTypeNames, genericType); + return propertyMetadata; + } + + @SuppressWarnings("unchecked") + private static PropertyMetadata getOrCreatePropertyMetadata(final String propertyName, + final String declaringClassName, + final Map> propertyNameMap, + final TypeData typeData) { + PropertyMetadata propertyMetadata = (PropertyMetadata) propertyNameMap.get(propertyName); + if (propertyMetadata == null) { + propertyMetadata = new PropertyMetadata(propertyName, declaringClassName, typeData); + propertyNameMap.put(propertyName, propertyMetadata); + } + return propertyMetadata; + } + + private static void cachePropertyTypeData(final PropertyMetadata propertyMetadata, + final Map propertyTypeParameterMap, + final TypeData parentClassTypeData, + final List genericTypeNames, + final Type genericType) { + TypeParameterMap typeParameterMap = getTypeParameterMap(genericTypeNames, genericType); + propertyTypeParameterMap.put(propertyMetadata.getName(), typeParameterMap); + propertyMetadata.typeParameterInfo(typeParameterMap, parentClassTypeData); + } + + private static Type getGenericType(final Method method) { + return isGetter(method) ? method.getGenericReturnType() : method.getGenericParameterTypes()[0]; + } + + @SuppressWarnings("unchecked") + static PropertyModelBuilder createPropertyModelBuilder(final PropertyMetadata propertyMetadata) { + PropertyModelBuilder propertyModelBuilder = PropertyModel.builder() + .propertyName(propertyMetadata.getName()) + .readName(propertyMetadata.getName()) + .writeName(propertyMetadata.getName()) + .typeData(propertyMetadata.getTypeData()) + .readAnnotations(propertyMetadata.getReadAnnotations()) + .writeAnnotations(propertyMetadata.getWriteAnnotations()) + .propertySerialization(new PropertyModelSerializationImpl()) + .propertyAccessor(new PropertyAccessorImpl(propertyMetadata)) + .setError(propertyMetadata.getError()); + + if (propertyMetadata.getTypeParameters() != null) { + specializePropertyModelBuilder(propertyModelBuilder, propertyMetadata); + } + + return propertyModelBuilder; + } + + private static TypeParameterMap getTypeParameterMap(final List genericTypeNames, final Type propertyType) { + int classParamIndex = genericTypeNames.indexOf(propertyType.toString()); + TypeParameterMap.Builder builder = TypeParameterMap.builder(); + if (classParamIndex != -1) { + builder.addIndex(classParamIndex); + } else { + if (propertyType instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) propertyType; + for (int i = 0; i < pt.getActualTypeArguments().length; i++) { + classParamIndex = genericTypeNames.indexOf(pt.getActualTypeArguments()[i].toString()); + if (classParamIndex != -1) { + builder.addIndex(i, classParamIndex); + } + } + } + } + return builder.build(); + } + @SuppressWarnings("unchecked") + private static void specializePropertyModelBuilder(final PropertyModelBuilder propertyModelBuilder, + final PropertyMetadata propertyMetadata) { + if (propertyMetadata.getTypeParameterMap().hasTypeParameters() && !propertyMetadata.getTypeParameters().isEmpty()) { + TypeData specializedFieldType; + Map fieldToClassParamIndexMap = propertyMetadata.getTypeParameterMap().getPropertyToClassParamIndexMap(); + Integer classTypeParamRepresentsWholeField = fieldToClassParamIndexMap.get(-1); + if (classTypeParamRepresentsWholeField != null) { + specializedFieldType = (TypeData) propertyMetadata.getTypeParameters().get(classTypeParamRepresentsWholeField); + } else { + TypeData.Builder builder = TypeData.builder(propertyModelBuilder.getTypeData().getType()); + List> typeParameters = new ArrayList>(propertyModelBuilder.getTypeData().getTypeParameters()); + for (int i = 0; i < typeParameters.size(); i++) { + for (Map.Entry mapping : fieldToClassParamIndexMap.entrySet()) { + if (mapping.getKey().equals(i)) { + typeParameters.set(i, propertyMetadata.getTypeParameters().get(mapping.getValue())); + } + } + } + builder.addTypeParameters(typeParameters); + specializedFieldType = builder.build(); + } + propertyModelBuilder.typeData(specializedFieldType); + } + } + + static V stateNotNull(final String property, final V value) { + if (value == null) { + throw new IllegalStateException(format("%s cannot be null", property)); + } + return value; + } + + private PojoBuilderHelper() { + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PojoCodec.java b/bson/src/main/org/bson/codecs/pojo/PojoCodec.java new file mode 100644 index 00000000000..be6ac86248d --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PojoCodec.java @@ -0,0 +1,24 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; + +abstract class PojoCodec implements Codec { + + abstract ClassModel getClassModel(); +} diff --git a/bson/src/main/org/bson/codecs/pojo/PojoCodecImpl.java b/bson/src/main/org/bson/codecs/pojo/PojoCodecImpl.java new file mode 100644 index 00000000000..fcd67e9ae72 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PojoCodecImpl.java @@ -0,0 +1,389 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo; + +import org.bson.BsonInvalidOperationException; +import org.bson.BsonReader; +import org.bson.BsonReaderMark; +import org.bson.BsonType; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.diagnostics.Logger; +import org.bson.diagnostics.Loggers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static java.lang.String.format; +import static org.bson.codecs.configuration.CodecRegistries.fromCodecs; +import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; + + +final class PojoCodecImpl extends PojoCodec { + private static final Logger LOGGER = Loggers.getLogger("PojoCodec"); + private final ClassModel classModel; + private final CodecRegistry registry; + private final PropertyCodecRegistry propertyCodecRegistry; + private final DiscriminatorLookup discriminatorLookup; + private final ConcurrentMap, Codec> codecCache; + private final boolean specialized; + + PojoCodecImpl(final ClassModel classModel, final CodecRegistry codecRegistry, + final List propertyCodecProviders, final DiscriminatorLookup discriminatorLookup) { + this.classModel = classModel; + this.registry = fromRegistries(fromCodecs(this), codecRegistry); + this.discriminatorLookup = discriminatorLookup; + this.codecCache = new ConcurrentHashMap, Codec>(); + this.propertyCodecRegistry = new PropertyCodecRegistryImpl(this, registry, propertyCodecProviders); + this.specialized = shouldSpecialize(classModel); + specialize(); + } + + PojoCodecImpl(final ClassModel classModel, final CodecRegistry registry, final PropertyCodecRegistry propertyCodecRegistry, + final DiscriminatorLookup discriminatorLookup, final ConcurrentMap, Codec> codecCache, + final boolean specialized) { + this.classModel = classModel; + this.registry = fromRegistries(fromCodecs(this), registry); + this.discriminatorLookup = discriminatorLookup; + this.codecCache = codecCache; + this.propertyCodecRegistry = propertyCodecRegistry; + this.specialized = specialized; + specialize(); + } + + private void specialize() { + if (specialized) { + codecCache.put(classModel, this); + for (PropertyModel propertyModel : classModel.getPropertyModels()) { + addToCache(propertyModel); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void encode(final BsonWriter writer, final T value, final EncoderContext encoderContext) { + if (!specialized) { + throw new CodecConfigurationException(format("%s contains generic types that have not been specialised.%n" + + "Top level classes with generic types are not supported by the PojoCodec.", classModel.getName())); + } + + if (areEquivalentTypes(value.getClass(), classModel.getType())) { + writer.writeStartDocument(); + + encodeIdProperty(writer, value, encoderContext, classModel.getIdPropertyModelHolder()); + + if (classModel.useDiscriminator()) { + writer.writeString(classModel.getDiscriminatorKey(), classModel.getDiscriminator()); + } + + for (PropertyModel propertyModel : classModel.getPropertyModels()) { + if (propertyModel.equals(classModel.getIdPropertyModel())) { + continue; + } + encodeProperty(writer, value, encoderContext, propertyModel); + } + writer.writeEndDocument(); + } else { + ((Codec) registry.get(value.getClass())).encode(writer, value, encoderContext); + } + } + + @Override + public T decode(final BsonReader reader, final DecoderContext decoderContext) { + if (decoderContext.hasCheckedDiscriminator()) { + if (!specialized) { + throw new CodecConfigurationException(format("%s contains generic types that have not been specialised.%n" + + "Top level classes with generic types are not supported by the PojoCodec.", classModel.getName())); + } + InstanceCreator instanceCreator = classModel.getInstanceCreator(); + decodeProperties(reader, decoderContext, instanceCreator); + return instanceCreator.getInstance(); + } else { + return getCodecFromDocument(reader, classModel.useDiscriminator(), classModel.getDiscriminatorKey(), registry, + discriminatorLookup, this).decode(reader, DecoderContext.builder().checkedDiscriminator(true).build()); + } + } + + @Override + public Class getEncoderClass() { + return classModel.getType(); + } + + @Override + public String toString() { + return format("PojoCodec<%s>", classModel); + } + + ClassModel getClassModel() { + return classModel; + } + + private void encodeIdProperty(final BsonWriter writer, final T instance, final EncoderContext encoderContext, + final IdPropertyModelHolder propertyModelHolder) { + if (propertyModelHolder.getPropertyModel() != null) { + if (propertyModelHolder.getIdGenerator() == null) { + encodeProperty(writer, instance, encoderContext, propertyModelHolder.getPropertyModel()); + } else { + S id = propertyModelHolder.getPropertyModel().getPropertyAccessor().get(instance); + if (id == null && encoderContext.isEncodingCollectibleDocument()) { + id = propertyModelHolder.getIdGenerator().generate(); + try { + propertyModelHolder.getPropertyModel().getPropertyAccessor().set(instance, id); + } catch (Exception e) { + // ignore + } + } + encodeValue(writer, encoderContext, propertyModelHolder.getPropertyModel(), id); + } + } + } + + @SuppressWarnings("unchecked") + private void encodeProperty(final BsonWriter writer, final T instance, final EncoderContext encoderContext, + final PropertyModel propertyModel) { + if (propertyModel != null && propertyModel.isReadable()) { + S propertyValue = propertyModel.getPropertyAccessor().get(instance); + encodeValue(writer, encoderContext, propertyModel, propertyValue); + } + } + + private void encodeValue(final BsonWriter writer, final EncoderContext encoderContext, final PropertyModel propertyModel, + final S propertyValue) { + if (propertyModel.shouldSerialize(propertyValue)) { + writer.writeName(propertyModel.getReadName()); + if (propertyValue == null) { + writer.writeNull(); + } else { + try { + encoderContext.encodeWithChildContext(propertyModel.getCachedCodec(), writer, propertyValue); + } catch (CodecConfigurationException e) { + throw new CodecConfigurationException(format("Failed to encode '%s'. Encoding '%s' errored with: %s", + classModel.getName(), propertyModel.getReadName(), e.getMessage()), e); + } + } + } + } + + @SuppressWarnings("unchecked") + private void decodeProperties(final BsonReader reader, final DecoderContext decoderContext, final InstanceCreator instanceCreator) { + reader.readStartDocument(); + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + String name = reader.readName(); + if (classModel.useDiscriminator() && classModel.getDiscriminatorKey().equals(name)) { + reader.readString(); + } else { + decodePropertyModel(reader, decoderContext, instanceCreator, name, getPropertyModelByWriteName(classModel, name)); + } + } + reader.readEndDocument(); + } + + @SuppressWarnings("unchecked") + private void decodePropertyModel(final BsonReader reader, final DecoderContext decoderContext, + final InstanceCreator instanceCreator, final String name, + final PropertyModel propertyModel) { + if (propertyModel != null) { + try { + S value = null; + if (reader.getCurrentBsonType() == BsonType.NULL) { + reader.readNull(); + } else { + value = decoderContext.decodeWithChildContext(propertyModel.getCachedCodec(), reader); + } + if (propertyModel.isWritable()) { + instanceCreator.set(value, propertyModel); + } + } catch (BsonInvalidOperationException e) { + throw new CodecConfigurationException(format("Failed to decode '%s'. Decoding '%s' errored with: %s", + classModel.getName(), name, e.getMessage()), e); + } catch (CodecConfigurationException e) { + throw new CodecConfigurationException(format("Failed to decode '%s'. Decoding '%s' errored with: %s", + classModel.getName(), name, e.getMessage()), e); + } + } else { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace(format("Found property not present in the ClassModel: %s", name)); + } + reader.skipValue(); + } + } + + private void addToCache(final PropertyModel propertyModel) { + Codec codec = propertyModel.getCodec() != null ? propertyModel.getCodec() : specializePojoCodec(propertyModel); + propertyModel.cachedCodec(codec); + } + + private boolean areEquivalentTypes(final Class t1, final Class t2) { + if (t1.equals(t2)) { + return true; + } else if (Collection.class.isAssignableFrom(t1) && Collection.class.isAssignableFrom(t2)) { + return true; + } else if (Map.class.isAssignableFrom(t1) && Map.class.isAssignableFrom(t2)) { + return true; + } + return false; + } + + + + @SuppressWarnings("unchecked") + private Codec specializePojoCodec(final PropertyModel propertyModel) { + Codec codec = getCodecFromPropertyRegistry(propertyModel); + if (codec instanceof PojoCodec) { + PojoCodec pojoCodec = (PojoCodec) codec; + ClassModel specialized = getSpecializedClassModel(pojoCodec.getClassModel(), propertyModel); + if (codecCache.containsKey(specialized)) { + codec = (Codec) codecCache.get(specialized); + } else { + codec = new LazyPojoCodec(specialized, registry, propertyCodecRegistry, discriminatorLookup, codecCache); + } + } + return codec; + } + + private Codec getCodecFromPropertyRegistry(final PropertyModel propertyModel) { + try { + return propertyCodecRegistry.get(propertyModel.getTypeData()); + } catch (CodecConfigurationException e) { + return new LazyMissingCodec(propertyModel.getTypeData().getType(), e); + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private ClassModel getSpecializedClassModel(final ClassModel clazzModel, final PropertyModel propertyModel) { + boolean useDiscriminator = propertyModel.useDiscriminator() == null ? clazzModel.useDiscriminator() + : propertyModel.useDiscriminator(); + boolean validDiscriminator = clazzModel.getDiscriminatorKey() != null && clazzModel.getDiscriminator() != null; + boolean changeTheDiscriminator = (useDiscriminator != clazzModel.useDiscriminator()) && validDiscriminator; + + if (propertyModel.getTypeData().getTypeParameters().isEmpty() && !changeTheDiscriminator){ + return clazzModel; + } + + ArrayList> concretePropertyModels = new ArrayList>(clazzModel.getPropertyModels()); + PropertyModel concreteIdProperty = clazzModel.getIdPropertyModel(); + + List> propertyTypeParameters = propertyModel.getTypeData().getTypeParameters(); + for (int i = 0; i < concretePropertyModels.size(); i++) { + PropertyModel model = concretePropertyModels.get(i); + String propertyName = model.getName(); + TypeParameterMap typeParameterMap = clazzModel.getPropertyNameToTypeParameterMap().get(propertyName); + if (typeParameterMap.hasTypeParameters()) { + PropertyModel concretePropertyModel = getSpecializedPropertyModel(model, typeParameterMap, propertyTypeParameters); + concretePropertyModels.set(i, concretePropertyModel); + if (concreteIdProperty != null && concreteIdProperty.getName().equals(propertyName)) { + concreteIdProperty = concretePropertyModel; + } + } + } + + boolean discriminatorEnabled = changeTheDiscriminator ? propertyModel.useDiscriminator() : clazzModel.useDiscriminator(); + return new ClassModel(clazzModel.getType(), clazzModel.getPropertyNameToTypeParameterMap(), + clazzModel.getInstanceCreatorFactory(), discriminatorEnabled, clazzModel.getDiscriminatorKey(), + clazzModel.getDiscriminator(), IdPropertyModelHolder.create(clazzModel, concreteIdProperty), concretePropertyModels); + } + + @SuppressWarnings("unchecked") + private PropertyModel getSpecializedPropertyModel(final PropertyModel propertyModel, final TypeParameterMap typeParameterMap, + final List> propertyTypeParameters) { + TypeData specializedPropertyType; + Map propertyToClassParamIndexMap = typeParameterMap.getPropertyToClassParamIndexMap(); + Integer classTypeParamRepresentsWholeProperty = propertyToClassParamIndexMap.get(-1); + if (classTypeParamRepresentsWholeProperty != null) { + specializedPropertyType = (TypeData) propertyTypeParameters.get(classTypeParamRepresentsWholeProperty); + } else { + TypeData.Builder builder = TypeData.builder(propertyModel.getTypeData().getType()); + List> typeParameters = new ArrayList>(propertyModel.getTypeData().getTypeParameters()); + for (int i = 0; i < typeParameters.size(); i++) { + for (Map.Entry mapping : propertyToClassParamIndexMap.entrySet()) { + if (mapping.getKey().equals(i)) { + typeParameters.set(i, propertyTypeParameters.get(mapping.getValue())); + } + } + } + builder.addTypeParameters(typeParameters); + specializedPropertyType = builder.build(); + } + if (propertyModel.getTypeData().equals(specializedPropertyType)) { + return propertyModel; + } + + return new PropertyModel(propertyModel.getName(), propertyModel.getReadName(), propertyModel.getWriteName(), + specializedPropertyType, null, propertyModel.getPropertySerialization(), propertyModel.useDiscriminator(), + propertyModel.getPropertyAccessor(), propertyModel.getError()); + } + + @SuppressWarnings("unchecked") + private Codec getCodecFromDocument(final BsonReader reader, final boolean useDiscriminator, final String discriminatorKey, + final CodecRegistry registry, final DiscriminatorLookup discriminatorLookup, + final Codec defaultCodec) { + Codec codec = defaultCodec; + if (useDiscriminator) { + BsonReaderMark mark = reader.getMark(); + reader.readStartDocument(); + boolean discriminatorKeyFound = false; + while (!discriminatorKeyFound && reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + String name = reader.readName(); + if (discriminatorKey.equals(name)) { + discriminatorKeyFound = true; + try { + codec = (Codec) registry.get(discriminatorLookup.lookup(reader.readString())); + } catch (Exception e) { + throw new CodecConfigurationException(format("Failed to decode '%s'. Decoding errored with: %s", + classModel.getName(), e.getMessage()), e); + } + } else { + reader.skipValue(); + } + } + mark.reset(); + } + return codec; + } + + private PropertyModel getPropertyModelByWriteName(final ClassModel classModel, final String readName) { + for (PropertyModel propertyModel : classModel.getPropertyModels()) { + if (propertyModel.isWritable() && propertyModel.getWriteName().equals(readName)) { + return propertyModel; + } + } + return null; + } + + private static boolean shouldSpecialize(final ClassModel classModel) { + if (!classModel.hasTypeParameters()) { + return true; + } + + for (Map.Entry entry : classModel.getPropertyNameToTypeParameterMap().entrySet()) { + TypeParameterMap typeParameterMap = entry.getValue(); + PropertyModel propertyModel = classModel.getPropertyModel(entry.getKey()); + if (typeParameterMap.hasTypeParameters() && (propertyModel == null || propertyModel.getCodec() == null)) { + return false; + } + } + return true; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PojoCodecProvider.java b/bson/src/main/org/bson/codecs/pojo/PojoCodecProvider.java new file mode 100644 index 00000000000..38ee53ebe66 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PojoCodecProvider.java @@ -0,0 +1,221 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.diagnostics.Logger; +import org.bson.diagnostics.Loggers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static org.bson.assertions.Assertions.notNull; + +/** + * Provides Codecs for registered POJOs via the ClassModel abstractions. + * + * @since 3.5 + */ +public final class PojoCodecProvider implements CodecProvider { + static final Logger LOGGER = Loggers.getLogger("codecs.pojo"); + private final boolean automatic; + private final Map, ClassModel> classModels; + private final Set packages; + private final List conventions; + private final DiscriminatorLookup discriminatorLookup; + private final List propertyCodecProviders; + + private PojoCodecProvider(final boolean automatic, final Map, ClassModel> classModels, final Set packages, + final List conventions, final List propertyCodecProviders) { + this.automatic = automatic; + this.classModels = classModels; + this.packages = packages; + this.conventions = conventions; + this.discriminatorLookup = new DiscriminatorLookup(classModels, packages); + this.propertyCodecProviders = propertyCodecProviders; + } + + /** + * Creates a Builder so classes or packages can be registered and configured before creating an immutable CodecProvider. + * + * @return the Builder + * @see Builder#register(Class[]) + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public Codec get(final Class clazz, final CodecRegistry registry) { + return getPojoCodec(clazz, registry); + } + + @SuppressWarnings("unchecked") + private PojoCodec getPojoCodec(final Class clazz, final CodecRegistry registry) { + ClassModel classModel = (ClassModel) classModels.get(clazz); + if (classModel != null) { + return new PojoCodecImpl(classModel, registry, propertyCodecProviders, discriminatorLookup); + } else if (automatic || (clazz.getPackage() != null && packages.contains(clazz.getPackage().getName()))) { + try { + classModel = createClassModel(clazz, conventions); + if (clazz.isInterface() || !classModel.getPropertyModels().isEmpty()) { + discriminatorLookup.addClassModel(classModel); + return new AutomaticPojoCodec(new PojoCodecImpl(classModel, registry, propertyCodecProviders, + discriminatorLookup)); + } + } catch (Exception e) { + LOGGER.warn(format("Cannot use '%s' with the PojoCodec.", clazz.getSimpleName()), e); + return null; + } + } + return null; + } + + /** + * A Builder for the PojoCodecProvider + */ + public static final class Builder { + private final Set packages = new HashSet(); + private final Map, ClassModel> classModels = new HashMap, ClassModel>(); + private final List> clazzes = new ArrayList>(); + private List conventions = null; + private final List propertyCodecProviders = new ArrayList(); + private boolean automatic; + + /** + * Creates the PojoCodecProvider with the classes or packages that configured and registered. + * + * @return the Provider + * @see #register(Class...) + */ + public PojoCodecProvider build() { + List immutableConventions = conventions != null + ? Collections.unmodifiableList(new ArrayList(conventions)) + : null; + for (Class clazz : clazzes) { + if (!classModels.containsKey(clazz)) { + register(createClassModel(clazz, immutableConventions)); + } + } + return new PojoCodecProvider(automatic, classModels, packages, immutableConventions, propertyCodecProviders); + } + + /** + * Sets whether the provider should automatically try to wrap a {@link ClassModel} for any class that is requested. + * + *

    Note: As Java Beans are convention based, when using automatic settings the provider should be the last provider in the + * registry.

    + * + * @param automatic whether to automatically wrap {@code ClassModels} or not. + * @return this + */ + public Builder automatic(final boolean automatic) { + this.automatic = automatic; + return this; + } + + /** + * Sets the conventions to use when creating {@code ClassModels} from classes or packages. + * + * @param conventions a list of conventions + * @return this + */ + public Builder conventions(final List conventions) { + this.conventions = notNull("conventions", conventions); + return this; + } + + /** + * Registers a classes with the builder for inclusion in the Provider. + * + *

    Note: Uses reflection for the property mapping. If no conventions are configured on the builder the + * {@link Conventions#DEFAULT_CONVENTIONS} will be used.

    + * + * @param classes the classes to register + * @return this + */ + public Builder register(final Class... classes) { + clazzes.addAll(asList(classes)); + return this; + } + + /** + * Registers classModels for inclusion in the Provider. + * + * @param classModels the classModels to register + * @return this + */ + public Builder register(final ClassModel... classModels) { + notNull("classModels", classModels); + for (ClassModel classModel : classModels) { + this.classModels.put(classModel.getType(), classModel); + } + return this; + } + + /** + * Registers the packages of the given classes with the builder for inclusion in the Provider. This will allow classes in the + * given packages to mapped for use with PojoCodecProvider. + * + *

    Note: Uses reflection for the field mapping. If no conventions are configured on the builder the + * {@link Conventions#DEFAULT_CONVENTIONS} will be used.

    + * + * @param packageNames the package names to register + * @return this + */ + public Builder register(final String... packageNames) { + packages.addAll(asList(notNull("packageNames", packageNames))); + return this; + } + + /** + * Registers codec providers that receive the type parameters of properties for instances encoded and decoded + * by a {@link PojoCodec} handled by this provider. + * + *

    Note that you should prefer working with the {@link CodecRegistry}/{@link CodecProvider} hierarchy. Providers + * should only be registered here if a codec needs to be created for custom container types like optionals and + * collections. Support for types {@link Map} and {@link java.util.Collection} are built-in so explicitly handling + * them is not necessary. + * @param providers property codec providers to register + * @return this + * @since 3.6 + */ + public Builder register(final PropertyCodecProvider... providers) { + propertyCodecProviders.addAll(asList(notNull("providers", providers))); + return this; + } + + private Builder() { + } + } + + private static ClassModel createClassModel(final Class clazz, final List conventions) { + ClassModelBuilder builder = ClassModel.builder(clazz); + if (conventions != null) { + builder.conventions(conventions); + } + return builder.build(); + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyAccessor.java b/bson/src/main/org/bson/codecs/pojo/PropertyAccessor.java new file mode 100644 index 00000000000..740856d876e --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyAccessor.java @@ -0,0 +1,44 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +/** + * Provides access for getting and setting property data. + * + * @param the type of the property + * @since 3.5 + */ +public interface PropertyAccessor { + + /** + * Gets the value for a given PropertyModel instance. + * + * @param instance the class instance to get the property value from + * @param the class instance type + * @return the value of the property. + */ + T get(S instance); + + /** + * Sets a value on the given PropertyModel + * + * @param instance the instance to set the property value to + * @param the class instance type + * @param value the new value for the property + */ + void set(S instance, T value); +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyAccessorImpl.java b/bson/src/main/org/bson/codecs/pojo/PropertyAccessorImpl.java new file mode 100644 index 00000000000..d0d963b093e --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyAccessorImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; + +import static java.lang.String.format; + +final class PropertyAccessorImpl implements PropertyAccessor { + + private final PropertyMetadata propertyMetadata; + + PropertyAccessorImpl(final PropertyMetadata propertyMetadata) { + this.propertyMetadata = propertyMetadata; + } + + @Override + @SuppressWarnings("unchecked") + public T get(final S instance) { + try { + if (propertyMetadata.isSerializable()) { + if (propertyMetadata.getGetter() != null) { + return (T) propertyMetadata.getGetter().invoke(instance); + } else { + return (T) propertyMetadata.getField().get(instance); + } + } else { + throw getError(null); + } + } catch (final Exception e) { + throw getError(e); + } + } + + @Override + public void set(final S instance, final T value) { + try { + if (propertyMetadata.isDeserializable()) { + if (propertyMetadata.getSetter() != null) { + propertyMetadata.getSetter().invoke(instance, value); + } else { + propertyMetadata.getField().set(instance, value); + } + } + } catch (final Exception e) { + throw setError(e); + } + } + + PropertyMetadata getPropertyMetadata() { + return propertyMetadata; + } + + private CodecConfigurationException getError(final Exception cause) { + return new CodecConfigurationException(format("Unable to get value for property '%s' in %s", propertyMetadata.getName(), + propertyMetadata.getDeclaringClassName()), cause); + } + + private CodecConfigurationException setError(final Exception cause) { + return new CodecConfigurationException(format("Unable to set value for property '%s' in %s", propertyMetadata.getName(), + propertyMetadata.getDeclaringClassName()), cause); + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyCodecProvider.java b/bson/src/main/org/bson/codecs/pojo/PropertyCodecProvider.java new file mode 100644 index 00000000000..765a88400aa --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyCodecProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; + +/** + * A variant of {@link org.bson.codecs.configuration.CodecProvider} that generates codecs for {@link PojoCodec}. + * + *

    This is a specialized codec provider that retrieves codecs which account for type parameters associated with + * a property. In particular this should only be used to add support for custom container types like optionals. + * It's only applicable for use by {@link PojoCodec} registered through {@link PojoCodecProvider#builder()}. + * + * @since 3.6 + */ +public interface PropertyCodecProvider { + + /** + * Get a {@code Codec} using the given context, which includes, most importantly, the class and bound type parameters + * for which a {@code Codec} is required. + * + * @param type the class and bound type parameters for which to get a Codec + * @param registry the registry to use for resolving dependent Codec instances + * @param the type of the class for which a Codec is required + * @return the Codec instance, which may be null, if this source is unable to provide one for the requested Class + */ + Codec get(TypeWithTypeParameters type, PropertyCodecRegistry registry); +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyCodecRegistry.java b/bson/src/main/org/bson/codecs/pojo/PropertyCodecRegistry.java new file mode 100644 index 00000000000..7b91628b987 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyCodecRegistry.java @@ -0,0 +1,41 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecConfigurationException; + +/** + * A variant of {@link org.bson.codecs.configuration.CodecRegistry} that generates codecs for {@link PojoCodec}. + * + *

    This is a specialized codec registry that retrieves codecs which account for type parameters associated with + * a property. In particular this should only be used to add support for custom container types like optionals. + * It's only applicable for use by {@link PojoCodec} registered through {@link PojoCodecProvider#builder()}. + * + * @since 3.6 + */ +public interface PropertyCodecRegistry { + + /** + * Gets a {@code Codec} for the given Class. + * + * @param type the Class associated type parameters for this property for which to get a Codec + * @param the class type + * @return a codec for the given class + * @throws CodecConfigurationException if the registry does not contain a codec for the given class. + */ + Codec get(TypeWithTypeParameters type); +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyCodecRegistryImpl.java b/bson/src/main/org/bson/codecs/pojo/PropertyCodecRegistryImpl.java new file mode 100644 index 00000000000..5adeebeb018 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyCodecRegistryImpl.java @@ -0,0 +1,51 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.ArrayList; +import java.util.List; + +class PropertyCodecRegistryImpl implements PropertyCodecRegistry { + private final List propertyCodecProviders; + + PropertyCodecRegistryImpl(final PojoCodec pojoCodec, final CodecRegistry codecRegistry, + final List propertyCodecProviders) { + List augmentedProviders = new ArrayList(); + if (propertyCodecProviders != null) { + augmentedProviders.addAll(propertyCodecProviders); + } + augmentedProviders.add(new CollectionPropertyCodecProvider()); + augmentedProviders.add(new MapPropertyCodecProvider()); + augmentedProviders.add(new EnumPropertyCodecProvider(codecRegistry)); + augmentedProviders.add(new FallbackPropertyCodecProvider(pojoCodec, codecRegistry)); + this.propertyCodecProviders = augmentedProviders; + } + + @Override + public Codec get(final TypeWithTypeParameters type) { + for (PropertyCodecProvider propertyCodecProvider : propertyCodecProviders) { + Codec codec = propertyCodecProvider.get(type, this); + if (codec != null) { + return codec; + } + } + return null; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyMetadata.java b/bson/src/main/org/bson/codecs/pojo/PropertyMetadata.java new file mode 100644 index 00000000000..cf1c9a45e06 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyMetadata.java @@ -0,0 +1,171 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; +import static java.lang.reflect.Modifier.isFinal; +import static java.lang.reflect.Modifier.isPublic; +import static java.lang.reflect.Modifier.isStatic; +import static java.lang.reflect.Modifier.isTransient; + +final class PropertyMetadata { + private final String name; + private final String declaringClassName; + private final TypeData typeData; + private final Map, Annotation> readAnnotations = new HashMap, Annotation>(); + private final Map, Annotation> writeAnnotations = new HashMap, Annotation>(); + private TypeParameterMap typeParameterMap; + private List> typeParameters; + + private String error; + private Field field; + private Method getter; + private Method setter; + + PropertyMetadata(final String name, final String declaringClassName, final TypeData typeData) { + this.name = name; + this.declaringClassName = declaringClassName; + this.typeData = typeData; + } + + public String getName() { + return name; + } + + public List getReadAnnotations() { + return new ArrayList(readAnnotations.values()); + } + + public PropertyMetadata addReadAnnotation(final Annotation annotation) { + if (readAnnotations.containsKey(annotation.annotationType())) { + if (annotation.equals(readAnnotations.get(annotation.annotationType()))) { + return this; + } + throw new CodecConfigurationException(format("Read annotation %s for '%s' already exists in %s", annotation.annotationType(), + name, declaringClassName)); + } + readAnnotations.put(annotation.annotationType(), annotation); + return this; + } + + public List getWriteAnnotations() { + return new ArrayList(writeAnnotations.values()); + } + + public PropertyMetadata addWriteAnnotation(final Annotation annotation) { + if (writeAnnotations.containsKey(annotation.annotationType())) { + if (annotation.equals(writeAnnotations.get(annotation.annotationType()))) { + return this; + } + throw new CodecConfigurationException(format("Write annotation %s for '%s' already exists in %s", annotation.annotationType(), + name, declaringClassName)); + } + writeAnnotations.put(annotation.annotationType(), annotation); + return this; + } + + public Field getField() { + return field; + } + + public PropertyMetadata field(final Field field) { + this.field = field; + return this; + } + + public Method getGetter() { + return getter; + } + + public void setGetter(final Method getter) { + this.getter = getter; + } + + public Method getSetter() { + return setter; + } + + public void setSetter(final Method setter) { + this.setter = setter; + } + + public String getDeclaringClassName() { + return declaringClassName; + } + + public TypeData getTypeData() { + return typeData; + } + + public TypeParameterMap getTypeParameterMap() { + return typeParameterMap; + } + + public List> getTypeParameters() { + return typeParameters; + } + + public PropertyMetadata typeParameterInfo(final TypeParameterMap typeParameterMap, final TypeData parentTypeData) { + if (typeParameterMap != null && parentTypeData != null) { + this.typeParameterMap = typeParameterMap; + this.typeParameters = parentTypeData.getTypeParameters(); + } + return this; + } + + String getError() { + return error; + } + + void setError(final String error) { + this.error = error; + } + + public boolean isSerializable() { + if (getter != null) { + return field == null || notStaticOrTransient(field.getModifiers()); + } else { + return field != null && isPublicAndNotStaticOrTransient(field.getModifiers()); + } + } + + public boolean isDeserializable() { + if (setter != null) { + return field == null || !isFinal(field.getModifiers()) && notStaticOrTransient(field.getModifiers()); + } else { + return field != null && !isFinal(field.getModifiers()) && isPublicAndNotStaticOrTransient(field.getModifiers()); + } + } + + private boolean notStaticOrTransient(final int modifiers) { + return !(isTransient(modifiers) || isStatic(modifiers)); + } + + private boolean isPublicAndNotStaticOrTransient(final int modifiers) { + return isPublic(modifiers) && notStaticOrTransient(modifiers); + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyModel.java b/bson/src/main/org/bson/codecs/pojo/PropertyModel.java new file mode 100644 index 00000000000..799fe88644b --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyModel.java @@ -0,0 +1,233 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; + +/** + * Represents a property on a class and stores various metadata such as generic parameters + * + * @param the type of the property that the PropertyModel represents. + * @since 3.5 + */ +public final class PropertyModel { + private final String name; + private final String readName; + private final String writeName; + private final TypeData typeData; + private final Codec codec; + private final PropertySerialization propertySerialization; + private final Boolean useDiscriminator; + private final PropertyAccessor propertyAccessor; + private final String error; + private volatile Codec cachedCodec; + + PropertyModel(final String name, final String readName, final String writeName, final TypeData typeData, + final Codec codec, final PropertySerialization propertySerialization, final Boolean useDiscriminator, + final PropertyAccessor propertyAccessor, final String error) { + this.name = name; + this.readName = readName; + this.writeName = writeName; + this.typeData = typeData; + this.codec = codec; + this.cachedCodec = codec; + this.propertySerialization = propertySerialization; + this.useDiscriminator = useDiscriminator; + this.propertyAccessor = propertyAccessor; + this.error = error; + } + + /** + * Create a new {@link PropertyModelBuilder} + * @param the type of the property + * @return the builder + */ + public static PropertyModelBuilder builder() { + return new PropertyModelBuilder(); + } + + /** + * @return the property name for the model + */ + public String getName() { + return name; + } + + /** + * @return the name of the property to use as the key when deserializing from BSON + */ + public String getWriteName() { + return writeName; + } + + /** + * @return the name of the property to use as the key when serializing into BSON + */ + public String getReadName() { + return readName; + } + + /** + * Property is writable. + * + * @return true if can be deserialized from BSON + */ + public boolean isWritable() { + return writeName != null; + } + + /** + * Property is readable. + * + * @return true if can be serialized to BSON + */ + public boolean isReadable() { + return readName != null; + } + + /** + * @return the type data for the property + */ + public TypeData getTypeData() { + return typeData; + } + + /** + * @return the custom codec to use if set or null + */ + public Codec getCodec() { + return codec; + } + + /** + * Returns true if the value should be serialized. + * + * @param value the value to check + * @return true if the value should be serialized. + */ + public boolean shouldSerialize(final T value) { + return propertySerialization.shouldSerialize(value); + } + + /** + * @return the property accessor + */ + public PropertyAccessor getPropertyAccessor() { + return propertyAccessor; + } + + /** + * @return true or false if a discriminator should be used when serializing or null if not set + */ + public Boolean useDiscriminator() { + return useDiscriminator; + } + + @Override + public String toString() { + return "PropertyModel{" + + "propertyName='" + name + "'" + + ", readName='" + readName + "'" + + ", writeName='" + writeName + "'" + + ", typeData=" + typeData + + "}"; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PropertyModel that = (PropertyModel) o; + + if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { + return false; + } + if (getReadName() != null ? !getReadName().equals(that.getReadName()) : that.getReadName() != null) { + return false; + } + if (getWriteName() != null ? !getWriteName().equals(that.getWriteName()) : that.getWriteName() != null) { + return false; + } + if (getTypeData() != null ? !getTypeData().equals(that.getTypeData()) : that.getTypeData() != null) { + return false; + } + if (getCodec() != null ? !getCodec().equals(that.getCodec()) : that.getCodec() != null) { + return false; + } + if (getPropertySerialization() != null ? !getPropertySerialization().equals(that.getPropertySerialization()) : that + .getPropertySerialization() != null) { + return false; + } + if (useDiscriminator != null ? !useDiscriminator.equals(that.useDiscriminator) : that.useDiscriminator != null) { + return false; + } + if (getPropertyAccessor() != null ? !getPropertyAccessor().equals(that.getPropertyAccessor()) + : that.getPropertyAccessor() != null) { + return false; + } + + if (getError() != null ? !getError().equals(that.getError()) : that.getError() != null) { + return false; + } + + if (getCachedCodec() != null ? !getCachedCodec().equals(that.getCachedCodec()) : that.getCachedCodec() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getName() != null ? getName().hashCode() : 0; + result = 31 * result + (getReadName() != null ? getReadName().hashCode() : 0); + result = 31 * result + (getWriteName() != null ? getWriteName().hashCode() : 0); + result = 31 * result + (getTypeData() != null ? getTypeData().hashCode() : 0); + result = 31 * result + (getCodec() != null ? getCodec().hashCode() : 0); + result = 31 * result + (getPropertySerialization() != null ? getPropertySerialization().hashCode() : 0); + result = 31 * result + (useDiscriminator != null ? useDiscriminator.hashCode() : 0); + result = 31 * result + (getPropertyAccessor() != null ? getPropertyAccessor().hashCode() : 0); + result = 31 * result + (getError() != null ? getError().hashCode() : 0); + result = 31 * result + (getCachedCodec() != null ? getCachedCodec().hashCode() : 0); + return result; + } + + boolean hasError() { + return error != null; + } + + String getError() { + return error; + } + + PropertySerialization getPropertySerialization() { + return propertySerialization; + } + + void cachedCodec(final Codec codec) { + this.cachedCodec = codec; + } + + Codec getCachedCodec() { + return cachedCodec; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyModelBuilder.java b/bson/src/main/org/bson/codecs/pojo/PropertyModelBuilder.java new file mode 100644 index 00000000000..9470d8a6931 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyModelBuilder.java @@ -0,0 +1,277 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; + +import java.lang.annotation.Annotation; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; +import static org.bson.assertions.Assertions.notNull; +import static org.bson.codecs.pojo.PojoBuilderHelper.stateNotNull; + +/** + * A builder for programmatically creating {@code PropertyModels}. + * + * @param the type of the property + * @since 3.5 + * @see PropertyModel + */ +public final class PropertyModelBuilder { + private String name; + private String readName; + private String writeName; + private TypeData typeData; + private PropertySerialization propertySerialization; + private Codec codec; + private PropertyAccessor propertyAccessor; + private List readAnnotations = emptyList(); + private List writeAnnotations = emptyList(); + private Boolean discriminatorEnabled; + private String error; + + PropertyModelBuilder() { + } + + /** + * @return the property name + */ + public String getName() { + return name; + } + + /** + * @return the name of the property to use as the key when deserializing the data from BSON. + */ + public String getReadName() { + return readName; + } + + /** + * Sets the readName, the key for this property when deserializing the data from BSON. + * + *

    Note: A null means this property will not used when deserializing.

    + * + * @param readName the name of the property to use as the key when deserializing the data from BSON. + * @return this + */ + public PropertyModelBuilder readName(final String readName) { + this.readName = readName; + return this; + } + + /** + * @return the name of the property to use as the key when serializing the data into BSON. + */ + public String getWriteName() { + return writeName; + } + + /** + * Sets the writeName, the key for this property when serializing the data into BSON. + * + *

    Note: A null means this property will not be serialized.

    + * + * @param writeName the name of the property to use as the key when serializing the data into BSON. + * @return this + */ + public PropertyModelBuilder writeName(final String writeName) { + this.writeName = writeName; + return this; + } + + /** + * Sets a custom codec for the property + * + * @param codec the custom codec for the property + * @return this + */ + public PropertyModelBuilder codec(final Codec codec) { + this.codec = codec; + return this; + } + + /** + * @return the custom codec to use if set or null + */ + Codec getCodec() { + return codec; + } + + /** + * Sets the {@link PropertySerialization} checker + * + * @param propertySerialization checks if a property should be serialized + * @return this + */ + public PropertyModelBuilder propertySerialization(final PropertySerialization propertySerialization) { + this.propertySerialization = notNull("propertySerialization", propertySerialization); + return this; + } + + /** + * @return the {@link PropertySerialization} checker + */ + public PropertySerialization getPropertySerialization() { + return propertySerialization; + } + + /** + * Returns the read annotations, to be applied when serializing to BSON + * + * @return the read annotations + */ + public List getReadAnnotations() { + return readAnnotations; + } + + /** + * Sets the read annotations, to be applied when serializing to BSON + * + * @param annotations the read annotations + * @return this + */ + public PropertyModelBuilder readAnnotations(final List annotations) { + this.readAnnotations = unmodifiableList(notNull("annotations", annotations)); + return this; + } + + /** + * Returns the write annotations, to be applied when deserializing from BSON + * + * @return the write annotations + */ + public List getWriteAnnotations() { + return writeAnnotations; + } + + /** + * Sets the writeAnnotations, to be applied when deserializing from BSON + * + * @param writeAnnotations the writeAnnotations + * @return this + */ + public PropertyModelBuilder writeAnnotations(final List writeAnnotations) { + this.writeAnnotations = writeAnnotations; + return this; + } + + /** + * Property is writable. + * + * @return true if can be deserialized from BSON + */ + public boolean isWritable() { + return writeName != null; + } + + /** + * Property is readable. + * + * @return true if can be serialized to BSON + */ + public boolean isReadable() { + return readName != null; + } + + /** + * @return true or false if a discriminator should be used when serializing or null if not set + */ + public Boolean isDiscriminatorEnabled() { + return discriminatorEnabled; + } + + /** + * Enables or disables the use of a discriminator when serializing + * + * @param discriminatorEnabled the useDiscriminator value + * @return this + */ + public PropertyModelBuilder discriminatorEnabled(final boolean discriminatorEnabled) { + this.discriminatorEnabled = discriminatorEnabled; + return this; + } + + /** + * Returns the {@link PropertyAccessor} + * + * @return the PropertyAccessor + */ + public PropertyAccessor getPropertyAccessor() { + return propertyAccessor; + } + + /** + * Sets the {@link PropertyAccessor} + * + * @param propertyAccessor the PropertyAccessor + * @return this + */ + public PropertyModelBuilder propertyAccessor(final PropertyAccessor propertyAccessor) { + this.propertyAccessor = propertyAccessor; + return this; + } + + /** + * Creates the {@link PropertyModel}. + * + * @return the PropertyModel + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public PropertyModel build() { + if (!isReadable() && !isWritable()) { + throw new IllegalStateException(format("Invalid PropertyModel '%s', neither readable or writable,", name)); + } + return new PropertyModel( + stateNotNull("propertyName", name), + readName, + writeName, + stateNotNull("typeData", typeData), + codec, + stateNotNull("propertySerialization", propertySerialization), + discriminatorEnabled, + stateNotNull("propertyAccessor", propertyAccessor), + error); + } + + @Override + public String toString() { + return format("PropertyModelBuilder{propertyName=%s, typeData=%s}", name, typeData); + } + + PropertyModelBuilder propertyName(final String propertyName) { + this.name = notNull("propertyName", propertyName); + return this; + } + + TypeData getTypeData() { + return typeData; + } + + PropertyModelBuilder typeData(final TypeData typeData) { + this.typeData = notNull("typeData", typeData); + return this; + } + + PropertyModelBuilder setError(final String error) { + this.error = error; + return this; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyModelSerializationImpl.java b/bson/src/main/org/bson/codecs/pojo/PropertyModelSerializationImpl.java new file mode 100644 index 00000000000..41f44b4a570 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyModelSerializationImpl.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +class PropertyModelSerializationImpl implements PropertySerialization { + + PropertyModelSerializationImpl() { + } + + @Override + public boolean shouldSerialize(final T value) { + return value != null; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertyReflectionUtils.java b/bson/src/main/org/bson/codecs/pojo/PropertyReflectionUtils.java new file mode 100644 index 00000000000..7f6017a3dc9 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertyReflectionUtils.java @@ -0,0 +1,98 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static java.lang.reflect.Modifier.isPublic; + +final class PropertyReflectionUtils { + private PropertyReflectionUtils() {} + + private static final String IS_PREFIX = "is"; + private static final String GET_PREFIX = "get"; + private static final String SET_PREFIX = "set"; + + static boolean isGetter(final Method method) { + if (method.getParameterTypes().length > 0) { + return false; + } else if (method.getName().startsWith(GET_PREFIX) && method.getName().length() > GET_PREFIX.length()) { + return Character.isUpperCase(method.getName().charAt(GET_PREFIX.length())); + } else if (method.getName().startsWith(IS_PREFIX) && method.getName().length() > IS_PREFIX.length()) { + return Character.isUpperCase(method.getName().charAt(IS_PREFIX.length())); + } + return false; + } + + static boolean isSetter(final Method method) { + if (method.getName().startsWith(SET_PREFIX) && method.getName().length() > SET_PREFIX.length() + && method.getParameterTypes().length == 1) { + return Character.isUpperCase(method.getName().charAt(SET_PREFIX.length())); + } + return false; + } + + static String toPropertyName(final Method method) { + String name = method.getName(); + String propertyName = name.substring(name.startsWith(IS_PREFIX) ? 2 : 3, name.length()); + char[] chars = propertyName.toCharArray(); + chars[0] = Character.toLowerCase(chars[0]); + return new String(chars); + } + + static PropertyMethods getPropertyMethods(final Class clazz) { + List setters = new ArrayList(); + List getters = new ArrayList(); + for (Method method : clazz.getDeclaredMethods()) { + // Note that if you override a getter to provide a more specific return type, getting the declared methods + // on the subclass will return the overridden method as well as the method that was overridden from + // the super class. This original method is copied over into the subclass as a bridge method, so we're + // excluding them here to avoid multiple getters of the same property with different return types + if (isPublic(method.getModifiers()) && !method.isBridge()) { + if (isGetter(method)) { + getters.add(method); + } else if (isSetter(method)) { + // Setters are a bit more tricky - don't do anything fancy here + setters.add(method); + } + } + } + + return new PropertyMethods(getters, setters); + } + + static class PropertyMethods { + private final Collection getterMethods; + private final Collection setterMethods; + + PropertyMethods(final Collection getterMethods, final Collection setterMethods) { + this.getterMethods = getterMethods; + this.setterMethods = setterMethods; + } + + Collection getGetterMethods() { + return getterMethods; + } + + Collection getSetterMethods() { + return setterMethods; + } + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/PropertySerialization.java b/bson/src/main/org/bson/codecs/pojo/PropertySerialization.java new file mode 100644 index 00000000000..46e6324131f --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/PropertySerialization.java @@ -0,0 +1,34 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +/** + * An interface allowing a {@link PropertyModel} to determine if a value should be serialized. + * + * @param the type of the property. + * @since 3.5 + */ +public interface PropertySerialization { + + /** + * Determines if a value should be serialized + * + * @param value the value to check + * @return true if the value should be serialized + */ + boolean shouldSerialize(T value); +} diff --git a/bson/src/main/org/bson/codecs/pojo/TypeData.java b/bson/src/main/org/bson/codecs/pojo/TypeData.java new file mode 100644 index 00000000000..6934128b9b3 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/TypeData.java @@ -0,0 +1,241 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; +import static org.bson.assertions.Assertions.notNull; +import static org.bson.codecs.pojo.PropertyReflectionUtils.isGetter; + + +final class TypeData implements TypeWithTypeParameters { + private final Class type; + private final List> typeParameters; + + /** + * Creates a new builder for ClassTypeData + * + * @param type the class for the type + * @param the type + * @return the builder + */ + public static Builder builder(final Class type) { + return new Builder(notNull("type", type)); + } + + public static TypeData newInstance(final Method method) { + if (isGetter(method)) { + return newInstance(method.getGenericReturnType(), method.getReturnType()); + } else { + return newInstance(method.getGenericParameterTypes()[0], method.getParameterTypes()[0]); + } + } + + public static TypeData newInstance(final Field field) { + return newInstance(field.getGenericType(), field.getType()); + } + + public static TypeData newInstance(final Type genericType, final Class clazz) { + TypeData.Builder builder = TypeData.builder(clazz); + if (genericType instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) genericType; + for (Type argType : pType.getActualTypeArguments()) { + getNestedTypeData(builder, argType); + } + } + return builder.build(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static void getNestedTypeData(final TypeData.Builder builder, final Type type) { + if (type instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) type; + TypeData.Builder paramBuilder = TypeData.builder((Class) pType.getRawType()); + for (Type argType : pType.getActualTypeArguments()) { + getNestedTypeData(paramBuilder, argType); + } + builder.addTypeParameter(paramBuilder.build()); + } else if (type instanceof WildcardType) { + builder.addTypeParameter(TypeData.builder((Class) ((WildcardType) type).getUpperBounds()[0]).build()); + } else if (type instanceof TypeVariable) { + builder.addTypeParameter(TypeData.builder(Object.class).build()); + } else if (type instanceof Class) { + builder.addTypeParameter(TypeData.builder((Class) type).build()); + } + } + + /** + * @return the class this {@code ClassTypeData} represents + */ + @Override + public Class getType() { + return type; + } + + /** + * @return the type parameters for the class + */ + @Override + public List> getTypeParameters() { + return typeParameters; + } + + /** + * A builder for TypeData + * + * @param the main type + */ + public static final class Builder { + private final Class type; + private final List> typeParameters = new ArrayList>(); + + private Builder(final Class type) { + this.type = type; + } + + /** + * Adds a type parameter + * + * @param typeParameter the type parameter + * @param the type of the type parameter + * @return this + */ + public Builder addTypeParameter(final TypeData typeParameter) { + typeParameters.add(notNull("typeParameter", typeParameter)); + return this; + } + + /** + * Adds multiple type parameters + * + * @param typeParameters the type parameters + * @return this + */ + public Builder addTypeParameters(final List> typeParameters) { + notNull("typeParameters", typeParameters); + for (TypeData typeParameter : typeParameters) { + addTypeParameter(typeParameter); + } + return this; + } + + /** + * @return the class type data + */ + public TypeData build() { + return new TypeData(type, Collections.unmodifiableList(typeParameters)); + } + } + + @Override + public String toString() { + String typeParams = typeParameters.isEmpty() ? "" + : ", typeParameters=[" + nestedTypeParameters(typeParameters) + "]"; + return "TypeData{" + + "type=" + type.getSimpleName() + + typeParams + + "}"; + } + + private static String nestedTypeParameters(final List> typeParameters) { + StringBuilder builder = new StringBuilder(); + int count = 0; + int last = typeParameters.size(); + for (TypeData typeParameter : typeParameters) { + count++; + builder.append(typeParameter.getType().getSimpleName()); + if (!typeParameter.getTypeParameters().isEmpty()) { + builder.append(format("<%s>", nestedTypeParameters(typeParameter.getTypeParameters()))); + } + if (count < last) { + builder.append(", "); + } + } + return builder.toString(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TypeData)) { + return false; + } + + TypeData that = (TypeData) o; + + if (!getType().equals(that.getType())) { + return false; + } + if (!getTypeParameters().equals(that.getTypeParameters())) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getType().hashCode(); + result = 31 * result + getTypeParameters().hashCode(); + return result; + } + + private TypeData(final Class type, final List> typeParameters) { + this.type = boxType(type); + this.typeParameters = typeParameters; + } + + boolean isAssignableFrom(final Class cls) { + return type.isAssignableFrom(boxType(cls)); + } + + @SuppressWarnings("unchecked") + private Class boxType(final Class clazz) { + if (clazz.isPrimitive()) { + return (Class) PRIMITIVE_CLASS_MAP.get(clazz); + } else { + return clazz; + } + } + + private static final Map, Class> PRIMITIVE_CLASS_MAP; + static { + Map, Class> map = new HashMap, Class>(); + map.put(boolean.class, Boolean.class); + map.put(byte.class, Byte.class); + map.put(char.class, Character.class); + map.put(double.class, Double.class); + map.put(float.class, Float.class); + map.put(int.class, Integer.class); + map.put(long.class, Long.class); + map.put(short.class, Short.class); + PRIMITIVE_CLASS_MAP = map; + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/TypeParameterMap.java b/bson/src/main/org/bson/codecs/pojo/TypeParameterMap.java new file mode 100644 index 00000000000..60cdd8a6eec --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/TypeParameterMap.java @@ -0,0 +1,131 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.unmodifiableMap; + + +/** + * Maps the index of a class's generic parameter type index to a property's. + */ +final class TypeParameterMap { + private final Map propertyToClassParamIndexMap; + + /** + * Creates a new builder for the TypeParameterMap + * + * @return the builder + */ + static Builder builder() { + return new Builder(); + } + + /** + * Returns a mapping of property type parameter index to the class type parameter index. + * + *

    Note: A property index of -1, means the class's parameter type represents the whole property

    + * + * @return a mapping of property type parameter index to the class type parameter index. + */ + Map getPropertyToClassParamIndexMap() { + return propertyToClassParamIndexMap; + } + + boolean hasTypeParameters() { + return !propertyToClassParamIndexMap.isEmpty(); + } + + /** + * A builder for mapping field type parameter indices to the class type parameter indices + */ + static final class Builder { + private final Map propertyToClassParamIndexMap = new HashMap(); + + private Builder() { + } + + /** + * Adds the type parameter index for a class that represents the whole property + * + * @param classTypeParameterIndex the class's type parameter index that represents the whole field + * @return this + */ + Builder addIndex(final int classTypeParameterIndex) { + propertyToClassParamIndexMap.put(-1, classTypeParameterIndex); + return this; + } + + /** + * Adds a mapping that represents the property + * + * @param propertyTypeParameterIndex the property's type parameter index + * @param classTypeParameterIndex the class's type parameter index + * @return this + */ + Builder addIndex(final int propertyTypeParameterIndex, final int classTypeParameterIndex) { + propertyToClassParamIndexMap.put(propertyTypeParameterIndex, classTypeParameterIndex); + return this; + } + + /** + * @return the TypeParameterMap + */ + TypeParameterMap build() { + if (propertyToClassParamIndexMap.size() > 1 && propertyToClassParamIndexMap.containsKey(-1)) { + throw new IllegalStateException("You cannot have a generic field that also has type parameters."); + } + return new TypeParameterMap(propertyToClassParamIndexMap); + } + } + + @Override + public String toString() { + return "TypeParameterMap{" + + "fieldToClassParamIndexMap=" + propertyToClassParamIndexMap + + "}"; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TypeParameterMap that = (TypeParameterMap) o; + + if (!getPropertyToClassParamIndexMap().equals(that.getPropertyToClassParamIndexMap())) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getPropertyToClassParamIndexMap().hashCode(); + } + + private TypeParameterMap(final Map propertyToClassParamIndexMap) { + this.propertyToClassParamIndexMap = unmodifiableMap(propertyToClassParamIndexMap); + } +} diff --git a/bson/src/main/org/bson/codecs/pojo/TypeWithTypeParameters.java b/bson/src/main/org/bson/codecs/pojo/TypeWithTypeParameters.java new file mode 100644 index 00000000000..4d8ae778613 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/TypeWithTypeParameters.java @@ -0,0 +1,36 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo; + +import java.util.List; + +/** + * A combination of a type and its type parameters. + * + * @param the type which potentially has parameterized types + * @since 3.6 + */ +public interface TypeWithTypeParameters { + /** + * @return the class this {@code TypeWithTypeParameters} represents + */ + Class getType(); + + /** + * @return the type parameters for {@link #getType()} + */ + List> getTypeParameters(); +} diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/BsonCreator.java b/bson/src/main/org/bson/codecs/pojo/annotations/BsonCreator.java new file mode 100644 index 00000000000..82441d1aeb1 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/annotations/BsonCreator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that configures a constructor or method as the Creator for the Pojo. + * + *

    Note: Requires the {@link org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION}

    + * + * @since 3.5 + * @see org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface BsonCreator { +} diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/BsonDiscriminator.java b/bson/src/main/org/bson/codecs/pojo/annotations/BsonDiscriminator.java new file mode 100644 index 00000000000..85da49bf0ba --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/annotations/BsonDiscriminator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that configures the discriminator key and value for a class. + * + *

    Note: Requires the {@link org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION}

    + * + * @since 3.5 + * @see org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION + */ +@Inherited +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface BsonDiscriminator { + + /** + * @return the discriminator value to use for this type. + */ + String value() default ""; + + /** + * @return the discriminator key to use for this type. + */ + String key() default "_t"; +} diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/BsonId.java b/bson/src/main/org/bson/codecs/pojo/annotations/BsonId.java new file mode 100644 index 00000000000..af6d4fb4fd9 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/annotations/BsonId.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that configures the property as the id property for a {@link org.bson.codecs.pojo.ClassModel}. + * + *

    Note: Requires the {@link org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION}

    + * + * @since 3.5 + * @see org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +public @interface BsonId { +} diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/BsonIgnore.java b/bson/src/main/org/bson/codecs/pojo/annotations/BsonIgnore.java new file mode 100644 index 00000000000..019526b135e --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/annotations/BsonIgnore.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that configures a property to be ignored when reading and writing to BSON + * + *

    Note: Requires the {@link org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION}

    + * + * @since 3.5 + * @see org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION + */ +@Documented +@Target({ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface BsonIgnore { +} diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/BsonProperty.java b/bson/src/main/org/bson/codecs/pojo/annotations/BsonProperty.java new file mode 100644 index 00000000000..8ce352a16ff --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/annotations/BsonProperty.java @@ -0,0 +1,58 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.annotations; + +import org.bson.codecs.pojo.PropertyModel; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that configures a property. + * + *

    Note: Requires the {@link org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION}

    + * + * @since 3.5 + * @see org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION + */ +@Documented +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface BsonProperty { + /** + * The name of the property. + * + *

    + * For asymmetrical property names, the context of the {@code BsonProperty} can be important. + * For example, when used with {@code @BsonCreator} the value will relate to the read name. + * When used directly on a field it will set both the read name if unset and the write name if unset. + *

    + * + * @return the name to use for the property + * @see PropertyModel#getWriteName() + * @see PropertyModel#getReadName() + */ + String value() default ""; + + /** + * @return whether to include a discriminator when serializing nested Pojos. + */ + boolean useDiscriminator() default false; +} diff --git a/bson/src/main/org/bson/codecs/pojo/annotations/package-info.java b/bson/src/main/org/bson/codecs/pojo/annotations/package-info.java new file mode 100644 index 00000000000..0a5f54f8046 --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/annotations/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package defines various annotations used by the driver provided when used in conjunction with the + * {@link org.bson.codecs.pojo.Conventions#ANNOTATION_CONVENTION}. + */ +package org.bson.codecs.pojo.annotations; diff --git a/bson/src/main/org/bson/codecs/pojo/package-info.java b/bson/src/main/org/bson/codecs/pojo/package-info.java new file mode 100644 index 00000000000..a4a0f6d54ec --- /dev/null +++ b/bson/src/main/org/bson/codecs/pojo/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains classes specific to mapping user POJOs. + */ +package org.bson.codecs.pojo; diff --git a/bson/src/main/org/bson/conversions/Bson.java b/bson/src/main/org/bson/conversions/Bson.java index 21e338b4a71..4b5c22a691a 100644 --- a/bson/src/main/org/bson/conversions/Bson.java +++ b/bson/src/main/org/bson/conversions/Bson.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,5 +35,5 @@ public interface Bson { * @param the type of the document class * @return the BsonDocument */ - BsonDocument toBsonDocument(final Class documentClass, final CodecRegistry codecRegistry); + BsonDocument toBsonDocument(Class documentClass, CodecRegistry codecRegistry); } diff --git a/bson/src/main/org/bson/conversions/package-info.java b/bson/src/main/org/bson/conversions/package-info.java index 917e7e4d3ef..dc8e5866fcd 100644 --- a/bson/src/main/org/bson/conversions/package-info.java +++ b/bson/src/main/org/bson/conversions/package-info.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/bson/src/main/org/bson/diagnostics/JULLogger.java b/bson/src/main/org/bson/diagnostics/JULLogger.java new file mode 100644 index 00000000000..65595112140 --- /dev/null +++ b/bson/src/main/org/bson/diagnostics/JULLogger.java @@ -0,0 +1,128 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.diagnostics; + +import java.util.logging.Level; + +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.FINER; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.SEVERE; +import static java.util.logging.Level.WARNING; + +class JULLogger implements Logger { + + private final java.util.logging.Logger delegate; + + JULLogger(final String name) { + this.delegate = java.util.logging.Logger.getLogger(name); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public boolean isTraceEnabled() { + return isEnabled(FINER); + } + + @Override + public void trace(final String msg) { + log(FINER, msg); + } + + @Override + public void trace(final String msg, final Throwable t) { + log(FINER, msg, t); + } + + @Override + public boolean isDebugEnabled() { + return isEnabled(FINE); + } + + @Override + public void debug(final String msg) { + log(FINE, msg); + } + + @Override + public void debug(final String msg, final Throwable t) { + log(FINE, msg, t); + } + + @Override + public boolean isInfoEnabled() { + return delegate.isLoggable(INFO); + } + + @Override + public void info(final String msg) { + log(INFO, msg); + } + + @Override + public void info(final String msg, final Throwable t) { + log(INFO, msg, t); + } + + @Override + public boolean isWarnEnabled() { + return delegate.isLoggable(WARNING); + } + + @Override + public void warn(final String msg) { + log(WARNING, msg); + } + + @Override + public void warn(final String msg, final Throwable t) { + log(WARNING, msg, t); + } + + + @Override + public boolean isErrorEnabled() { + return delegate.isLoggable(SEVERE); + } + + @Override + public void error(final String msg) { + log(SEVERE, msg); + } + + @Override + public void error(final String msg, final Throwable t) { + log(SEVERE, msg, t); + } + + + private boolean isEnabled(final Level level) { + return delegate.isLoggable(level); + } + + private void log(final Level level, final String msg) { + delegate.log(level, msg); + } + + public void log(final Level level, final String msg, final Throwable t) { + delegate.log(level, msg, t); + } +} diff --git a/bson/src/main/org/bson/diagnostics/Logger.java b/bson/src/main/org/bson/diagnostics/Logger.java new file mode 100644 index 00000000000..2ff055ddb4e --- /dev/null +++ b/bson/src/main/org/bson/diagnostics/Logger.java @@ -0,0 +1,146 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.diagnostics; + +/** + * This class is not part of the public API. It may be removed or changed at any time. + * + */ +public interface Logger { + /** + * Return the name of this Logger instance. + * + * @return name of this logger instance + */ + String getName(); + + /** + * Is the logger instance enabled for the TRACE level? + * + * @return True if this Logger is enabled for the TRACE level, false otherwise. + * @since 1.4 + */ + boolean isTraceEnabled(); + + /** + * Log a message at the TRACE level. + * + * @param msg the message string to be logged + * @since 1.4 + */ + void trace(String msg); + + /** + * Log an exception (throwable) at the TRACE level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + * @since 1.4 + */ + void trace(String msg, Throwable t); + + /** + * Is the logger instance enabled for the DEBUG level? + * + * @return True if this Logger is enabled for the DEBUG level, false otherwise. + */ + boolean isDebugEnabled(); + + + /** + * Log a message at the DEBUG level. + * + * @param msg the message string to be logged + */ + void debug(String msg); + + + /** + * Log an exception (throwable) at the DEBUG level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void debug(String msg, Throwable t); + + /** + * Is the logger instance enabled for the INFO level? + * + * @return True if this Logger is enabled for the INFO level, false otherwise. + */ + boolean isInfoEnabled(); + + + /** + * Log a message at the INFO level. + * + * @param msg the message string to be logged + */ + void info(String msg); + + /** + * Log an exception (throwable) at the INFO level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void info(String msg, Throwable t); + + /** + * Is the logger instance enabled for the WARN level? + * + * @return True if this Logger is enabled for the WARN level, false otherwise. + */ + boolean isWarnEnabled(); + + /** + * Log a message at the WARN level. + * + * @param msg the message string to be logged + */ + void warn(String msg); + + /** + * Log an exception (throwable) at the WARN level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void warn(String msg, Throwable t); + + /** + * Is the logger instance enabled for the ERROR level? + * + * @return True if this Logger is enabled for the ERROR level, false otherwise. + */ + boolean isErrorEnabled(); + + /** + * Log a message at the ERROR level. + * + * @param msg the message string to be logged + */ + void error(String msg); + + /** + * Log an exception (throwable) at the ERROR level with an accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void error(String msg, Throwable t); +} diff --git a/bson/src/main/org/bson/diagnostics/Loggers.java b/bson/src/main/org/bson/diagnostics/Loggers.java index 771318c0e15..ad3bec649ed 100644 --- a/bson/src/main/org/bson/diagnostics/Loggers.java +++ b/bson/src/main/org/bson/diagnostics/Loggers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.bson.diagnostics; -import java.util.logging.Logger; +import static org.bson.assertions.Assertions.notNull; /** * This class is not part of the public API. @@ -29,23 +29,38 @@ public final class Loggers { */ public static final String PREFIX = "org.bson"; + private static final boolean USE_SLF4J = shouldUseSLF4J(); + /** * Gets a logger with the given suffix appended on to {@code PREFIX}, separated by a '.'. * - * @param suffix the suffix for the logger. + * @param suffix the suffix for the logger * @return the logger * @see Loggers#PREFIX */ public static Logger getLogger(final String suffix) { - if (suffix == null) { - throw new IllegalArgumentException("suffix can not be null"); - } + notNull("suffix", suffix); if (suffix.startsWith(".") || suffix.endsWith(".")) { throw new IllegalArgumentException("The suffix can not start or end with a '.'"); } - return Logger.getLogger(PREFIX + "." + suffix); + + String name = PREFIX + "." + suffix; + + if (USE_SLF4J) { + return new SLF4JLogger(name); + } else { + return new JULLogger(name); + } } + private static boolean shouldUseSLF4J() { + try { + Class.forName("org.slf4j.Logger"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } private Loggers() { } } diff --git a/bson/src/main/org/bson/diagnostics/SLF4JLogger.java b/bson/src/main/org/bson/diagnostics/SLF4JLogger.java new file mode 100644 index 00000000000..8a63aa43373 --- /dev/null +++ b/bson/src/main/org/bson/diagnostics/SLF4JLogger.java @@ -0,0 +1,108 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.diagnostics; + +import org.slf4j.LoggerFactory; + +class SLF4JLogger implements Logger { + + private final org.slf4j.Logger delegate; + + SLF4JLogger(final String name) { + this.delegate = LoggerFactory.getLogger(name); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public boolean isTraceEnabled() { + return delegate.isTraceEnabled(); + } + + @Override + public void trace(final String msg) { + delegate.trace(msg); + } + + @Override + public void trace(final String msg, final Throwable t) { + delegate.trace(msg, t); + } + + @Override + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + @Override + public void debug(final String msg) { + delegate.debug(msg); + } + + @Override + public void debug(final String msg, final Throwable t) { + delegate.debug(msg, t); + } + + @Override + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + @Override + public void info(final String msg) { + delegate.info(msg); + } + + @Override + public void info(final String msg, final Throwable t) { + delegate.info(msg, t); + } + + @Override + public boolean isWarnEnabled() { + return delegate.isWarnEnabled(); + } + + @Override + public void warn(final String msg) { + delegate.warn(msg); + } + + @Override + public void warn(final String msg, final Throwable t) { + delegate.warn(msg, t); + } + + @Override + public boolean isErrorEnabled() { + return delegate.isErrorEnabled(); + } + + @Override + public void error(final String msg) { + delegate.error(msg); + } + + @Override + public void error(final String msg, final Throwable t) { + delegate.error(msg, t); + } +} diff --git a/bson/src/main/org/bson/diagnostics/package-info.java b/bson/src/main/org/bson/diagnostics/package-info.java index d59a737dbe0..746722d277c 100644 --- a/bson/src/main/org/bson/diagnostics/package-info.java +++ b/bson/src/main/org/bson/diagnostics/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/internal/Base64.java b/bson/src/main/org/bson/internal/Base64.java new file mode 100644 index 00000000000..d6371f73f44 --- /dev/null +++ b/bson/src/main/org/bson/internal/Base64.java @@ -0,0 +1,151 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal; + +/** + *

    Provides Base64 encoding and decoding.

    + *

    This class implements Base64 encoding

    + *

    Thanks to Apache Commons project. This class refactored from org.apache.commons.codec.binary

    + *

    Original Thanks to commons project in + * ws.apache.org for this code.

    + * + * @since 3.5 + */ +public final class Base64 { + private static final int BYTES_PER_UNENCODED_BLOCK = 3; + private static final int BYTES_PER_ENCODED_BLOCK = 4; + + /** + * Mask used to extract 6 bits, used when encoding + */ + private static final int SIX_BIT_MASK = 0x3f; + + /** + * padding char + */ + private static final byte PAD = '='; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" + * equivalents as specified in Table 1 of RFC 2045. + */ + private static final byte[] ENCODE_TABLE = {'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/'}; + + private static final int[] DECODE_TABLE = new int[128]; + + static { + for (int i = 0; i < ENCODE_TABLE.length; i++) { + DECODE_TABLE[ENCODE_TABLE[i]] = i; + } + } + + /** + * Decodes the given Base64-encoded string. + * + * @param s the Base64-encoded string + * @return the decoded byte array + */ + public static byte[] decode(final String s) { + int delta = s.endsWith("==") ? 2 : s.endsWith("=") ? 1 : 0; + byte[] buffer = new byte[s.length() * BYTES_PER_UNENCODED_BLOCK / BYTES_PER_ENCODED_BLOCK - delta]; + int mask = 0xFF; + int pos = 0; + for (int i = 0; i < s.length(); i += BYTES_PER_ENCODED_BLOCK) { + int c0 = DECODE_TABLE[s.charAt(i)]; + int c1 = DECODE_TABLE[s.charAt(i + 1)]; + buffer[pos++] = (byte) (((c0 << 2) | (c1 >> 4)) & mask); + if (pos >= buffer.length) { + return buffer; + } + int c2 = DECODE_TABLE[s.charAt(i + 2)]; + buffer[pos++] = (byte) (((c1 << 4) | (c2 >> 2)) & mask); + if (pos >= buffer.length) { + return buffer; + } + int c3 = DECODE_TABLE[s.charAt(i + 3)]; + buffer[pos++] = (byte) (((c2 << 6) | c3) & mask); + } + return buffer; + } + + /** + * Encodes the given byte array into a Base64-encoded string. + * + * + * @param in the byte array + * @return the Base64-encoded string + */ + public static String encode(final byte[] in) { + + int modulus = 0; + int bitWorkArea = 0; + int numEncodedBytes = (in.length / BYTES_PER_UNENCODED_BLOCK) * BYTES_PER_ENCODED_BLOCK + + ((in.length % BYTES_PER_UNENCODED_BLOCK == 0) ? 0 : 4); + + byte[] buffer = new byte[numEncodedBytes]; + int pos = 0; + + for (int b : in) { + modulus = (modulus + 1) % BYTES_PER_UNENCODED_BLOCK; + + if (b < 0) { + b += 256; + } + + bitWorkArea = (bitWorkArea << 8) + b; // BITS_PER_BYTE + if (0 == modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract + buffer[pos++] = ENCODE_TABLE[(bitWorkArea >> 18) & SIX_BIT_MASK]; + buffer[pos++] = ENCODE_TABLE[(bitWorkArea >> 12) & SIX_BIT_MASK]; + buffer[pos++] = ENCODE_TABLE[(bitWorkArea >> 6) & SIX_BIT_MASK]; + buffer[pos++] = ENCODE_TABLE[bitWorkArea & SIX_BIT_MASK]; + } + } + + switch (modulus) { // 0-2 + case 1: // 8 bits = 6 + 2 + buffer[pos++] = ENCODE_TABLE[(bitWorkArea >> 2) & SIX_BIT_MASK]; // top 6 bits + buffer[pos++] = ENCODE_TABLE[(bitWorkArea << 4) & SIX_BIT_MASK]; // remaining 2 + buffer[pos++] = PAD; + buffer[pos] = PAD; // Last entry no need to ++ + break; + + case 2: // 16 bits = 6 + 6 + 4 + buffer[pos++] = ENCODE_TABLE[(bitWorkArea >> 10) & SIX_BIT_MASK]; + buffer[pos++] = ENCODE_TABLE[(bitWorkArea >> 4) & SIX_BIT_MASK]; + buffer[pos++] = ENCODE_TABLE[(bitWorkArea << 2) & SIX_BIT_MASK]; + buffer[pos] = PAD; // Last entry no need to ++ + break; + default: + break; + } + + return byteArrayToString(buffer); + } + + @SuppressWarnings("deprecation") + private static String byteArrayToString(final byte[] buffer) { + return new String(buffer, 0, 0, buffer.length); + } + + private Base64() { + } +} diff --git a/bson/src/main/org/bson/codecs/configuration/ChildCodecRegistry.java b/bson/src/main/org/bson/internal/ChildCodecRegistry.java similarity index 91% rename from bson/src/main/org/bson/codecs/configuration/ChildCodecRegistry.java rename to bson/src/main/org/bson/internal/ChildCodecRegistry.java index 8a86fcacb3b..393eb9c6dbc 100644 --- a/bson/src/main/org/bson/codecs/configuration/ChildCodecRegistry.java +++ b/bson/src/main/org/bson/internal/ChildCodecRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,20 @@ * limitations under the License. */ -package org.bson.codecs.configuration; +package org.bson.internal; import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecRegistry; // An implementation of CodecRegistry that is used to detect cyclic dependencies between Codecs class ChildCodecRegistry implements CodecRegistry { private final ChildCodecRegistry parent; - private final ProvidersCodecRegistry registry; + private final CycleDetectingCodecRegistry registry; private final Class codecClass; - ChildCodecRegistry(final ProvidersCodecRegistry registry, final Class codecClass) { + ChildCodecRegistry(final CycleDetectingCodecRegistry registry, final Class codecClass) { this.codecClass = codecClass; this.parent = null; this.registry = registry; diff --git a/bson/src/main/org/bson/codecs/configuration/CodecCache.java b/bson/src/main/org/bson/internal/CodecCache.java similarity index 89% rename from bson/src/main/org/bson/codecs/configuration/CodecCache.java rename to bson/src/main/org/bson/internal/CodecCache.java index 11e52a00051..004a8f13c1b 100644 --- a/bson/src/main/org/bson/codecs/configuration/CodecCache.java +++ b/bson/src/main/org/bson/internal/CodecCache.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,9 +14,10 @@ * limitations under the License. */ -package org.bson.codecs.configuration; +package org.bson.internal; import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecConfigurationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; diff --git a/bson/src/main/org/bson/internal/CodecRegistryHelper.java b/bson/src/main/org/bson/internal/CodecRegistryHelper.java new file mode 100644 index 00000000000..ef8104fcfca --- /dev/null +++ b/bson/src/main/org/bson/internal/CodecRegistryHelper.java @@ -0,0 +1,41 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal; + +import org.bson.UuidRepresentation; +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +public final class CodecRegistryHelper { + + public static CodecRegistry createRegistry(final CodecRegistry codecRegistry, final UuidRepresentation uuidRepresentation) { + CodecRegistry retVal = codecRegistry; + if (uuidRepresentation != UuidRepresentation.JAVA_LEGACY) { + if (codecRegistry instanceof CodecProvider) { + retVal = new OverridableUuidRepresentationCodecRegistry((CodecProvider) codecRegistry, uuidRepresentation); + } else { + throw new CodecConfigurationException("Changing the default UuidRepresentation requires a CodecRegistry that also " + + "implements the CodecProvider interface"); + } + } + return retVal; + } + + private CodecRegistryHelper() { + } +} diff --git a/bson/src/main/org/bson/internal/CycleDetectingCodecRegistry.java b/bson/src/main/org/bson/internal/CycleDetectingCodecRegistry.java new file mode 100644 index 00000000000..2aecba9f188 --- /dev/null +++ b/bson/src/main/org/bson/internal/CycleDetectingCodecRegistry.java @@ -0,0 +1,36 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecRegistry; + +/** + * A marker interface for {@code CodecRegistry} implementations that are able to detect cycles. + * + * @since 3.12 + */ +interface CycleDetectingCodecRegistry extends CodecRegistry { + /** + * Get the Codec using the given context. + * + * @param context the child context + * @param the value type + * @return the Codec + */ + Codec get(ChildCodecRegistry context); +} diff --git a/bson/src/main/org/bson/codecs/configuration/LazyCodec.java b/bson/src/main/org/bson/internal/LazyCodec.java similarity index 89% rename from bson/src/main/org/bson/codecs/configuration/LazyCodec.java rename to bson/src/main/org/bson/internal/LazyCodec.java index 9206f2ba92c..e8c7b67adf9 100644 --- a/bson/src/main/org/bson/codecs/configuration/LazyCodec.java +++ b/bson/src/main/org/bson/internal/LazyCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,21 @@ * limitations under the License. */ -package org.bson.codecs.configuration; +package org.bson.internal; import org.bson.BsonReader; import org.bson.BsonWriter; import org.bson.codecs.Codec; import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecRegistry; class LazyCodec implements Codec { private final CodecRegistry registry; private final Class clazz; private volatile Codec wrapped; - public LazyCodec(final CodecRegistry registry, final Class clazz) { + LazyCodec(final CodecRegistry registry, final Class clazz) { this.registry = registry; this.clazz = clazz; } diff --git a/bson/src/main/org/bson/internal/Optional.java b/bson/src/main/org/bson/internal/Optional.java new file mode 100644 index 00000000000..1e8528e3397 --- /dev/null +++ b/bson/src/main/org/bson/internal/Optional.java @@ -0,0 +1,85 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal; + +import java.util.NoSuchElementException; + + +abstract class Optional { + + private static final Optional NONE = new Optional() { + @Override + public Object get() { + throw new NoSuchElementException(".get call on None!"); + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + @SuppressWarnings("unchecked") + public static Optional empty() { + return (Optional) NONE; + } + + @SuppressWarnings("unchecked") + public static Optional of(final T it) { + if (it == null) { + return (Optional) Optional.NONE; + } else { + return new Some(it); + } + } + + public abstract T get(); + + public abstract boolean isEmpty(); + + @Override + public String toString() { + return "None"; + } + + public boolean isDefined() { + return !isEmpty(); + } + + public static class Some extends Optional { + private final T value; + + Some(final T value) { + this.value = value; + } + + @Override + public T get() { + return value; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return String.format("Some(%s)", value); + } + } +} diff --git a/bson/src/main/org/bson/internal/OverridableUuidRepresentationCodecRegistry.java b/bson/src/main/org/bson/internal/OverridableUuidRepresentationCodecRegistry.java new file mode 100644 index 00000000000..3ce1b2da528 --- /dev/null +++ b/bson/src/main/org/bson/internal/OverridableUuidRepresentationCodecRegistry.java @@ -0,0 +1,86 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal; + +import org.bson.UuidRepresentation; +import org.bson.codecs.Codec; +import org.bson.codecs.OverridableUuidRepresentationCodec; +import org.bson.codecs.configuration.CodecProvider; + +import static org.bson.assertions.Assertions.notNull; + +public class OverridableUuidRepresentationCodecRegistry implements CycleDetectingCodecRegistry { + + private final CodecProvider wrapped; + private final CodecCache codecCache = new CodecCache(); + private final UuidRepresentation uuidRepresentation; + + OverridableUuidRepresentationCodecRegistry(final CodecProvider wrapped, final UuidRepresentation uuidRepresentation) { + this.uuidRepresentation = notNull("uuidRepresentation", uuidRepresentation); + this.wrapped = notNull("wrapped", wrapped); + } + + public UuidRepresentation getUuidRepresentation() { + return uuidRepresentation; + } + + public CodecProvider getWrapped() { + return wrapped; + } + + @Override + public Codec get(final Class clazz) { + return get(new ChildCodecRegistry(this, clazz)); + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public Codec get(final ChildCodecRegistry context) { + if (!codecCache.containsKey(context.getCodecClass())) { + Codec codec = wrapped.get(context.getCodecClass(), context); + if (codec instanceof OverridableUuidRepresentationCodec) { + codec = ((OverridableUuidRepresentationCodec) codec).withUuidRepresentation(uuidRepresentation); + } + codecCache.put(context.getCodecClass(), codec); + } + return codecCache.getOrThrow(context.getCodecClass()); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OverridableUuidRepresentationCodecRegistry that = (OverridableUuidRepresentationCodecRegistry) o; + + if (!wrapped.equals(that.wrapped)) { + return false; + } + return uuidRepresentation == that.uuidRepresentation; + } + + @Override + public int hashCode() { + int result = wrapped.hashCode(); + result = 31 * result + uuidRepresentation.hashCode(); + return result; + } +} diff --git a/bson/src/main/org/bson/codecs/configuration/ProvidersCodecRegistry.java b/bson/src/main/org/bson/internal/ProvidersCodecRegistry.java similarity index 85% rename from bson/src/main/org/bson/codecs/configuration/ProvidersCodecRegistry.java rename to bson/src/main/org/bson/internal/ProvidersCodecRegistry.java index 7c090fb3368..5bb7a6062a3 100644 --- a/bson/src/main/org/bson/codecs/configuration/ProvidersCodecRegistry.java +++ b/bson/src/main/org/bson/internal/ProvidersCodecRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,22 @@ * limitations under the License. */ -package org.bson.codecs.configuration; +package org.bson.internal; import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; import java.util.ArrayList; import java.util.List; import static org.bson.assertions.Assertions.isTrueArgument; -final class ProvidersCodecRegistry implements CodecRegistry, CodecProvider { +public final class ProvidersCodecRegistry implements CodecRegistry, CodecProvider, CycleDetectingCodecRegistry { private final List codecProviders; private final CodecCache codecCache = new CodecCache(); - ProvidersCodecRegistry(final List codecProviders) { + public ProvidersCodecRegistry(final List codecProviders) { isTrueArgument("codecProviders must not be null or empty", codecProviders != null && codecProviders.size() > 0); this.codecProviders = new ArrayList(codecProviders); } @@ -37,7 +39,7 @@ public Codec get(final Class clazz) { return get(new ChildCodecRegistry(this, clazz)); } - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings("rawtypes") public Codec get(final Class clazz, final CodecRegistry registry) { for (CodecProvider provider : codecProviders) { Codec codec = provider.get(clazz, registry); @@ -49,7 +51,7 @@ public Codec get(final Class clazz, final CodecRegistry registry) { } @SuppressWarnings({ "unchecked", "rawtypes" }) - Codec get(final ChildCodecRegistry context) { + public Codec get(final ChildCodecRegistry context) { if (!codecCache.containsKey(context.getCodecClass())) { for (CodecProvider provider : codecProviders) { Codec codec = provider.get(context.getCodecClass(), context); diff --git a/bson/src/main/org/bson/internal/UnsignedLongs.java b/bson/src/main/org/bson/internal/UnsignedLongs.java new file mode 100644 index 00000000000..3980df5b80a --- /dev/null +++ b/bson/src/main/org/bson/internal/UnsignedLongs.java @@ -0,0 +1,183 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2010 The Guava Authors + * Copyright 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal; + +import java.math.BigInteger; + +/** + * Utilities for treating long values as unsigned. + * + *

    + * Similar methods are now available in Java 8, but are required here for Java 6/7 compatibility. + *

    + *

    + * This class is not part of the public API and may be removed or changed at any time. + *

    + */ +public final class UnsignedLongs { + /** + * Equivalent of Long.compareUnsigned in Java 8. + * + * @param first the first value + * @param second the second value + * @return 0 if the values are equal, a value greater than zero if first is greater than second, + * a value less than zero if first is less than second + */ + public static int compare(final long first, final long second) { + return compareLongs(first + Long.MIN_VALUE, second + Long.MIN_VALUE); + } + + /** + * Equivalent to Long.toUnsignedString in Java 8. + * + * @param value the long value to treat as unsigned + * @return the string representation of unsignedLong treated as an unsigned value + */ + public static String toString(final long value) { + if (value >= 0) { + return Long.toString(value); + } else { + // emulate unsigned division and then append the remainder + long quotient = (value >>> 1) / 5; // Unsigned divide by 10 and floor + long remainder = value - quotient * 10; + return Long.toString(quotient) + remainder; + } + } + + // + + /** + * Equivalent to Long.parseUnsignedLong in Java 8. + * + * @param string the string representation of an unsigned long + * @return the unsigned long + */ + public static long parse(final String string) { + if (string.length() == 0) { + throw new NumberFormatException("empty string"); + } + int radix = 10; + int maxSafePos = MAX_SAFE_DIGITS[radix] - 1; + long value = 0; + for (int pos = 0; pos < string.length(); pos++) { + int digit = Character.digit(string.charAt(pos), radix); + if (digit == -1) { + throw new NumberFormatException(string); + } + if (pos > maxSafePos && overflowInParse(value, digit, radix)) { + throw new NumberFormatException("Too large for unsigned long: " + string); + } + value = (value * radix) + digit; + } + + return value; + } + + // Returns true if (current * radix) + digit is a number too large to be represented by an + // unsigned long. This is useful for detecting overflow while parsing a string representation of a + // number. + private static boolean overflowInParse(final long current, final int digit, final int radix) { + if (current >= 0) { + if (current < MAX_VALUE_DIVS[radix]) { + return false; + } + if (current > MAX_VALUE_DIVS[radix]) { + return true; + } + // current == maxValueDivs[radix] + return (digit > MAX_VALUE_MODS[radix]); + } + + // current < 0: high bit is set + return true; + } + + // this is the equivalent of Long.compare in Java 7 + private static int compareLongs(final long x, final long y) { + return (x < y) ? -1 : ((x == y) ? 0 : 1); + } + + // Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit quantities. + private static long divide(final long dividend, final long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return 0; // dividend < divisor + } else { + return 1; // dividend >= divisor + } + } + + // Optimization - use signed division if dividend < 2^63 + if (dividend >= 0) { + return dividend / divisor; + } + + + // Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + // guaranteed to be either exact or one less than the correct value. This follows from fact that + // floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not quite + // trivial. + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return quotient + (compare(rem, divisor) >= 0 ? 1 : 0); + } + + // Returns dividend % divisor, where the dividend and divisor are treated as unsigned 64-bit* quantities. + private static long remainder(final long dividend, final long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return dividend; // dividend < divisor + } else { + return dividend - divisor; // dividend >= divisor + } + } + + // Optimization - use signed modulus if dividend < 2^63 + if (dividend >= 0) { + return dividend % divisor; + } + + + // Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + // guaranteed to be either exact or one less than the correct value. This follows from the fact + // that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not + // quite trivial. + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return rem - (compare(rem, divisor) >= 0 ? divisor : 0); + } + + private static final long MAX_VALUE = -1L; // Equivalent to 2^64 - 1 + private static final long[] MAX_VALUE_DIVS = new long[Character.MAX_RADIX + 1]; + private static final int[] MAX_VALUE_MODS = new int[Character.MAX_RADIX + 1]; + private static final int[] MAX_SAFE_DIGITS = new int[Character.MAX_RADIX + 1]; + + static { + BigInteger overflow = new BigInteger("10000000000000000", 16); + for (int i = Character.MIN_RADIX; i <= Character.MAX_RADIX; i++) { + MAX_VALUE_DIVS[i] = divide(MAX_VALUE, i); + MAX_VALUE_MODS[i] = (int) remainder(MAX_VALUE, i); + MAX_SAFE_DIGITS[i] = overflow.toString(i).length() - 1; + } + } + + private UnsignedLongs() { + } + +} diff --git a/bson/src/main/org/bson/internal/UuidHelper.java b/bson/src/main/org/bson/internal/UuidHelper.java new file mode 100644 index 00000000000..427837420ab --- /dev/null +++ b/bson/src/main/org/bson/internal/UuidHelper.java @@ -0,0 +1,119 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal; + +import org.bson.BSONException; +import org.bson.BsonBinarySubType; +import org.bson.BsonSerializationException; +import org.bson.UuidRepresentation; + +import java.util.UUID; + +/** + * Utilities for encoding and decoding UUID into binary. + */ +public final class UuidHelper { + private static void writeLongToArrayBigEndian(final byte[] bytes, final int offset, final long x) { + bytes[offset + 7] = (byte) (0xFFL & (x)); + bytes[offset + 6] = (byte) (0xFFL & (x >> 8)); + bytes[offset + 5] = (byte) (0xFFL & (x >> 16)); + bytes[offset + 4] = (byte) (0xFFL & (x >> 24)); + bytes[offset + 3] = (byte) (0xFFL & (x >> 32)); + bytes[offset + 2] = (byte) (0xFFL & (x >> 40)); + bytes[offset + 1] = (byte) (0xFFL & (x >> 48)); + bytes[offset] = (byte) (0xFFL & (x >> 56)); + } + + private static long readLongFromArrayBigEndian(final byte[] bytes, final int offset) { + long x = 0; + x |= (0xFFL & bytes[offset + 7]); + x |= (0xFFL & bytes[offset + 6]) << 8; + x |= (0xFFL & bytes[offset + 5]) << 16; + x |= (0xFFL & bytes[offset + 4]) << 24; + x |= (0xFFL & bytes[offset + 3]) << 32; + x |= (0xFFL & bytes[offset + 2]) << 40; + x |= (0xFFL & bytes[offset + 1]) << 48; + x |= (0xFFL & bytes[offset]) << 56; + return x; + } + + // reverse elements in the subarray data[start:start+length] + private static void reverseByteArray(final byte[] data, final int start, final int length) { + for (int left = start, right = start + length - 1; left < right; left++, right--) { + // swap the values at the left and right indices + byte temp = data[left]; + data[left] = data[right]; + data[right] = temp; + } + } + + public static byte[] encodeUuidToBinary(final UUID uuid, final UuidRepresentation uuidRepresentation) { + byte[] binaryData = new byte[16]; + writeLongToArrayBigEndian(binaryData, 0, uuid.getMostSignificantBits()); + writeLongToArrayBigEndian(binaryData, 8, uuid.getLeastSignificantBits()); + switch(uuidRepresentation) { + case C_SHARP_LEGACY: + reverseByteArray(binaryData, 0, 4); + reverseByteArray(binaryData, 4, 2); + reverseByteArray(binaryData, 6, 2); + break; + case JAVA_LEGACY: + reverseByteArray(binaryData, 0, 8); + reverseByteArray(binaryData, 8, 8); + break; + case PYTHON_LEGACY: + case STANDARD: + break; + default: + throw new BSONException("Unexpected UUID representation: " + uuidRepresentation); + } + + return binaryData; + } + + public static UUID decodeBinaryToUuid(final byte[] data, final byte type, final UuidRepresentation uuidRepresentation) { + if (data.length != 16) { + throw new BsonSerializationException(String.format("Expected length to be 16, not %d.", data.length)); + } + + if (type == BsonBinarySubType.UUID_LEGACY.getValue()) { + switch(uuidRepresentation) { + case C_SHARP_LEGACY: + reverseByteArray(data, 0, 4); + reverseByteArray(data, 4, 2); + reverseByteArray(data, 6, 2); + break; + case JAVA_LEGACY: + reverseByteArray(data, 0, 8); + reverseByteArray(data, 8, 8); + break; + case PYTHON_LEGACY: + break; + case STANDARD: + throw new BSONException("Can not decode a subtype 3 (UUID legacy) BSON binary when the decoder is configured to use " + + "the standard UUID representation"); + default: + throw new BSONException("Unexpected UUID representation: " + uuidRepresentation); + } + } + + return new UUID(readLongFromArrayBigEndian(data, 0), readLongFromArrayBigEndian(data, 8)); + } + + private UuidHelper() { + } +} diff --git a/bson/src/main/org/bson/io/BasicOutputBuffer.java b/bson/src/main/org/bson/io/BasicOutputBuffer.java index 622c5f9b218..80acefcb7ec 100644 --- a/bson/src/main/org/bson/io/BasicOutputBuffer.java +++ b/bson/src/main/org/bson/io/BasicOutputBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import java.util.List; import static java.lang.String.format; +import static java.nio.ByteOrder.LITTLE_ENDIAN; /** * A BSON output stream that stores the output in a single, un-pooled byte array. @@ -131,7 +132,7 @@ public void truncateToPosition(final int newPosition) { @Override public List getByteBuffers() { ensureOpen(); - return Arrays.asList(new ByteBufNIO(ByteBuffer.wrap(buffer, 0, position).duplicate())); + return Arrays.asList(new ByteBufNIO(ByteBuffer.wrap(buffer, 0, position).duplicate().order(LITTLE_ENDIAN))); } @Override diff --git a/driver/src/main/org/bson/io/Bits.java b/bson/src/main/org/bson/io/Bits.java similarity index 98% rename from driver/src/main/org/bson/io/Bits.java rename to bson/src/main/org/bson/io/Bits.java index 1cb758fb064..e1bd4997a96 100644 --- a/driver/src/main/org/bson/io/Bits.java +++ b/bson/src/main/org/bson/io/Bits.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,9 @@ /** * Utility class for reading values from an input stream. + * @deprecated there is no replacement for this utility class */ +@Deprecated public class Bits { /** diff --git a/bson/src/main/org/bson/io/BsonInput.java b/bson/src/main/org/bson/io/BsonInput.java index 472674d99e2..c1ea2c5fea5 100644 --- a/bson/src/main/org/bson/io/BsonInput.java +++ b/bson/src/main/org/bson/io/BsonInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -115,12 +115,25 @@ public interface BsonInput extends Closeable { * Marks the current position in the stream. This method obeys the contract as specified in the same method in {@code InputStream}. * * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid + * @deprecated Use {@link #getMark(int)} instead */ + @Deprecated void mark(int readLimit); + /** + * Gets a mark for the current position in the stream. + * + * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid + * @return the mark + * @since 3.7 + */ + BsonInputMark getMark(int readLimit); + /** * Resets the stream to the current mark. This method obeys the contract as specified in the same method in {@code InputStream}. + * @deprecated Prefer {@link #getMark(int)} */ + @Deprecated void reset(); /** diff --git a/bson/src/main/org/bson/io/BsonInputMark.java b/bson/src/main/org/bson/io/BsonInputMark.java new file mode 100644 index 00000000000..fd0b9d8b5cc --- /dev/null +++ b/bson/src/main/org/bson/io/BsonInputMark.java @@ -0,0 +1,31 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.io; + +/** + * Represents a bookmark that can be used to reset a {@link BsonInput} to its state at the time the mark was created. + * + * @see BsonInput#getMark(int) + * + * @since 3.7 + */ +public interface BsonInputMark { + /** + * Reset the {@link BsonInput} to its state at the time the mark was created. + */ + void reset(); +} diff --git a/bson/src/main/org/bson/io/BsonOutput.java b/bson/src/main/org/bson/io/BsonOutput.java index 884b73b333b..b62fe412b1b 100644 --- a/bson/src/main/org/bson/io/BsonOutput.java +++ b/bson/src/main/org/bson/io/BsonOutput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/io/ByteBufferBsonInput.java b/bson/src/main/org/bson/io/ByteBufferBsonInput.java index 23949b4aaea..eca8c313990 100644 --- a/bson/src/main/org/bson/io/ByteBufferBsonInput.java +++ b/bson/src/main/org/bson/io/ByteBufferBsonInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -142,7 +142,10 @@ public String readCString() { private String readString(final int size) { if (size == 2) { byte asciiByte = readByte(); // if only one byte in the string, it must be ascii. - readByte(); // read null terminator + byte nullByte = readByte(); // read null terminator + if (nullByte != 0) { + throw new BsonSerializationException("Found a BSON string that is not null-terminated"); + } if (asciiByte < 0) { return UTF8_CHARSET.newDecoder().replacement(); } @@ -178,12 +181,26 @@ public void skip(final int numBytes) { buffer.position(buffer.position() + numBytes); } + @Deprecated @Override public void mark(final int readLimit) { ensureOpen(); mark = buffer.position(); } + @Override + public BsonInputMark getMark(final int readLimit) { + return new BsonInputMark() { + private int mark = buffer.position(); + @Override + public void reset() { + ensureOpen(); + buffer.position(mark); + } + }; + } + + @Deprecated @Override public void reset() { ensureOpen(); diff --git a/bson/src/main/org/bson/io/OutputBuffer.java b/bson/src/main/org/bson/io/OutputBuffer.java index e46ae076fcf..c733032bad1 100644 --- a/bson/src/main/org/bson/io/OutputBuffer.java +++ b/bson/src/main/org/bson/io/OutputBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -122,7 +122,7 @@ public int size() { * Get a list of byte buffers that are prepared to be read from; in other words, whose position is 0 and whose limit is the number of * bytes that should read.

    Note that the byte buffers may be read-only.

    * - * @return the non-null list of byte buffers. + * @return the non-null list of byte buffers, in LITTLE_ENDIAN order */ public abstract List getByteBuffers(); @@ -171,7 +171,7 @@ public String toString() { * @param position the position, which must be greater than equal to 0 and at least 4 less than the stream size * @param value the value to write. The 24 high-order bits of the value are ignored. */ - protected abstract void write(final int position, final int value); + protected abstract void write(int position, int value); /** * Writes the given long value to the buffer. @@ -187,7 +187,7 @@ private int writeCharacters(final String str, final boolean checkForNullCharacte int len = str.length(); int total = 0; - for (int i = 0; i < len;/*i gets incremented*/) { + for (int i = 0; i < len;) { int c = Character.codePointAt(str, i); if (checkForNullCharacters && c == 0x0) { diff --git a/bson/src/main/org/bson/io/package-info.java b/bson/src/main/org/bson/io/package-info.java index 82a24cc4275..dca8d1a8df1 100644 --- a/bson/src/main/org/bson/io/package-info.java +++ b/bson/src/main/org/bson/io/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/json/Converter.java b/bson/src/main/org/bson/json/Converter.java new file mode 100644 index 00000000000..06342f109f9 --- /dev/null +++ b/bson/src/main/org/bson/json/Converter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +/** + * A converter from a BSON value to JSON. + * + * @param the value type to convert + * @since 3.5 + */ +public interface Converter { + /** + * Convert the given value to JSON using the JSON writer. + * + * @param value the value, which may be null depending on the type + * @param writer the JSON writer + */ + void convert(T value, StrictJsonWriter writer); +} diff --git a/bson/src/main/org/bson/json/DateTimeFormatter.java b/bson/src/main/org/bson/json/DateTimeFormatter.java new file mode 100644 index 00000000000..9188e05bfd2 --- /dev/null +++ b/bson/src/main/org/bson/json/DateTimeFormatter.java @@ -0,0 +1,158 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQuery; +import java.util.Calendar; +import java.util.TimeZone; + +import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME; + +final class DateTimeFormatter { + private static final FormatterImpl FORMATTER_IMPL; + + static { + FormatterImpl dateTimeHelper; + try { + dateTimeHelper = loadDateTimeFormatter("org.bson.json.DateTimeFormatter$Java8DateTimeFormatter"); + } catch (LinkageError e) { + // this is expected if running on a release prior to Java 8: fallback to JAXB. + dateTimeHelper = loadDateTimeFormatter("org.bson.json.DateTimeFormatter$JaxbDateTimeFormatter"); + } + + FORMATTER_IMPL = dateTimeHelper; + } + + private static FormatterImpl loadDateTimeFormatter(final String className) { + try { + return (FormatterImpl) Class.forName(className).getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException e) { + // this is unexpected as it means the class itself is not found + throw new ExceptionInInitializerError(e); + } catch (InstantiationException e) { + // this is unexpected as it means the class can't be instantiated + throw new ExceptionInInitializerError(e); + } catch (IllegalAccessException e) { + // this is unexpected as it means the no-args constructor isn't accessible + throw new ExceptionInInitializerError(e); + } catch (NoSuchMethodException e) { + throw new ExceptionInInitializerError(e); + } catch (InvocationTargetException e) { + throw new ExceptionInInitializerError(e); + } + } + + static long parse(final String dateTimeString) { + return FORMATTER_IMPL.parse(dateTimeString); + } + + static String format(final long dateTime) { + return FORMATTER_IMPL.format(dateTime); + } + + private interface FormatterImpl { + long parse(String dateTimeString); + String format(long dateTime); + } + + // Reflective use of DatatypeConverter avoids a compile-time dependency on the java.xml.bind module in Java 9 + static class JaxbDateTimeFormatter implements FormatterImpl { + + private static final Method DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD; + private static final Method DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD; + + static { + try { + DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD = Class.forName("javax.xml.bind.DatatypeConverter") + .getDeclaredMethod("parseDateTime", String.class); + DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD = Class.forName("javax.xml.bind.DatatypeConverter") + .getDeclaredMethod("printDateTime", Calendar.class); + } catch (NoSuchMethodException e) { + throw new ExceptionInInitializerError(e); + } catch (ClassNotFoundException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public long parse(final String dateTimeString) { + try { + return ((Calendar) DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD.invoke(null, dateTimeString)).getTimeInMillis(); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } catch (InvocationTargetException e) { + throw (RuntimeException) e.getCause(); + } + } + + @Override + public String format(final long dateTime) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(dateTime); + calendar.setTimeZone(TimeZone.getTimeZone("Z")); + try { + return (String) DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD.invoke(null, calendar); + } catch (IllegalAccessException e) { + throw new IllegalStateException(); + } catch (InvocationTargetException e) { + throw (RuntimeException) e.getCause(); + } + } + } + + static class Java8DateTimeFormatter implements FormatterImpl { + + // if running on Java 8 or above then java.time.format.DateTimeFormatter will be available and initialization will succeed. + // Otherwise it will fail. + static { + try { + Class.forName("java.time.format.DateTimeFormatter"); + } catch (ClassNotFoundException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public long parse(final String dateTimeString) { + try { + return ISO_OFFSET_DATE_TIME.parse(dateTimeString, new TemporalQuery() { + @Override + public Instant queryFrom(final TemporalAccessor temporal) { + return Instant.from(temporal); + } + }).toEpochMilli(); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + @Override + public String format(final long dateTime) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateTime), ZoneId.of("Z")).format(ISO_OFFSET_DATE_TIME); + } + } + + private DateTimeFormatter() { + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonBinaryConverter.java b/bson/src/main/org/bson/json/ExtendedJsonBinaryConverter.java new file mode 100644 index 00000000000..4deaf55d333 --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonBinaryConverter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonBinary; +import org.bson.internal.Base64; + +class ExtendedJsonBinaryConverter implements Converter { + + @Override + public void convert(final BsonBinary value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeStartObject("$binary"); + writer.writeString("base64", Base64.encode(value.getData())); + writer.writeString("subType", String.format("%02X", value.getType())); + writer.writeEndObject(); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonDateTimeConverter.java b/bson/src/main/org/bson/json/ExtendedJsonDateTimeConverter.java new file mode 100644 index 00000000000..05a0152e68d --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonDateTimeConverter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class ExtendedJsonDateTimeConverter implements Converter { + @Override + public void convert(final Long value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeStartObject("$date"); + writer.writeString("$numberLong", Long.toString(value)); + writer.writeEndObject(); + writer.writeEndObject(); + } + +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonDecimal128Converter.java b/bson/src/main/org/bson/json/ExtendedJsonDecimal128Converter.java new file mode 100644 index 00000000000..f42d701d705 --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonDecimal128Converter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.types.Decimal128; + +class ExtendedJsonDecimal128Converter implements Converter { + @Override + public void convert(final Decimal128 value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeName("$numberDecimal"); + writer.writeString(value.toString()); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonDoubleConverter.java b/bson/src/main/org/bson/json/ExtendedJsonDoubleConverter.java new file mode 100644 index 00000000000..1ad0db0ec1b --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonDoubleConverter.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class ExtendedJsonDoubleConverter implements Converter { + @Override + public void convert(final Double value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeName("$numberDouble"); + writer.writeString(Double.toString(value)); + writer.writeEndObject(); + + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonInt32Converter.java b/bson/src/main/org/bson/json/ExtendedJsonInt32Converter.java new file mode 100644 index 00000000000..64f30294fcd --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonInt32Converter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class ExtendedJsonInt32Converter implements Converter { + @Override + public void convert(final Integer value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeName("$numberInt"); + writer.writeString(Integer.toString(value)); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonInt64Converter.java b/bson/src/main/org/bson/json/ExtendedJsonInt64Converter.java new file mode 100644 index 00000000000..ce0c1b959a0 --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonInt64Converter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class ExtendedJsonInt64Converter implements Converter { + @Override + public void convert(final Long value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeName("$numberLong"); + writer.writeString(Long.toString(value)); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonMaxKeyConverter.java b/bson/src/main/org/bson/json/ExtendedJsonMaxKeyConverter.java new file mode 100644 index 00000000000..c5ce321b308 --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonMaxKeyConverter.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonMaxKey; + +class ExtendedJsonMaxKeyConverter implements Converter { + @Override + public void convert(final BsonMaxKey value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeNumber("$maxKey", "1"); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonMinKeyConverter.java b/bson/src/main/org/bson/json/ExtendedJsonMinKeyConverter.java new file mode 100644 index 00000000000..f21f69309ed --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonMinKeyConverter.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonMinKey; + +class ExtendedJsonMinKeyConverter implements Converter { + @Override + public void convert(final BsonMinKey value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeNumber("$minKey", "1"); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonObjectIdConverter.java b/bson/src/main/org/bson/json/ExtendedJsonObjectIdConverter.java new file mode 100644 index 00000000000..be0ed8af348 --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonObjectIdConverter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.types.ObjectId; + +class ExtendedJsonObjectIdConverter implements Converter { + + @Override + public void convert(final ObjectId value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeString("$oid", value.toHexString()); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonRegularExpressionConverter.java b/bson/src/main/org/bson/json/ExtendedJsonRegularExpressionConverter.java new file mode 100644 index 00000000000..588f46a83e0 --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonRegularExpressionConverter.java @@ -0,0 +1,31 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonRegularExpression; + +class ExtendedJsonRegularExpressionConverter implements Converter { + @Override + public void convert(final BsonRegularExpression value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeStartObject("$regularExpression"); + writer.writeString("pattern", value.getPattern()); + writer.writeString("options", value.getOptions()); + writer.writeEndObject(); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonTimestampConverter.java b/bson/src/main/org/bson/json/ExtendedJsonTimestampConverter.java new file mode 100644 index 00000000000..e82549abc5d --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonTimestampConverter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonTimestamp; +import org.bson.internal.UnsignedLongs; + +class ExtendedJsonTimestampConverter implements Converter { + @Override + public void convert(final BsonTimestamp value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeStartObject("$timestamp"); + writer.writeNumber("t", UnsignedLongs.toString(toUnsignedLong(value.getTime()))); + writer.writeNumber("i", UnsignedLongs.toString(toUnsignedLong(value.getInc()))); + writer.writeEndObject(); + writer.writeEndObject(); + } + + // Equivalent to Integer.toUnsignedLong() in Java 8 + private long toUnsignedLong(final int value) { + return ((long) value) & 0xffffffffL; + } +} diff --git a/bson/src/main/org/bson/json/ExtendedJsonUndefinedConverter.java b/bson/src/main/org/bson/json/ExtendedJsonUndefinedConverter.java new file mode 100644 index 00000000000..196e21e816a --- /dev/null +++ b/bson/src/main/org/bson/json/ExtendedJsonUndefinedConverter.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonUndefined; + +class ExtendedJsonUndefinedConverter implements Converter { + @Override + public void convert(final BsonUndefined value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeBoolean("$undefined", true); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/JsonBooleanConverter.java b/bson/src/main/org/bson/json/JsonBooleanConverter.java new file mode 100644 index 00000000000..9d97d7b7969 --- /dev/null +++ b/bson/src/main/org/bson/json/JsonBooleanConverter.java @@ -0,0 +1,24 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class JsonBooleanConverter implements Converter { + @Override + public void convert(final Boolean value, final StrictJsonWriter writer) { + writer.writeBoolean(value); + } +} diff --git a/bson/src/main/org/bson/json/JsonBuffer.java b/bson/src/main/org/bson/json/JsonBuffer.java index ca88ff55e8d..2db6c116238 100644 --- a/bson/src/main/org/bson/json/JsonBuffer.java +++ b/bson/src/main/org/bson/json/JsonBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,47 +16,17 @@ package org.bson.json; -class JsonBuffer { - - private final String buffer; - private int position; - private boolean eof; - - public JsonBuffer(final String buffer) { - this.buffer = buffer; - } - - public int getPosition() { - return position; - } - - public void setPosition(final int position) { - this.position = position; - } - - public int read() { - if (eof) { - throw new JsonParseException("Trying to read past EOF."); - } else if (position >= buffer.length()) { - eof = true; - return -1; - } else { - return buffer.charAt(position++); - } - } - - public void unread(final int c) { - eof = false; - if (c != -1 && buffer.charAt(position - 1) == c) { - position--; - } - } - - public String substring(final int beginIndex) { - return buffer.substring(beginIndex); - } - - public String substring(final int beginIndex, final int endIndex) { - return buffer.substring(beginIndex, endIndex); - } +interface JsonBuffer { + + int getPosition(); + + int read(); + + void unread(int c); + + int mark(); + + void reset(int markPos); + + void discard(int markPos); } diff --git a/bson/src/main/org/bson/json/JsonDoubleConverter.java b/bson/src/main/org/bson/json/JsonDoubleConverter.java new file mode 100644 index 00000000000..26b46ab89d5 --- /dev/null +++ b/bson/src/main/org/bson/json/JsonDoubleConverter.java @@ -0,0 +1,24 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class JsonDoubleConverter implements Converter { + @Override + public void convert(final Double value, final StrictJsonWriter writer) { + writer.writeNumber(Double.toString(value)); + } +} diff --git a/bson/src/main/org/bson/json/JsonInt32Converter.java b/bson/src/main/org/bson/json/JsonInt32Converter.java new file mode 100644 index 00000000000..fc555e421f6 --- /dev/null +++ b/bson/src/main/org/bson/json/JsonInt32Converter.java @@ -0,0 +1,24 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class JsonInt32Converter implements Converter { + @Override + public void convert(final Integer value, final StrictJsonWriter writer) { + writer.writeNumber(Integer.toString(value)); + } +} diff --git a/bson/src/main/org/bson/json/JsonJavaScriptConverter.java b/bson/src/main/org/bson/json/JsonJavaScriptConverter.java new file mode 100644 index 00000000000..437b8b09f95 --- /dev/null +++ b/bson/src/main/org/bson/json/JsonJavaScriptConverter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class JsonJavaScriptConverter implements Converter { + @Override + public void convert(final String value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeString("$code", value); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/JsonMode.java b/bson/src/main/org/bson/json/JsonMode.java index dfd1286ce2c..cbe48b32fde 100644 --- a/bson/src/main/org/bson/json/JsonMode.java +++ b/bson/src/main/org/bson/json/JsonMode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,18 +21,37 @@ * * @see JsonWriter * @since 3.0 - * @mongodb.driver.manual reference/mongodb-extended-json/ MongoDB Extended JSON */ public enum JsonMode { /** * Strict mode representations of BSON types conform to the JSON RFC spec. + * + * @deprecated The format generated with this mode is no longer considered standard for MongoDB tools. This value is not currently + * scheduled for removal. */ + @Deprecated // NOT CURRENTLY INTENDED FOR REMOVAL STRICT, /** * While not formally documented, this output mode will attempt to produce output that corresponds to what the MongoDB shell actually * produces when showing query results. */ - SHELL + SHELL, + + /** + * Standard extended JSON representation. + * + * @since 3.5 + * @see Extended JSON Specification + */ + EXTENDED, + + /** + * Standard relaxed extended JSON representation. + * + * @since 3.5 + * @see Extended JSON Specification + */ + RELAXED } diff --git a/bson/src/main/org/bson/json/JsonNullConverter.java b/bson/src/main/org/bson/json/JsonNullConverter.java new file mode 100644 index 00000000000..b5208018c4e --- /dev/null +++ b/bson/src/main/org/bson/json/JsonNullConverter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonNull; + +class JsonNullConverter implements Converter { + @Override + public void convert(final BsonNull value, final StrictJsonWriter writer) { + writer.writeNull(); + } +} diff --git a/bson/src/main/org/bson/json/JsonParseException.java b/bson/src/main/org/bson/json/JsonParseException.java index 08ea07e7a17..00dcc172f98 100644 --- a/bson/src/main/org/bson/json/JsonParseException.java +++ b/bson/src/main/org/bson/json/JsonParseException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/json/JsonReader.java b/bson/src/main/org/bson/json/JsonReader.java index ad423cc912b..01f4124c684 100644 --- a/bson/src/main/org/bson/json/JsonReader.java +++ b/bson/src/main/org/bson/json/JsonReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,15 +24,18 @@ import org.bson.BsonContextType; import org.bson.BsonDbPointer; import org.bson.BsonInvalidOperationException; +import org.bson.BsonReaderMark; import org.bson.BsonRegularExpression; import org.bson.BsonTimestamp; import org.bson.BsonType; import org.bson.BsonUndefined; +import org.bson.internal.Base64; +import org.bson.types.Decimal128; import org.bson.types.MaxKey; import org.bson.types.MinKey; import org.bson.types.ObjectId; -import javax.xml.bind.DatatypeConverter; +import java.io.Reader; import java.text.DateFormat; import java.text.ParsePosition; import java.text.SimpleDateFormat; @@ -41,6 +44,8 @@ import java.util.Locale; import java.util.TimeZone; +import static java.lang.String.format; + /** * Reads a JSON in one of the following modes: @@ -65,13 +70,31 @@ public class JsonReader extends AbstractBsonReader { private Mark mark; /** - * Constructs a new instance with the given JSON string. + * Constructs a new instance with the given string positioned at a JSON object. * - * @param json A string representation of a JSON. + * @param json A string representation of a JSON object. */ public JsonReader(final String json) { + this(new JsonScanner(json)); + } + + /** + * Constructs a new instance with the given {@code Reader} positioned at a JSON object. + * + *

    + * The application is responsible for closing the {@code Reader}. + *

    + * + * @param reader A reader representation of a JSON object. + * @since 3.11 + */ + public JsonReader(final Reader reader) { + this(new JsonScanner(reader)); + } + + private JsonReader(final JsonScanner scanner) { super(); - scanner = new JsonScanner(json); + this.scanner = scanner; setContext(new Context(null, BsonContextType.TOP_LEVEL)); } @@ -82,7 +105,12 @@ protected BsonBinary doReadBinaryData() { @Override protected byte doPeekBinarySubType() { - return doReadBinaryData().getType(); + return doReadBinaryData().getType(); + } + + @Override + protected int doPeekBinarySize() { + return doReadBinaryData().getData().length; } @Override @@ -177,6 +205,14 @@ public BsonType readBsonType() { setCurrentBsonType(BsonType.NULL); } else if ("undefined".equals(value)) { setCurrentBsonType(BsonType.UNDEFINED); + } else if ("MinKey".equals(value)) { + visitEmptyConstructor(); + setCurrentBsonType(BsonType.MIN_KEY); + currentValue = new MinKey(); + } else if ("MaxKey".equals(value)) { + visitEmptyConstructor(); + setCurrentBsonType(BsonType.MAX_KEY); + currentValue = new MaxKey(); } else if ("BinData".equals(value)) { setCurrentBsonType(BsonType.BINARY); currentValue = visitBinDataConstructor(); @@ -189,9 +225,15 @@ public BsonType readBsonType() { } else if ("ISODate".equals(value)) { setCurrentBsonType(BsonType.DATE_TIME); currentValue = visitISODateTimeConstructor(); + } else if ("NumberInt".equals(value)) { + setCurrentBsonType(BsonType.INT32); + currentValue = visitNumberIntConstructor(); } else if ("NumberLong".equals(value)) { setCurrentBsonType(BsonType.INT64); currentValue = visitNumberLongConstructor(); + } else if ("NumberDecimal".equals(value)) { + setCurrentBsonType(BsonType.DECIMAL128); + currentValue = visitNumberDecimalConstructor(); } else if ("ObjectId".equals(value)) { setCurrentBsonType(BsonType.OBJECT_ID); currentValue = visitObjectIdConstructor(); @@ -251,6 +293,11 @@ public BsonType readBsonType() { } //CHECKSTYLE:ON + @Override + public Decimal128 doReadDecimal128() { + return (Decimal128) currentValue; + } + @Override protected long doReadDateTime() { return (Long) currentValue; @@ -273,26 +320,12 @@ protected void doReadEndArray() { } } - private void setStateOnEnd() { - switch (getContext().getContextType()) { - case ARRAY: - case DOCUMENT: - setState(State.TYPE); - break; - case TOP_LEVEL: - setState(State.DONE); - break; - default: - throw new JsonParseException("Unexpected ContextType %s.", getContext().getContextType()); - } - } - @Override protected void doReadEndDocument() { setContext(getContext().getParentContext()); if (getContext() != null && getContext().getContextType() == BsonContextType.SCOPE_DOCUMENT) { setContext(getContext().getParentContext()); // JavaScriptWithScope - verifyToken("}"); // outermost closing bracket for JavaScriptWithScope + verifyToken(JsonTokenType.END_OBJECT); // outermost closing bracket for JavaScriptWithScope } if (getContext() == null) { @@ -423,6 +456,9 @@ protected void doSkipValue() { case INT64: readInt64(); break; + case DECIMAL128: + readDecimal128(); + break; case JAVASCRIPT: readJavaScript(); break; @@ -484,13 +520,20 @@ private void pushToken(final JsonToken token) { } } - private void verifyToken(final Object expected) { - if (expected == null) { - throw new IllegalArgumentException("Can't be null"); + private void verifyToken(final JsonTokenType expectedType) { + JsonToken token = popToken(); + if (expectedType != token.getType()) { + throw new JsonParseException("JSON reader expected token type '%s' but found '%s'.", expectedType, token.getValue()); } + } + + private void verifyToken(final JsonTokenType expectedType, final Object expectedValue) { JsonToken token = popToken(); - if (!expected.equals(token.getValue())) { - throw new JsonParseException("JSON reader expected '%s' but found '%s'.", expected, token.getValue()); + if (expectedType != token.getType()) { + throw new JsonParseException("JSON reader expected token type '%s' but found '%s'.", expectedType, token.getValue()); + } + if (!expectedValue.equals(token.getValue())) { + throw new JsonParseException("JSON reader expected '%s' but found '%s'.", expectedValue, token.getValue()); } } @@ -502,7 +545,7 @@ private void verifyString(final String expected) { JsonToken token = popToken(); JsonTokenType type = token.getType(); - if ((type != JsonTokenType.STRING && type != JsonTokenType.UNQUOTED_STRING) && !expected.equals(token.getValue())) { + if ((type != JsonTokenType.STRING && type != JsonTokenType.UNQUOTED_STRING) || !expected.equals(token.getValue())) { throw new JsonParseException("JSON reader expected '%s' but found '%s'.", expected, token.getValue()); } } @@ -515,7 +558,15 @@ private void visitNew() { String value = typeToken.getValue(String.class); - if ("BinData".equals(value)) { + if ("MinKey".equals(value)) { + visitEmptyConstructor(); + setCurrentBsonType(BsonType.MIN_KEY); + currentValue = new MinKey(); + } else if ("MaxKey".equals(value)) { + visitEmptyConstructor(); + setCurrentBsonType(BsonType.MAX_KEY); + currentValue = new MaxKey(); + } else if ("BinData".equals(value)) { currentValue = visitBinDataConstructor(); setCurrentBsonType(BsonType.BINARY); } else if ("Date".equals(value)) { @@ -527,9 +578,15 @@ private void visitNew() { } else if ("ISODate".equals(value)) { currentValue = visitISODateTimeConstructor(); setCurrentBsonType(BsonType.DATE_TIME); + } else if ("NumberInt".equals(value)) { + currentValue = visitNumberIntConstructor(); + setCurrentBsonType(BsonType.INT32); } else if ("NumberLong".equals(value)) { currentValue = visitNumberLongConstructor(); setCurrentBsonType(BsonType.INT64); + } else if ("NumberDecimal".equals(value)) { + currentValue = visitNumberDecimalConstructor(); + setCurrentBsonType(BsonType.DECIMAL128); } else if ("ObjectId".equals(value)) { currentValue = visitObjectIdConstructor(); setCurrentBsonType(BsonType.OBJECT_ID); @@ -560,10 +617,19 @@ private void visitExtendedJSON() { JsonTokenType type = nameToken.getType(); if (type == JsonTokenType.STRING || type == JsonTokenType.UNQUOTED_STRING) { - if ("$binary".equals(value)) { - currentValue = visitBinDataExtendedJson(); - setCurrentBsonType(BsonType.BINARY); - return; + + if ("$binary".equals(value) || "$type".equals(value)) { + currentValue = visitBinDataExtendedJson(value); + if (currentValue != null) { + setCurrentBsonType(BsonType.BINARY); + return; + } + } else if ("$regex".equals(value) || "$options".equals(value)) { + currentValue = visitRegularExpressionExtendedJson(value); + if (currentValue != null) { + setCurrentBsonType(BsonType.REGULAR_EXPRESSION); + return; + } } else if ("$code".equals(value)) { visitJavaScriptExtendedJson(); return; @@ -583,8 +649,8 @@ private void visitExtendedJSON() { currentValue = visitObjectIdExtendedJson(); setCurrentBsonType(BsonType.OBJECT_ID); return; - } else if ("$regex".equals(value)) { - currentValue = visitRegularExpressionExtendedJson(); + } else if ("$regularExpression".equals(value)) { + currentValue = visitNewRegularExpressionExtendedJson(); setCurrentBsonType(BsonType.REGULAR_EXPRESSION); return; } else if ("$symbol".equals(value)) { @@ -603,39 +669,60 @@ private void visitExtendedJSON() { currentValue = visitNumberLongExtendedJson(); setCurrentBsonType(BsonType.INT64); return; + } else if ("$numberInt".equals(value)) { + currentValue = visitNumberIntExtendedJson(); + setCurrentBsonType(BsonType.INT32); + return; + } else if ("$numberDouble".equals(value)) { + currentValue = visitNumberDoubleExtendedJson(); + setCurrentBsonType(BsonType.DOUBLE); + return; + } else if ("$numberDecimal".equals(value)) { + currentValue = visitNumberDecimalExtendedJson(); + setCurrentBsonType(BsonType.DECIMAL128); + return; + } else if ("$dbPointer".equals(value)) { + currentValue = visitDbPointerExtendedJson(); + setCurrentBsonType(BsonType.DB_POINTER); + return; } } + pushToken(nameToken); setCurrentBsonType(BsonType.DOCUMENT); } + private void visitEmptyConstructor() { + JsonToken nextToken = popToken(); + if (nextToken.getType() == JsonTokenType.LEFT_PAREN) { + verifyToken(JsonTokenType.RIGHT_PAREN); + } else { + pushToken(nextToken); + } + } + private BsonBinary visitBinDataConstructor() { - verifyToken("("); + verifyToken(JsonTokenType.LEFT_PAREN); JsonToken subTypeToken = popToken(); if (subTypeToken.getType() != JsonTokenType.INT32) { throw new JsonParseException("JSON reader expected a binary subtype but found '%s'.", subTypeToken.getValue()); } - verifyToken(","); + verifyToken(JsonTokenType.COMMA); JsonToken bytesToken = popToken(); - if (bytesToken.getType() != JsonTokenType.UNQUOTED_STRING) { + if (bytesToken.getType() != JsonTokenType.UNQUOTED_STRING && bytesToken.getType() != JsonTokenType.STRING) { throw new JsonParseException("JSON reader expected a string but found '%s'.", bytesToken.getValue()); } - verifyToken(")"); + verifyToken(JsonTokenType.RIGHT_PAREN); - byte[] bytes = DatatypeConverter.parseBase64Binary(bytesToken.getValue(String.class)); + byte[] bytes = Base64.decode(bytesToken.getValue(String.class)); return new BsonBinary(subTypeToken.getValue(Integer.class).byteValue(), bytes); } private BsonBinary visitUUIDConstructor(final String uuidConstructorName) { - //TODO verify information related to https://jira.mongodb.org/browse/SERVER-3168 - verifyToken("("); - JsonToken bytesToken = popToken(); - if (bytesToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", bytesToken.getValue()); - } - verifyToken(")"); - String hexString = bytesToken.getValue(String.class).replaceAll("\\{", "").replaceAll("\\}", "").replaceAll("-", ""); - byte[] bytes = DatatypeConverter.parseHexBinary(hexString); + verifyToken(JsonTokenType.LEFT_PAREN); + String hexString = readStringFromExtendedJson().replaceAll("\\{", "").replaceAll("}", "").replaceAll("-", ""); + verifyToken(JsonTokenType.RIGHT_PAREN); + byte[] bytes = decodeHex(hexString); BsonBinarySubType subType = BsonBinarySubType.UUID_STANDARD; if (!"UUID".equals(uuidConstructorName) || !"GUID".equals(uuidConstructorName)) { subType = BsonBinarySubType.UUID_LEGACY; @@ -644,38 +731,28 @@ private BsonBinary visitUUIDConstructor(final String uuidConstructorName) { } private BsonRegularExpression visitRegularExpressionConstructor() { - verifyToken("("); - JsonToken patternToken = popToken(); - if (patternToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", patternToken.getValue()); - } + verifyToken(JsonTokenType.LEFT_PAREN); + String pattern = readStringFromExtendedJson(); String options = ""; JsonToken commaToken = popToken(); if (commaToken.getType() == JsonTokenType.COMMA) { - JsonToken optionsToken = popToken(); - if (optionsToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", optionsToken.getValue()); - } - options = optionsToken.getValue(String.class); + options = readStringFromExtendedJson(); } else { pushToken(commaToken); } - verifyToken(")"); - return new BsonRegularExpression(patternToken.getValue(String.class), options); + verifyToken(JsonTokenType.RIGHT_PAREN); + return new BsonRegularExpression(pattern, options); } private ObjectId visitObjectIdConstructor() { - verifyToken("("); - JsonToken valueToken = popToken(); - if (valueToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", valueToken.getValue()); - } - verifyToken(")"); - return new ObjectId(valueToken.getValue(String.class)); + verifyToken(JsonTokenType.LEFT_PAREN); + ObjectId objectId = new ObjectId(readStringFromExtendedJson()); + verifyToken(JsonTokenType.RIGHT_PAREN); + return objectId; } private BsonTimestamp visitTimestampConstructor() { - verifyToken("("); + verifyToken(JsonTokenType.LEFT_PAREN); JsonToken timeToken = popToken(); int time; if (timeToken.getType() != JsonTokenType.INT32) { @@ -683,7 +760,7 @@ private BsonTimestamp visitTimestampConstructor() { } else { time = timeToken.getValue(Integer.class); } - verifyToken(","); + verifyToken(JsonTokenType.COMMA); JsonToken incrementToken = popToken(); int increment; if (incrementToken.getType() != JsonTokenType.INT32) { @@ -692,27 +769,36 @@ private BsonTimestamp visitTimestampConstructor() { increment = incrementToken.getValue(Integer.class); } - verifyToken(")"); + verifyToken(JsonTokenType.RIGHT_PAREN); return new BsonTimestamp(time, increment); } private BsonDbPointer visitDBPointerConstructor() { - verifyToken("("); - JsonToken namespaceToken = popToken(); - if (namespaceToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", namespaceToken.getValue()); - } - verifyToken(","); - JsonToken idToken = popToken(); - if (namespaceToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", idToken.getValue()); + verifyToken(JsonTokenType.LEFT_PAREN); + String namespace = readStringFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + ObjectId id = new ObjectId(readStringFromExtendedJson()); + verifyToken(JsonTokenType.RIGHT_PAREN); + return new BsonDbPointer(namespace, id); + } + + private int visitNumberIntConstructor() { + verifyToken(JsonTokenType.LEFT_PAREN); + JsonToken valueToken = popToken(); + int value; + if (valueToken.getType() == JsonTokenType.INT32) { + value = valueToken.getValue(Integer.class); + } else if (valueToken.getType() == JsonTokenType.STRING) { + value = Integer.parseInt(valueToken.getValue(String.class)); + } else { + throw new JsonParseException("JSON reader expected an integer or a string but found '%s'.", valueToken.getValue()); } - verifyToken(")"); - return new BsonDbPointer(namespaceToken.getValue(String.class), new ObjectId(idToken.getValue(String.class))); + verifyToken(JsonTokenType.RIGHT_PAREN); + return value; } private long visitNumberLongConstructor() { - verifyToken("("); + verifyToken(JsonTokenType.LEFT_PAREN); JsonToken valueToken = popToken(); long value; if (valueToken.getType() == JsonTokenType.INT32 || valueToken.getType() == JsonTokenType.INT64) { @@ -722,12 +808,28 @@ private long visitNumberLongConstructor() { } else { throw new JsonParseException("JSON reader expected an integer or a string but found '%s'.", valueToken.getValue()); } - verifyToken(")"); + verifyToken(JsonTokenType.RIGHT_PAREN); + return value; + } + + private Decimal128 visitNumberDecimalConstructor() { + verifyToken(JsonTokenType.LEFT_PAREN); + JsonToken valueToken = popToken(); + Decimal128 value; + if (valueToken.getType() == JsonTokenType.INT32 || valueToken.getType() == JsonTokenType.INT64 + || valueToken.getType() == JsonTokenType.DOUBLE) { + value = valueToken.getValue(Decimal128.class); + } else if (valueToken.getType() == JsonTokenType.STRING) { + value = Decimal128.parse(valueToken.getValue(String.class)); + } else { + throw new JsonParseException("JSON reader expected a number or a string but found '%s'.", valueToken.getValue()); + } + verifyToken(JsonTokenType.RIGHT_PAREN); return value; } private long visitISODateTimeConstructor() { - verifyToken("("); + verifyToken(JsonTokenType.LEFT_PAREN); JsonToken token = popToken(); if (token.getType() == JsonTokenType.RIGHT_PAREN) { @@ -736,7 +838,7 @@ private long visitISODateTimeConstructor() { throw new JsonParseException("JSON reader expected a string but found '%s'.", token.getValue()); } - verifyToken(")"); + verifyToken(JsonTokenType.RIGHT_PAREN); String[] patterns = {"yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm:ssz", "yyyy-MM-dd'T'HH:mm:ss.SSSz"}; SimpleDateFormat format = new SimpleDateFormat(patterns[0], Locale.ENGLISH); @@ -762,41 +864,37 @@ private long visitISODateTimeConstructor() { } private BsonBinary visitHexDataConstructor() { - verifyToken("("); + verifyToken(JsonTokenType.LEFT_PAREN); JsonToken subTypeToken = popToken(); if (subTypeToken.getType() != JsonTokenType.INT32) { throw new JsonParseException("JSON reader expected a binary subtype but found '%s'.", subTypeToken.getValue()); } - verifyToken(","); - JsonToken bytesToken = popToken(); - if (bytesToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", bytesToken.getValue()); - } - verifyToken(")"); + verifyToken(JsonTokenType.COMMA); + String hex = readStringFromExtendedJson(); + verifyToken(JsonTokenType.RIGHT_PAREN); - String hex = bytesToken.getValue(String.class); if ((hex.length() & 1) != 0) { hex = "0" + hex; } for (final BsonBinarySubType subType : BsonBinarySubType.values()) { if (subType.getValue() == subTypeToken.getValue(Integer.class)) { - return new BsonBinary(subType, DatatypeConverter.parseHexBinary(hex)); + return new BsonBinary(subType, decodeHex(hex)); } } - return new BsonBinary(DatatypeConverter.parseHexBinary(hex)); + return new BsonBinary(decodeHex(hex)); } private long visitDateTimeConstructor() { DateFormat format = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss z", Locale.ENGLISH); - verifyToken("("); + verifyToken(JsonTokenType.LEFT_PAREN); JsonToken token = popToken(); if (token.getType() == JsonTokenType.RIGHT_PAREN) { return new Date().getTime(); } else if (token.getType() == JsonTokenType.STRING) { - verifyToken(")"); + verifyToken(JsonTokenType.RIGHT_PAREN); String s = token.getValue(String.class); ParsePosition pos = new ParsePosition(0); Date dateTime = format.parse(s, pos); @@ -846,7 +944,7 @@ private long visitDateTimeConstructor() { } private String visitDateTimeConstructorWithOutNew() { - verifyToken("("); + verifyToken(JsonTokenType.LEFT_PAREN); JsonToken token = popToken(); if (token.getType() != JsonTokenType.RIGHT_PAREN) { while (token.getType() != JsonTokenType.END_OF_FILE) { @@ -864,196 +962,420 @@ private String visitDateTimeConstructorWithOutNew() { return df.format(new Date()); } - private BsonBinary visitBinDataExtendedJson() { - verifyToken(":"); - JsonToken bytesToken = popToken(); - if (bytesToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", bytesToken.getValue()); - } - verifyToken(","); - verifyString("$type"); - verifyToken(":"); - JsonToken subTypeToken = popToken(); - if (subTypeToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", subTypeToken.getValue()); + private BsonBinary visitBinDataExtendedJson(final String firstKey) { + + Mark mark = new Mark(); + + try { + verifyToken(JsonTokenType.COLON); + + if (firstKey.equals("$binary")) { + JsonToken nextToken = popToken(); + if (nextToken.getType() == JsonTokenType.BEGIN_OBJECT) { + JsonToken nameToken = popToken(); + String firstNestedKey = nameToken.getValue(String.class); + byte[] data; + byte type; + if (firstNestedKey.equals("base64")) { + verifyToken(JsonTokenType.COLON); + data = Base64.decode(readStringFromExtendedJson()); + verifyToken(JsonTokenType.COMMA); + verifyString("subType"); + verifyToken(JsonTokenType.COLON); + type = readBinarySubtypeFromExtendedJson(); + } else if (firstNestedKey.equals("subType")) { + verifyToken(JsonTokenType.COLON); + type = readBinarySubtypeFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("base64"); + verifyToken(JsonTokenType.COLON); + data = Base64.decode(readStringFromExtendedJson()); + } else { + throw new JsonParseException("Unexpected key for $binary: " + firstNestedKey); + } + verifyToken(JsonTokenType.END_OBJECT); + verifyToken(JsonTokenType.END_OBJECT); + return new BsonBinary(type, data); + } else { + mark.reset(); + return visitLegacyBinaryExtendedJson(firstKey); + } + } else { + mark.reset(); + return visitLegacyBinaryExtendedJson(firstKey); + } + } finally { + mark.discard(); } - verifyToken("}"); + } - byte subType = (byte) Integer.parseInt(subTypeToken.getValue(String.class), 16); + private BsonBinary visitLegacyBinaryExtendedJson(final String firstKey) { - for (final BsonBinarySubType st : BsonBinarySubType.values()) { - if (st.getValue() == subType) { - return new BsonBinary(st, DatatypeConverter.parseBase64Binary(bytesToken.getValue(String.class))); + Mark mark = new Mark(); + + try { + verifyToken(JsonTokenType.COLON); + + byte[] data; + byte type; + + if (firstKey.equals("$binary")) { + data = Base64.decode(readStringFromExtendedJson()); + verifyToken(JsonTokenType.COMMA); + verifyString("$type"); + verifyToken(JsonTokenType.COLON); + type = readBinarySubtypeFromExtendedJson(); + } else { + type = readBinarySubtypeFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("$binary"); + verifyToken(JsonTokenType.COLON); + data = Base64.decode(readStringFromExtendedJson()); } + verifyToken(JsonTokenType.END_OBJECT); + + return new BsonBinary(type, data); + } catch (JsonParseException e) { + mark.reset(); + return null; + } catch (NumberFormatException e) { + mark.reset(); + return null; + } finally { + mark.discard(); + } + } + + private byte readBinarySubtypeFromExtendedJson() { + JsonToken subTypeToken = popToken(); + if (subTypeToken.getType() != JsonTokenType.STRING && subTypeToken.getType() != JsonTokenType.INT32) { + throw new JsonParseException("JSON reader expected a string or number but found '%s'.", subTypeToken.getValue()); } - return new BsonBinary(DatatypeConverter.parseBase64Binary(bytesToken.getValue(String.class))); + if (subTypeToken.getType() == JsonTokenType.STRING) { + return (byte) Integer.parseInt(subTypeToken.getValue(String.class), 16); + } else { + return subTypeToken.getValue(Integer.class).byteValue(); + } } private long visitDateTimeExtendedJson() { - verifyToken(":"); + long value; + verifyToken(JsonTokenType.COLON); JsonToken valueToken = popToken(); - verifyToken("}"); - - if (valueToken.getType() == JsonTokenType.INT32 || valueToken.getType() == JsonTokenType.INT64) { - return valueToken.getValue(Long.class); - } else if (valueToken.getType() == JsonTokenType.STRING) { - String dateTimeString = valueToken.getValue(String.class); - try { - return DatatypeConverter.parseDateTime(dateTimeString).getTimeInMillis(); - } catch (IllegalArgumentException e) { - throw new JsonParseException("JSON reader expected an ISO-8601 date time string but found '%s'.", dateTimeString); + if (valueToken.getType() == JsonTokenType.BEGIN_OBJECT) { + JsonToken nameToken = popToken(); + String name = nameToken.getValue(String.class); + if (!name.equals("$numberLong")) { + throw new JsonParseException(String.format("JSON reader expected $numberLong within $date, but found %s", name)); } + value = visitNumberLongExtendedJson(); + verifyToken(JsonTokenType.END_OBJECT); } else { - throw new JsonParseException("JSON reader expected an integer or string but found '%s'.", valueToken.getValue()); + if (valueToken.getType() == JsonTokenType.INT32 || valueToken.getType() == JsonTokenType.INT64) { + value = valueToken.getValue(Long.class); + } else if (valueToken.getType() == JsonTokenType.STRING) { + String dateTimeString = valueToken.getValue(String.class); + try { + value = DateTimeFormatter.parse(dateTimeString); + } catch (IllegalArgumentException e) { + throw new JsonParseException("Failed to parse string as a date", e); + } + } else { + throw new JsonParseException("JSON reader expected an integer or string but found '%s'.", valueToken.getValue()); + } + verifyToken(JsonTokenType.END_OBJECT); } + return value; } private MaxKey visitMaxKeyExtendedJson() { - verifyToken(":"); - verifyToken(1); - verifyToken("}"); + verifyToken(JsonTokenType.COLON); + verifyToken(JsonTokenType.INT32, 1); + verifyToken(JsonTokenType.END_OBJECT); return new MaxKey(); } private MinKey visitMinKeyExtendedJson() { - verifyToken(":"); - verifyToken(1); - verifyToken("}"); + verifyToken(JsonTokenType.COLON); + verifyToken(JsonTokenType.INT32, 1); + verifyToken(JsonTokenType.END_OBJECT); return new MinKey(); } private ObjectId visitObjectIdExtendedJson() { - verifyToken(":"); - JsonToken valueToken = popToken(); - if (valueToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", valueToken.getValue()); + verifyToken(JsonTokenType.COLON); + ObjectId objectId = new ObjectId(readStringFromExtendedJson()); + verifyToken(JsonTokenType.END_OBJECT); + return objectId; + } + + private BsonRegularExpression visitNewRegularExpressionExtendedJson() { + verifyToken(JsonTokenType.COLON); + verifyToken(JsonTokenType.BEGIN_OBJECT); + + String pattern; + String options = ""; + + String firstKey = readStringFromExtendedJson(); + if (firstKey.equals("pattern")) { + verifyToken(JsonTokenType.COLON); + pattern = readStringFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("options"); + verifyToken(JsonTokenType.COLON); + options = readStringFromExtendedJson(); + } else if (firstKey.equals("options")) { + verifyToken(JsonTokenType.COLON); + options = readStringFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("pattern"); + verifyToken(JsonTokenType.COLON); + pattern = readStringFromExtendedJson(); + } else { + throw new JsonParseException("Expected 't' and 'i' fields in $timestamp document but found " + firstKey); } - verifyToken("}"); - return new ObjectId(valueToken.getValue(String.class)); + + verifyToken(JsonTokenType.END_OBJECT); + verifyToken(JsonTokenType.END_OBJECT); + return new BsonRegularExpression(pattern, options); } - private BsonRegularExpression visitRegularExpressionExtendedJson() { - verifyToken(":"); + private BsonRegularExpression visitRegularExpressionExtendedJson(final String firstKey) { + Mark extendedJsonMark = new Mark(); + + try { + verifyToken(JsonTokenType.COLON); + + String pattern; + String options = ""; + if (firstKey.equals("$regex")) { + pattern = readStringFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("$options"); + verifyToken(JsonTokenType.COLON); + options = readStringFromExtendedJson(); + } else { + options = readStringFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("$regex"); + verifyToken(JsonTokenType.COLON); + pattern = readStringFromExtendedJson(); + } + verifyToken(JsonTokenType.END_OBJECT); + return new BsonRegularExpression(pattern, options); + } catch (JsonParseException e) { + extendedJsonMark.reset(); + return null; + } finally { + extendedJsonMark.discard(); + } + } + + private String readStringFromExtendedJson() { JsonToken patternToken = popToken(); if (patternToken.getType() != JsonTokenType.STRING) { throw new JsonParseException("JSON reader expected a string but found '%s'.", patternToken.getValue()); } - String options = ""; - JsonToken commaToken = popToken(); - if (commaToken.getType() == JsonTokenType.COMMA) { - verifyString("$options"); - verifyToken(":"); - JsonToken optionsToken = popToken(); - if (optionsToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", optionsToken.getValue()); - } - options = optionsToken.getValue(String.class); - } else { - pushToken(commaToken); - } - verifyToken("}"); - return new BsonRegularExpression(patternToken.getValue(String.class), options); + return patternToken.getValue(String.class); } + private String visitSymbolExtendedJson() { - verifyToken(":"); - JsonToken nameToken = popToken(); - if (nameToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", nameToken.getValue()); - } - verifyToken("}"); - return nameToken.getValue(String.class); + verifyToken(JsonTokenType.COLON); + String symbol = readStringFromExtendedJson(); + verifyToken(JsonTokenType.END_OBJECT); + return symbol; } private BsonTimestamp visitTimestampExtendedJson() { - verifyToken(":"); - verifyToken("{"); - verifyString("t"); - verifyToken(":"); + verifyToken(JsonTokenType.COLON); + verifyToken(JsonTokenType.BEGIN_OBJECT); - JsonToken timeToken = popToken(); int time; - if (timeToken.getType() == JsonTokenType.INT32) { - time = timeToken.getValue(Integer.class); - } else { - throw new JsonParseException("JSON reader expected an integer but found '%s'.", timeToken.getValue()); - } - verifyToken(","); - verifyString("i"); - verifyToken(":"); - JsonToken incrementToken = popToken(); int increment; - if (incrementToken.getType() == JsonTokenType.INT32) { - increment = incrementToken.getValue(Integer.class); + + String firstKey = readStringFromExtendedJson(); + if (firstKey.equals("t")) { + verifyToken(JsonTokenType.COLON); + time = readIntFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("i"); + verifyToken(JsonTokenType.COLON); + increment = readIntFromExtendedJson(); + } else if (firstKey.equals("i")) { + verifyToken(JsonTokenType.COLON); + increment = readIntFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("t"); + verifyToken(JsonTokenType.COLON); + time = readIntFromExtendedJson(); } else { - throw new JsonParseException("JSON reader expected an integer but found '%s'.", timeToken.getValue()); + throw new JsonParseException("Expected 't' and 'i' fields in $timestamp document but found " + firstKey); } - verifyToken("}"); - verifyToken("}"); + verifyToken(JsonTokenType.END_OBJECT); + verifyToken(JsonTokenType.END_OBJECT); return new BsonTimestamp(time, increment); } - private void visitJavaScriptExtendedJson() { - verifyToken(":"); - JsonToken codeToken = popToken(); - if (codeToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", codeToken.getValue()); + private int readIntFromExtendedJson() { + JsonToken nextToken = popToken(); + int value; + if (nextToken.getType() == JsonTokenType.INT32) { + value = nextToken.getValue(Integer.class); + } else if (nextToken.getType() == JsonTokenType.INT64) { + value = nextToken.getValue(Long.class).intValue(); + } else { + throw new JsonParseException("JSON reader expected an integer but found '%s'.", nextToken.getValue()); } + return value; + } + + private void visitJavaScriptExtendedJson() { + verifyToken(JsonTokenType.COLON); + String code = readStringFromExtendedJson(); JsonToken nextToken = popToken(); switch (nextToken.getType()) { case COMMA: verifyString("$scope"); - verifyToken(":"); + verifyToken(JsonTokenType.COLON); setState(State.VALUE); - currentValue = codeToken.getValue(); + currentValue = code; setCurrentBsonType(BsonType.JAVASCRIPT_WITH_SCOPE); setContext(new Context(getContext(), BsonContextType.SCOPE_DOCUMENT)); break; case END_OBJECT: - currentValue = codeToken.getValue(); + currentValue = code; setCurrentBsonType(BsonType.JAVASCRIPT); break; default: - throw new JsonParseException("JSON reader expected ',' or '}' but found '%s'.", codeToken.getValue()); + throw new JsonParseException("JSON reader expected ',' or '}' but found '%s'.", nextToken); } } private BsonUndefined visitUndefinedExtendedJson() { - verifyToken(":"); - JsonToken nameToken = popToken(); - if (!nameToken.getValue(String.class).equals("true")) { + verifyToken(JsonTokenType.COLON); + JsonToken valueToken = popToken(); + if (!valueToken.getValue(String.class).equals("true")) { throw new JsonParseException("JSON reader requires $undefined to have the value of true but found '%s'.", - nameToken.getValue()); + valueToken.getValue()); } - verifyToken("}"); + verifyToken(JsonTokenType.END_OBJECT); return new BsonUndefined(); } private Long visitNumberLongExtendedJson() { - verifyToken(":"); - JsonToken nameToken = popToken(); - if (nameToken.getType() != JsonTokenType.STRING) { - throw new JsonParseException("JSON reader expected a string but found '%s'.", nameToken.getValue()); + verifyToken(JsonTokenType.COLON); + Long value; + String longAsString = readStringFromExtendedJson(); + try { + value = Long.valueOf(longAsString); + } catch (NumberFormatException e) { + throw new JsonParseException(format("Exception converting value '%s' to type %s", longAsString, Long.class.getName()), e); + } + verifyToken(JsonTokenType.END_OBJECT); + return value; + } + + private Integer visitNumberIntExtendedJson() { + verifyToken(JsonTokenType.COLON); + Integer value; + String intAsString = readStringFromExtendedJson(); + try { + value = Integer.valueOf(intAsString); + } catch (NumberFormatException e) { + throw new JsonParseException(format("Exception converting value '%s' to type %s", intAsString, Integer.class.getName()), e); + } + verifyToken(JsonTokenType.END_OBJECT); + return value; + } + + private Double visitNumberDoubleExtendedJson() { + verifyToken(JsonTokenType.COLON); + Double value; + String doubleAsString = readStringFromExtendedJson(); + try { + value = Double.valueOf(doubleAsString); + } catch (NumberFormatException e) { + throw new JsonParseException(format("Exception converting value '%s' to type %s", doubleAsString, Double.class.getName()), e); + } + verifyToken(JsonTokenType.END_OBJECT); + return value; + } + + private Decimal128 visitNumberDecimalExtendedJson() { + verifyToken(JsonTokenType.COLON); + Decimal128 value; + String decimal128AsString = readStringFromExtendedJson(); + try { + value = Decimal128.parse(decimal128AsString); + } catch (NumberFormatException e) { + throw new JsonParseException(format("Exception converting value '%s' to type %s", decimal128AsString, + Decimal128.class.getName()), e); + } + verifyToken(JsonTokenType.END_OBJECT); + return value; + } + + private BsonDbPointer visitDbPointerExtendedJson() { + verifyToken(JsonTokenType.COLON); + verifyToken(JsonTokenType.BEGIN_OBJECT); + + String ref; + ObjectId oid; + + String firstKey = readStringFromExtendedJson(); + if (firstKey.equals("$ref")) { + verifyToken(JsonTokenType.COLON); + ref = readStringFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("$id"); + oid = readDbPointerIdFromExtendedJson(); + verifyToken(JsonTokenType.END_OBJECT); + } else if (firstKey.equals("$id")) { + oid = readDbPointerIdFromExtendedJson(); + verifyToken(JsonTokenType.COMMA); + verifyString("$ref"); + verifyToken(JsonTokenType.COLON); + ref = readStringFromExtendedJson(); + + } else { + throw new JsonParseException("Expected $ref and $id fields in $dbPointer document but found " + firstKey); } - verifyToken("}"); - return nameToken.getValue(Long.class); + verifyToken(JsonTokenType.END_OBJECT); + return new BsonDbPointer(ref, oid); + } + + private ObjectId readDbPointerIdFromExtendedJson() { + ObjectId oid; + verifyToken(JsonTokenType.COLON); + verifyToken(JsonTokenType.BEGIN_OBJECT); + verifyToken(JsonTokenType.STRING, "$oid"); + oid = visitObjectIdExtendedJson(); + return oid; } + @Deprecated @Override public void mark() { if (mark != null) { - throw new BSONException("A mark already exists; it needs to be reset before creating a new one"); - } + throw new BSONException("A mark already exists; it needs to be reset before creating a new one"); + } mark = new Mark(); } + @Override + public BsonReaderMark getMark() { + return new Mark(); + } + + @Deprecated @Override public void reset() { if (mark == null) { - throw new BSONException("trying to reset a mark before creating it"); - } + throw new BSONException("trying to reset a mark before creating it"); + } mark.reset(); mark = null; } @@ -1062,25 +1384,30 @@ public void reset() { protected Context getContext() { return (Context) super.getContext(); } + protected class Mark extends AbstractBsonReader.Mark { - private JsonToken pushedToken; - private Object currentValue; - private int position; + private final JsonToken pushedToken; + private final Object currentValue; + private final int markPos; protected Mark() { super(); pushedToken = JsonReader.this.pushedToken; currentValue = JsonReader.this.currentValue; - position = JsonReader.this.scanner.getBufferPosition(); + markPos = JsonReader.this.scanner.mark(); } - protected void reset() { + public void reset() { super.reset(); JsonReader.this.pushedToken = pushedToken; JsonReader.this.currentValue = currentValue; - JsonReader.this.scanner.setBufferPosition(position); + JsonReader.this.scanner.reset(markPos); JsonReader.this.setContext(new Context(getParentContext(), getContextType())); } + + public void discard() { + JsonReader.this.scanner.discard(markPos); + } } @@ -1097,5 +1424,25 @@ protected BsonContextType getContextType() { return super.getContextType(); } } + + private static byte[] decodeHex(final String hex) { + if (hex.length() % 2 != 0) { + throw new IllegalArgumentException("A hex string must contain an even number of characters: " + hex); + } + + byte[] out = new byte[hex.length() / 2]; + + for (int i = 0; i < hex.length(); i += 2) { + int high = Character.digit(hex.charAt(i), 16); + int low = Character.digit(hex.charAt(i + 1), 16); + if (high == -1 || low == -1) { + throw new IllegalArgumentException("A hex string can only contain the characters 0-9, A-F, a-f: " + hex); + } + + out[i / 2] = (byte) (high * 16 + low); + } + + return out; + } } diff --git a/bson/src/main/org/bson/json/JsonScanner.java b/bson/src/main/org/bson/json/JsonScanner.java index c5fad81770c..e2935a2a1d4 100644 --- a/bson/src/main/org/bson/json/JsonScanner.java +++ b/bson/src/main/org/bson/json/JsonScanner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import org.bson.BsonRegularExpression; +import java.io.Reader; + /** * Parses the string representation of a JSON object into a set of {@link JsonToken}-derived objects. * @@ -27,36 +29,28 @@ class JsonScanner { private final JsonBuffer buffer; - /** - * @param newPosition the new position of the cursor position in the buffer - */ - public void setBufferPosition(final int newPosition) { - buffer.setPosition(newPosition); + JsonScanner(final JsonBuffer buffer) { + this.buffer = buffer; } - /** - * @return the current location of the cursor in the buffer - */ - public int getBufferPosition() { - return buffer.getPosition(); + JsonScanner(final String json) { + this(new JsonStringBuffer(json)); } - /** - * Constructs a a new {@code JSONScanner} that produces values scanned from specified {@code JSONBuffer}. - * - * @param buffer A buffer to be scanned. - */ - public JsonScanner(final JsonBuffer buffer) { - this.buffer = buffer; + JsonScanner(final Reader reader) { + this(new JsonStreamBuffer(reader)); } - /** - * Constructs a a new {@code JSONScanner} that produces values scanned from the specified {@code String}. - * - * @param json A string representation of a JSON to be scanned. - */ - public JsonScanner(final String json) { - this(new JsonBuffer(json)); + public void reset(final int markPos) { + buffer.reset(markPos); + } + + public int mark() { + return buffer.mark(); + } + + public void discard(final int markPos) { + buffer.discard(markPos); } /** @@ -102,7 +96,7 @@ public JsonToken nextToken() { if (c == '-' || Character.isDigit(c)) { return scanNumber((char) c); } else if (c == '$' || c == '_' || Character.isLetter(c)) { - return scanUnquotedString(); + return scanUnquotedString((char) c); } else { int position = buffer.getPosition(); buffer.unread(c); @@ -125,9 +119,8 @@ public JsonToken nextToken() { */ private JsonToken scanRegularExpression() { - int start = buffer.getPosition() - 1; - int options = -1; - + StringBuilder patternBuilder = new StringBuilder(); + StringBuilder optionsBuilder = new StringBuilder(); RegularExpressionState state = RegularExpressionState.IN_PATTERN; while (true) { int c = buffer.read(); @@ -139,7 +132,6 @@ private JsonToken scanRegularExpression() { break; case '/': state = RegularExpressionState.IN_OPTIONS; - options = buffer.getPosition(); break; case '\\': state = RegularExpressionState.IN_ESCAPE_SEQUENCE; @@ -183,13 +175,22 @@ private JsonToken scanRegularExpression() { switch (state) { case DONE: buffer.unread(c); - int end = buffer.getPosition(); BsonRegularExpression regex - = new BsonRegularExpression(buffer.substring(start + 1, options - 1), buffer.substring(options, end)); + = new BsonRegularExpression(patternBuilder.toString(), optionsBuilder.toString()); return new JsonToken(JsonTokenType.REGULAR_EXPRESSION, regex); case INVALID: throw new JsonParseException("Invalid JSON regular expression. Position: %d.", buffer.getPosition()); default: + switch (state) { + case IN_OPTIONS: + if (c != '/') { + optionsBuilder.append((char) c); + } + break; + default: + patternBuilder.append((char) c); + break; + } } } } @@ -199,14 +200,16 @@ private JsonToken scanRegularExpression() { * * @return The string token. */ - private JsonToken scanUnquotedString() { - int start = buffer.getPosition() - 1; + private JsonToken scanUnquotedString(final char firstChar) { + StringBuilder sb = new StringBuilder(); + sb.append(firstChar); int c = buffer.read(); while (c == '$' || c == '_' || Character.isLetterOrDigit(c)) { + sb.append((char) c); c = buffer.read(); } buffer.unread(c); - String lexeme = buffer.substring(start, buffer.getPosition()); + String lexeme = sb.toString(); return new JsonToken(JsonTokenType.UNQUOTED_STRING, lexeme); } @@ -232,8 +235,8 @@ private JsonToken scanUnquotedString() { private JsonToken scanNumber(final char firstChar) { int c = firstChar; - - int start = buffer.getPosition() - 1; + StringBuilder sb = new StringBuilder(); + sb.append(firstChar); NumberState state; @@ -408,6 +411,7 @@ private JsonToken scanNumber(final char firstChar) { sawMinusInfinity = false; break; } + sb.append((char) c); c = buffer.read(); } if (sawMinusInfinity) { @@ -440,7 +444,7 @@ private JsonToken scanNumber(final char firstChar) { throw new JsonParseException("Invalid JSON number"); case DONE: buffer.unread(c); - String lexeme = buffer.substring(start, buffer.getPosition()); + String lexeme = sb.toString(); if (type == JsonTokenType.DOUBLE) { return new JsonToken(JsonTokenType.DOUBLE, Double.parseDouble(lexeme)); } else { @@ -452,6 +456,7 @@ private JsonToken scanNumber(final char firstChar) { } } default: + sb.append((char) c); } } diff --git a/bson/src/main/org/bson/json/JsonStreamBuffer.java b/bson/src/main/org/bson/json/JsonStreamBuffer.java new file mode 100644 index 00000000000..036a14dded2 --- /dev/null +++ b/bson/src/main/org/bson/json/JsonStreamBuffer.java @@ -0,0 +1,155 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +class JsonStreamBuffer implements JsonBuffer { + + private final Reader reader; + private final List markedPositions = new ArrayList(); + private final int initialBufferSize; + private int position; + private int lastChar; + private boolean reuseLastChar; + private boolean eof; + private char[] buffer; + private int bufferStartPos; + private int bufferCount; + + JsonStreamBuffer(final Reader reader) { + this(reader, 16); + } + + JsonStreamBuffer(final Reader reader, final int initialBufferSize) { + this.initialBufferSize = initialBufferSize; + this.reader = reader; + resetBuffer(); + } + + public int getPosition() { + return position; + } + + public int read() { + if (eof) { + throw new JsonParseException("Trying to read past EOF."); + } + + // if we just unread, we need to use the last character read since it may not be in the + // buffer + if (reuseLastChar) { + reuseLastChar = false; + int reusedChar = lastChar; + lastChar = -1; + position++; + return reusedChar; + } + + // use the buffer until we catch up to the stream position + if (position - bufferStartPos < bufferCount) { + int currChar = buffer[position - bufferStartPos]; + lastChar = currChar; + position++; + return currChar; + } + + if (markedPositions.isEmpty()) { + resetBuffer(); + } + + // otherwise, try and read from the stream + try { + int nextChar = reader.read(); + if (nextChar != -1) { + lastChar = nextChar; + addToBuffer((char) nextChar); + } + position++; + if (nextChar == -1) { + eof = true; + } + return nextChar; + + } catch (final IOException e) { + throw new JsonParseException(e); + } + } + + private void resetBuffer() { + bufferStartPos = -1; + bufferCount = 0; + buffer = new char[initialBufferSize]; + } + + public void unread(final int c) { + eof = false; + if (c != -1 && lastChar == c) { + reuseLastChar = true; + position--; + } + } + + public int mark() { + if (bufferCount == 0) { // Why not markedPositions.isEmpty()? + bufferStartPos = position; + } + if (!markedPositions.contains(position)) { + markedPositions.add(position); + } + return position; + } + + public void reset(final int markPos) { + if (markPos > position) { + throw new IllegalStateException("mark cannot reset ahead of position, only back"); + } + int idx = markedPositions.indexOf(markPos); + if (idx == -1) { + throw new IllegalArgumentException("mark invalidated"); + } + if (markPos != position) { + reuseLastChar = false; + } + markedPositions.subList(idx, markedPositions.size()).clear(); + position = markPos; + } + + public void discard(final int markPos) { + int idx = markedPositions.indexOf(markPos); + if (idx == -1) { + return; + } + markedPositions.subList(idx, markedPositions.size()).clear(); + } + + private void addToBuffer(final char curChar) { + // if the lowest mark is ahead of our position, we can safely add it to our buffer + if (!markedPositions.isEmpty()) { + if (bufferCount == buffer.length) { + char[] newBuffer = new char[buffer.length * 2]; + System.arraycopy(buffer, 0, newBuffer, 0, bufferCount); + buffer = newBuffer; + } + buffer[bufferCount] = curChar; + bufferCount++; + } + } +} diff --git a/bson/src/main/org/bson/json/JsonStringBuffer.java b/bson/src/main/org/bson/json/JsonStringBuffer.java new file mode 100644 index 00000000000..8a250bb2eb4 --- /dev/null +++ b/bson/src/main/org/bson/json/JsonStringBuffer.java @@ -0,0 +1,65 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class JsonStringBuffer implements JsonBuffer { + + private final String buffer; + private int position; + private boolean eof; + + JsonStringBuffer(final String buffer) { + this.buffer = buffer; + } + + public int getPosition() { + return position; + } + + public int read() { + if (eof) { + throw new JsonParseException("Trying to read past EOF."); + } else if (position >= buffer.length()) { + eof = true; + return -1; + } else { + return (int) buffer.charAt(position++); + } + } + + public void unread(final int c) { + eof = false; + if (c != -1 && buffer.charAt(position - 1) == c) { + position--; + } + } + + public int mark() { + return position; + } + + public void reset(final int markPos) { + if (markPos > position) { + throw new IllegalStateException("mark cannot reset ahead of position, only back"); + } + position = markPos; + } + + public void discard(final int markPos) { + } + +} diff --git a/bson/src/main/org/bson/json/JsonStringConverter.java b/bson/src/main/org/bson/json/JsonStringConverter.java new file mode 100644 index 00000000000..6a3d3456913 --- /dev/null +++ b/bson/src/main/org/bson/json/JsonStringConverter.java @@ -0,0 +1,24 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class JsonStringConverter implements Converter { + @Override + public void convert(final String value, final StrictJsonWriter writer) { + writer.writeString(value); + } +} diff --git a/bson/src/main/org/bson/json/JsonSymbolConverter.java b/bson/src/main/org/bson/json/JsonSymbolConverter.java new file mode 100644 index 00000000000..51b65e7fd31 --- /dev/null +++ b/bson/src/main/org/bson/json/JsonSymbolConverter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class JsonSymbolConverter implements Converter { + @Override + public void convert(final String value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeString("$symbol", value); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/JsonToken.java b/bson/src/main/org/bson/json/JsonToken.java index 2b2144e88e2..68c579f2369 100644 --- a/bson/src/main/org/bson/json/JsonToken.java +++ b/bson/src/main/org/bson/json/JsonToken.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,11 @@ package org.bson.json; +import org.bson.BsonDouble; +import org.bson.types.Decimal128; + +import static java.lang.String.format; + /** * A JSON token. */ @@ -24,7 +29,7 @@ class JsonToken { private final Object value; private final JsonTokenType type; - public JsonToken(final JsonTokenType type, final Object value) { + JsonToken(final JsonTokenType type, final Object value) { this.value = value; this.type = type; } @@ -34,18 +39,36 @@ public Object getValue() { } public T getValue(final Class clazz) { - if (Long.class == clazz) { - if (value instanceof Integer) { - return clazz.cast(((Integer) value).longValue()); - } else if (value instanceof String) { - return clazz.cast(Long.valueOf((String) value)); + try { + if (Long.class == clazz) { + if (value instanceof Integer) { + return clazz.cast(((Integer) value).longValue()); + } else if (value instanceof String) { + return clazz.cast(Long.valueOf((String) value)); + } + } else if (Integer.class == clazz) { + if (value instanceof String) { + return clazz.cast(Integer.valueOf((String) value)); + } + } else if (Double.class == clazz) { + if (value instanceof String) { + return clazz.cast(Double.valueOf((String) value)); + } + } else if (Decimal128.class == clazz) { + if (value instanceof Integer) { + return clazz.cast(new Decimal128((Integer) value)); + } else if (value instanceof Long) { + return clazz.cast(new Decimal128((Long) value)); + } else if (value instanceof Double) { + return clazz.cast(new BsonDouble((Double) value).decimal128Value()); + } else if (value instanceof String) { + return clazz.cast(Decimal128.parse((String) value)); + } } - } - try { return clazz.cast(value); - } catch (ClassCastException e) { - throw new IllegalStateException(e); + } catch (Exception e) { + throw new JsonParseException(format("Exception converting value '%s' to type %s", value, clazz.getName()), e); } } diff --git a/bson/src/main/org/bson/json/JsonTokenType.java b/bson/src/main/org/bson/json/JsonTokenType.java index d9ede9e1442..9b2e88d5341 100644 --- a/bson/src/main/org/bson/json/JsonTokenType.java +++ b/bson/src/main/org/bson/json/JsonTokenType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/json/JsonWriter.java b/bson/src/main/org/bson/json/JsonWriter.java index 74eae980d0d..f0feabb3e5f 100644 --- a/bson/src/main/org/bson/json/JsonWriter.java +++ b/bson/src/main/org/bson/json/JsonWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,22 +17,15 @@ package org.bson.json; import org.bson.AbstractBsonWriter; -import org.bson.BSONException; import org.bson.BsonBinary; import org.bson.BsonContextType; import org.bson.BsonDbPointer; import org.bson.BsonRegularExpression; import org.bson.BsonTimestamp; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; -import java.io.IOException; import java.io.Writer; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -import static java.lang.String.format; -import static javax.xml.bind.DatatypeConverter.printBase64Binary; /** * A {@code BsonWriter} implementation that outputs a JSON representation of BSON. @@ -40,14 +33,15 @@ * @since 3.0 */ public class JsonWriter extends AbstractBsonWriter { - private final Writer writer; private final JsonWriterSettings settings; + private final StrictCharacterStreamJsonWriter strictJsonWriter; /** * Creates a new instance which uses {@code writer} to write JSON to. * * @param writer the writer to write JSON to. */ + @SuppressWarnings("deprecation") public JsonWriter(final Writer writer) { this(writer, new JsonWriterSettings()); } @@ -61,8 +55,13 @@ public JsonWriter(final Writer writer) { public JsonWriter(final Writer writer, final JsonWriterSettings settings) { super(settings); this.settings = settings; - this.writer = writer; - setContext(new Context(null, BsonContextType.TOP_LEVEL, "")); + setContext(new Context(null, BsonContextType.TOP_LEVEL)); + strictJsonWriter = new StrictCharacterStreamJsonWriter(writer, StrictCharacterStreamJsonWriterSettings.builder() + .indent(settings.isIndent()) + .newLineCharacters(settings.getNewLineCharacters()) + .indentCharacters(settings.getIndentCharacters()) + .maxLength(settings.getMaxLength()) + .build()); } /** @@ -71,7 +70,7 @@ public JsonWriter(final Writer writer, final JsonWriterSettings settings) { * @return the writer */ public Writer getWriter() { - return writer; + return strictJsonWriter.getWriter(); } @Override @@ -79,189 +78,110 @@ protected Context getContext() { return (Context) super.getContext(); } + @Override + protected void doWriteName(final String name) { + strictJsonWriter.writeName(name); + } + @Override protected void doWriteStartDocument() { - try { - if (getState() == State.VALUE || getState() == State.SCOPE_DOCUMENT) { - writeNameHelper(getName()); - } - writer.write("{"); - - BsonContextType contextType = (getState() == State.SCOPE_DOCUMENT) ? BsonContextType.SCOPE_DOCUMENT : BsonContextType.DOCUMENT; - setContext(new Context(getContext(), contextType, settings.getIndentCharacters())); - } catch (IOException e) { - throwBSONException(e); - } + strictJsonWriter.writeStartObject(); + + BsonContextType contextType = (getState() == State.SCOPE_DOCUMENT) ? BsonContextType.SCOPE_DOCUMENT : BsonContextType.DOCUMENT; + setContext(new Context(getContext(), contextType)); } @Override protected void doWriteEndDocument() { - try { - if (settings.isIndent() && getContext().hasElements) { - writer.write(settings.getNewLineCharacters()); - if (getContext().getParentContext() != null) { - writer.write(getContext().getParentContext().indentation); - } - writer.write("}"); - } else { - writer.write(" }"); - } - - if (getContext().getContextType() == BsonContextType.SCOPE_DOCUMENT) { - setContext(getContext().getParentContext()); - writeEndDocument(); - } else { - setContext(getContext().getParentContext()); - } - } catch (IOException e) { - throwBSONException(e); + strictJsonWriter.writeEndObject(); + if (getContext().getContextType() == BsonContextType.SCOPE_DOCUMENT) { + setContext(getContext().getParentContext()); + writeEndDocument(); + } else { + setContext(getContext().getParentContext()); } } @Override protected void doWriteStartArray() { - try { - writeNameHelper(getName()); - writer.write("["); - setContext(new Context(getContext(), BsonContextType.ARRAY, settings.getIndentCharacters())); - } catch (IOException e) { - throwBSONException(e); - } + strictJsonWriter.writeStartArray(); + setContext(new Context(getContext(), BsonContextType.ARRAY)); } @Override protected void doWriteEndArray() { - try { - writer.write("]"); - } catch (IOException e) { - throwBSONException(e); - } + strictJsonWriter.writeEndArray(); setContext(getContext().getParentContext()); } @Override protected void doWriteBinaryData(final BsonBinary binary) { - try { - switch (settings.getOutputMode()) { - case SHELL: - writeNameHelper(getName()); - writer.write(format("new BinData(%s, \"%s\")", Integer.toString(binary.getType() & 0xFF), - printBase64Binary(binary.getData()))); - - break; - default: - writeStartDocument(); - writeString("$binary", printBase64Binary(binary.getData())); - writeString("$type", Integer.toHexString(binary.getType() & 0xFF)); - writeEndDocument(); - } - } catch (IOException e) { - throwBSONException(e); - } + settings.getBinaryConverter().convert(binary, strictJsonWriter); } @Override public void doWriteBoolean(final boolean value) { - try { - writeNameHelper(getName()); - writer.write(value ? "true" : "false"); - } catch (IOException e) { - throwBSONException(e); - } + settings.getBooleanConverter().convert(value, strictJsonWriter); } @Override protected void doWriteDateTime(final long value) { - try { - switch (settings.getOutputMode()) { - case STRICT: - writeStartDocument(); - writeNameHelper("$date"); - writer.write(Long.toString(value)); - writeEndDocument(); - break; - case SHELL: - writeNameHelper(getName()); - - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - if (value >= -59014396800000L && value <= 253399536000000L) { - writer.write(format("ISODate(\"%s\")", dateFormat.format(new Date(value)))); - } else { - writer.write(format("new Date(%d)", value)); - } - break; - default: - throw new BSONException("Unexpected JSONMode."); - } - } catch (IOException e) { - throwBSONException(e); - } + settings.getDateTimeConverter().convert(value, strictJsonWriter); } @Override protected void doWriteDBPointer(final BsonDbPointer value) { - writeStartDocument(); - writeString("$ref", value.getNamespace()); - writeObjectId("$id", value.getId()); - writeEndDocument(); + if (settings.getOutputMode() == JsonMode.EXTENDED) { + new Converter() { + @Override + public void convert(final BsonDbPointer value1, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeStartObject("$dbPointer"); + writer.writeString("$ref", value1.getNamespace()); + writer.writeName("$id"); + doWriteObjectId(value1.getId()); + writer.writeEndObject(); + writer.writeEndObject(); + } + }.convert(value, strictJsonWriter); + } else { + new Converter() { + @Override + public void convert(final BsonDbPointer value1, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeString("$ref", value1.getNamespace()); + writer.writeName("$id"); + doWriteObjectId(value1.getId()); + writer.writeEndObject(); + } + }.convert(value, strictJsonWriter); + } } @Override protected void doWriteDouble(final double value) { - try { - writeNameHelper(getName()); - writer.write(Double.toString(value)); - setState(getNextState()); - } catch (IOException e) { - throwBSONException(e); - } + settings.getDoubleConverter().convert(value, strictJsonWriter); } @Override protected void doWriteInt32(final int value) { - try { - writeNameHelper(getName()); - writer.write(Integer.toString(value)); - } catch (IOException e) { - throwBSONException(e); - } + settings.getInt32Converter().convert(value, strictJsonWriter); } @Override protected void doWriteInt64(final long value) { - try { - switch (settings.getOutputMode()) { - case STRICT: - writeStartDocument(); - writeNameHelper("$numberLong"); - writer.write(format("\"%d\"", value)); - writeEndDocument(); - break; - case SHELL: - writeNameHelper(getName()); - if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) { - writer.write(format("NumberLong(%d)", value)); - } else { - writer.write(format("NumberLong(\"%d\")", value)); - } - break; - default: - writeNameHelper(getName()); - writer.write(Long.toString(value)); - break; - } - } catch (IOException e) { - throwBSONException(e); - } + settings.getInt64Converter().convert(value, strictJsonWriter); + } + + @Override + protected void doWriteDecimal128(final Decimal128 value) { + settings.getDecimal128Converter().convert(value, strictJsonWriter); } @Override protected void doWriteJavaScript(final String code) { - writeStartDocument(); - writeString("$code", code); - writeEndDocument(); + settings.getJavaScriptConverter().convert(code, strictJsonWriter); } @Override @@ -273,242 +193,68 @@ protected void doWriteJavaScriptWithScope(final String code) { @Override protected void doWriteMaxKey() { - writeStartDocument(); - writeInt32("$maxKey", 1); - writeEndDocument(); + settings.getMaxKeyConverter().convert(null, strictJsonWriter); } @Override protected void doWriteMinKey() { - writeStartDocument(); - writeInt32("$minKey", 1); - writeEndDocument(); + settings.getMinKeyConverter().convert(null, strictJsonWriter); } @Override public void doWriteNull() { - try { - writeNameHelper(getName()); - writer.write("null"); - } catch (IOException e) { - throwBSONException(e); - } + settings.getNullConverter().convert(null, strictJsonWriter); } @Override public void doWriteObjectId(final ObjectId objectId) { - try { - switch (settings.getOutputMode()) { - case STRICT: - writeStartDocument(); - writeString("$oid", objectId.toString()); - writeEndDocument(); - break; - case SHELL: - writeNameHelper(getName()); - writer.write(format("ObjectId(\"%s\")", objectId.toString())); - break; - default: - throw new BSONException("Unknown output mode" + settings.getOutputMode()); - } - } catch (IOException e) { - throwBSONException(e); - } + settings.getObjectIdConverter().convert(objectId, strictJsonWriter); } @Override public void doWriteRegularExpression(final BsonRegularExpression regularExpression) { - try { - switch (settings.getOutputMode()) { - case STRICT: - writeStartDocument(); - writeString("$regex", regularExpression.getPattern()); - writeString("$options", regularExpression.getOptions()); - writeEndDocument(); - break; - case SHELL: - writeNameHelper(getName()); - writer.write("/"); - String escaped = (regularExpression.getPattern().equals("")) ? "(?:)" : regularExpression.getPattern() - .replace("/", "\\/"); - writer.write(escaped); - writer.write("/"); - writer.write(regularExpression.getOptions()); - break; - default: - throw new BSONException("Unknown output mode" + settings.getOutputMode()); - } - } catch (IOException e) { - throwBSONException(e); - } + settings.getRegularExpressionConverter().convert(regularExpression, strictJsonWriter); } @Override public void doWriteString(final String value) { - try { - writeNameHelper(getName()); - writeStringHelper(value); - } catch (IOException e) { - throwBSONException(e); - } + settings.getStringConverter().convert(value, strictJsonWriter); } @Override public void doWriteSymbol(final String value) { - writeStartDocument(); - writeString("$symbol", value); - writeEndDocument(); + settings.getSymbolConverter().convert(value, strictJsonWriter); } @Override public void doWriteTimestamp(final BsonTimestamp value) { - try { - switch (settings.getOutputMode()) { - case STRICT: - writeStartDocument(); - writeStartDocument("$timestamp"); - writeInt32("t", value.getTime()); - writeInt32("i", value.getInc()); - writeEndDocument(); - writeEndDocument(); - break; - case SHELL: - writeNameHelper(getName()); - writer.write(format("Timestamp(%d, %d)", value.getTime(), value.getInc())); - break; - default: - throw new BSONException("Unknown output mode" + settings.getOutputMode()); - } - } catch (IOException e) { - throwBSONException(e); - } + settings.getTimestampConverter().convert(value, strictJsonWriter); } @Override public void doWriteUndefined() { - try { - switch (settings.getOutputMode()) { - case STRICT: - writeStartDocument(); - writeBoolean("$undefined", true); - writeEndDocument(); - break; - case SHELL: - writeNameHelper(getName()); - writer.write("undefined"); - break; - default: - throw new BSONException("Unknown output mode" + settings.getOutputMode()); - } - } catch (IOException e) { - throwBSONException(e); - } + settings.getUndefinedConverter().convert(null, strictJsonWriter); } @Override public void flush() { - try { - writer.flush(); - } catch (IOException e) { - throwBSONException(e); - } + strictJsonWriter.flush(); } - private void writeNameHelper(final String name) throws IOException { - switch (getContext().getContextType()) { - case ARRAY: - // don't write Array element names in JSON - if (getContext().hasElements) { - writer.write(", "); - } - break; - case DOCUMENT: - case SCOPE_DOCUMENT: - if (getContext().hasElements) { - writer.write(","); - } - if (settings.isIndent()) { - writer.write(settings.getNewLineCharacters()); - writer.write(getContext().indentation); - } else { - writer.write(" "); - } - writeStringHelper(name); - writer.write(" : "); - break; - case TOP_LEVEL: - break; - default: - throw new BSONException("Invalid contextType."); - } - - getContext().hasElements = true; - } - - private void writeStringHelper(final String str) throws IOException { - writer.write('"'); - for (final char c : str.toCharArray()) { - switch (c) { - case '"': - writer.write("\\\""); - break; - case '\\': - writer.write("\\\\"); - break; - case '\b': - writer.write("\\b"); - break; - case '\f': - writer.write("\\f"); - break; - case '\n': - writer.write("\\n"); - break; - case '\r': - writer.write("\\r"); - break; - case '\t': - writer.write("\\t"); - break; - default: - switch (Character.getType(c)) { - case Character.UPPERCASE_LETTER: - case Character.LOWERCASE_LETTER: - case Character.TITLECASE_LETTER: - case Character.OTHER_LETTER: - case Character.DECIMAL_DIGIT_NUMBER: - case Character.LETTER_NUMBER: - case Character.OTHER_NUMBER: - case Character.SPACE_SEPARATOR: - case Character.CONNECTOR_PUNCTUATION: - case Character.DASH_PUNCTUATION: - case Character.START_PUNCTUATION: - case Character.END_PUNCTUATION: - case Character.INITIAL_QUOTE_PUNCTUATION: - case Character.FINAL_QUOTE_PUNCTUATION: - case Character.OTHER_PUNCTUATION: - case Character.MATH_SYMBOL: - case Character.CURRENCY_SYMBOL: - case Character.MODIFIER_SYMBOL: - case Character.OTHER_SYMBOL: - writer.write(c); - break; - default: - writer.write("\\u"); - writer.write(Integer.toHexString((c & 0xf000) >> 12)); - writer.write(Integer.toHexString((c & 0x0f00) >> 8)); - writer.write(Integer.toHexString((c & 0x00f0) >> 4)); - writer.write(Integer.toHexString(c & 0x000f)); - break; - } - break; - } - } - writer.write('"'); + /** + * Return true if the output has been truncated due to exceeding the length specified in {@link JsonWriterSettings#maxLength}. + * + * @return true if the output has been truncated + * @since 3.7 + * @see JsonWriterSettings#maxLength + */ + public boolean isTruncated() { + return strictJsonWriter.isTruncated(); } - private void throwBSONException(final IOException e) { - throw new BSONException("Wrapping IOException", e); + @Override + protected boolean abortPipe() { + return strictJsonWriter.isTruncated(); } /** @@ -516,8 +262,6 @@ private void throwBSONException(final IOException e) { * settings for the indentation level and whether there are any child elements at this level. */ public class Context extends AbstractBsonWriter.Context { - private final String indentation; - private boolean hasElements; /** * Creates a new context. @@ -526,9 +270,19 @@ public class Context extends AbstractBsonWriter.Context { * @param contextType the type of this context * @param indentChars the String to use for indentation at this level. */ + @Deprecated public Context(final Context parentContext, final BsonContextType contextType, final String indentChars) { + this(parentContext, contextType); + } + + /** + * Creates a new context. + * + * @param parentContext the parent context that can be used for going back up to the parent level + * @param contextType the type of this context + */ + public Context(final Context parentContext, final BsonContextType contextType) { super(parentContext, contextType); - this.indentation = (parentContext == null) ? indentChars : parentContext.indentation + indentChars; } @Override diff --git a/bson/src/main/org/bson/json/JsonWriterSettings.java b/bson/src/main/org/bson/json/JsonWriterSettings.java index a7f71b7d7ac..1b3aea26977 100644 --- a/bson/src/main/org/bson/json/JsonWriterSettings.java +++ b/bson/src/main/org/bson/json/JsonWriterSettings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,19 @@ package org.bson.json; +import org.bson.BsonBinary; +import org.bson.BsonMaxKey; +import org.bson.BsonMinKey; +import org.bson.BsonNull; +import org.bson.BsonRegularExpression; +import org.bson.BsonTimestamp; +import org.bson.BsonUndefined; import org.bson.BsonWriterSettings; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + +import static org.bson.assertions.Assertions.isTrueArgument; +import static org.bson.assertions.Assertions.notNull; /** * Settings to control the behavior of a {@code JSONWriter} instance. @@ -25,34 +37,116 @@ * @since 3.0 */ public class JsonWriterSettings extends BsonWriterSettings { + + private static final JsonNullConverter JSON_NULL_CONVERTER = new JsonNullConverter(); + private static final JsonStringConverter JSON_STRING_CONVERTER = new JsonStringConverter(); + private static final JsonBooleanConverter JSON_BOOLEAN_CONVERTER = new JsonBooleanConverter(); + private static final JsonDoubleConverter JSON_DOUBLE_CONVERTER = new JsonDoubleConverter(); + private static final ExtendedJsonDoubleConverter EXTENDED_JSON_DOUBLE_CONVERTER = new ExtendedJsonDoubleConverter(); + private static final RelaxedExtendedJsonDoubleConverter RELAXED_EXTENDED_JSON_DOUBLE_CONVERTER = + new RelaxedExtendedJsonDoubleConverter(); + private static final JsonInt32Converter JSON_INT_32_CONVERTER = new JsonInt32Converter(); + private static final ExtendedJsonInt32Converter EXTENDED_JSON_INT_32_CONVERTER = new ExtendedJsonInt32Converter(); + private static final JsonSymbolConverter JSON_SYMBOL_CONVERTER = new JsonSymbolConverter(); + private static final ExtendedJsonMinKeyConverter EXTENDED_JSON_MIN_KEY_CONVERTER = new ExtendedJsonMinKeyConverter(); + private static final ShellMinKeyConverter SHELL_MIN_KEY_CONVERTER = new ShellMinKeyConverter(); + private static final ExtendedJsonMaxKeyConverter EXTENDED_JSON_MAX_KEY_CONVERTER = new ExtendedJsonMaxKeyConverter(); + private static final ShellMaxKeyConverter SHELL_MAX_KEY_CONVERTER = new ShellMaxKeyConverter(); + private static final ExtendedJsonUndefinedConverter EXTENDED_JSON_UNDEFINED_CONVERTER = new ExtendedJsonUndefinedConverter(); + private static final ShellUndefinedConverter SHELL_UNDEFINED_CONVERTER = new ShellUndefinedConverter(); + private static final LegacyExtendedJsonDateTimeConverter LEGACY_EXTENDED_JSON_DATE_TIME_CONVERTER = + new LegacyExtendedJsonDateTimeConverter(); + private static final ExtendedJsonDateTimeConverter EXTENDED_JSON_DATE_TIME_CONVERTER = new ExtendedJsonDateTimeConverter(); + private static final RelaxedExtendedJsonDateTimeConverter RELAXED_EXTENDED_JSON_DATE_TIME_CONVERTER = + new RelaxedExtendedJsonDateTimeConverter(); + private static final ShellDateTimeConverter SHELL_DATE_TIME_CONVERTER = new ShellDateTimeConverter(); + private static final ExtendedJsonBinaryConverter EXTENDED_JSON_BINARY_CONVERTER = new ExtendedJsonBinaryConverter(); + private static final LegacyExtendedJsonBinaryConverter LEGACY_EXTENDED_JSON_BINARY_CONVERTER = new LegacyExtendedJsonBinaryConverter(); + private static final ShellBinaryConverter SHELL_BINARY_CONVERTER = new ShellBinaryConverter(); + private static final ExtendedJsonInt64Converter EXTENDED_JSON_INT_64_CONVERTER = new ExtendedJsonInt64Converter(); + private static final RelaxedExtendedJsonInt64Converter RELAXED_JSON_INT_64_CONVERTER = new RelaxedExtendedJsonInt64Converter(); + private static final ShellInt64Converter SHELL_INT_64_CONVERTER = new ShellInt64Converter(); + private static final ExtendedJsonDecimal128Converter EXTENDED_JSON_DECIMAL_128_CONVERTER = new ExtendedJsonDecimal128Converter(); + private static final ShellDecimal128Converter SHELL_DECIMAL_128_CONVERTER = new ShellDecimal128Converter(); + private static final ExtendedJsonObjectIdConverter EXTENDED_JSON_OBJECT_ID_CONVERTER = new ExtendedJsonObjectIdConverter(); + private static final ShellObjectIdConverter SHELL_OBJECT_ID_CONVERTER = new ShellObjectIdConverter(); + private static final ExtendedJsonTimestampConverter EXTENDED_JSON_TIMESTAMP_CONVERTER = new ExtendedJsonTimestampConverter(); + private static final ShellTimestampConverter SHELL_TIMESTAMP_CONVERTER = new ShellTimestampConverter(); + private static final ExtendedJsonRegularExpressionConverter EXTENDED_JSON_REGULAR_EXPRESSION_CONVERTER = + new ExtendedJsonRegularExpressionConverter(); + private static final LegacyExtendedJsonRegularExpressionConverter LEGACY_EXTENDED_JSON_REGULAR_EXPRESSION_CONVERTER = + new LegacyExtendedJsonRegularExpressionConverter(); + private static final ShellRegularExpressionConverter SHELL_REGULAR_EXPRESSION_CONVERTER = new ShellRegularExpressionConverter(); + private final boolean indent; private final String newLineCharacters; private final String indentCharacters; + private final int maxLength; private final JsonMode outputMode; + private final Converter nullConverter; + private final Converter stringConverter; + private final Converter dateTimeConverter; + private final Converter binaryConverter; + private final Converter booleanConverter; + private final Converter doubleConverter; + private final Converter int32Converter; + private final Converter int64Converter; + private final Converter decimal128Converter; + private final Converter objectIdConverter; + private final Converter timestampConverter; + private final Converter regularExpressionConverter; + private final Converter symbolConverter; + private final Converter undefinedConverter; + private final Converter minKeyConverter; + private final Converter maxKeyConverter; + private final Converter javaScriptConverter; + + /** + * Create a builder for JsonWriterSettings, which are immutable. + *

    + * Defaults to {@link JsonMode#RELAXED} + *

    + * + * @return a Builder instance + * @since 3.5 + */ + public static Builder builder() { + return new Builder(); + } /** * Creates a new instance with default values for all properties. + *

    + * Defaults to {@link JsonMode#STRICT} + *

    + * + * @deprecated Prefer {@link #builder()}, but note that the default output mode is different for that method */ + @Deprecated public JsonWriterSettings() { - this(JsonMode.STRICT, false, null, null); + this(builder().outputMode(JsonMode.STRICT)); } /** * Creates a new instance with the given output mode and default values for all other properties. * * @param outputMode the output mode + * @deprecated Use the {@link Builder} instead */ + @Deprecated public JsonWriterSettings(final JsonMode outputMode) { - this(outputMode, false, null, null); + this(builder().outputMode(outputMode)); } /** * Creates a new instance with indent mode enabled, and the default value for all other properties. * * @param indent whether indent mode is enabled + * @deprecated Use the {@link Builder} instead */ + @Deprecated public JsonWriterSettings(final boolean indent) { - this(JsonMode.STRICT, indent, indent ? " " : null, null); + this(builder().indent(indent)); } /** @@ -60,9 +154,11 @@ public JsonWriterSettings(final boolean indent) { * * @param outputMode the output mode * @param indent whether indent mode is enabled + * @deprecated Use the {@link Builder} instead */ + @Deprecated public JsonWriterSettings(final JsonMode outputMode, final boolean indent) { - this(outputMode, indent, indent ? " " : null, null); + this(builder().outputMode(outputMode).indent(indent)); } /** @@ -71,9 +167,11 @@ public JsonWriterSettings(final JsonMode outputMode, final boolean indent) { * * @param outputMode the output mode * @param indentCharacters the indent characters + * @deprecated Use the {@link Builder} instead */ + @Deprecated public JsonWriterSettings(final JsonMode outputMode, final String indentCharacters) { - this(outputMode, true, indentCharacters, null); + this(builder().outputMode(outputMode).indent(true).indentCharacters(indentCharacters)); } /** @@ -82,33 +180,159 @@ public JsonWriterSettings(final JsonMode outputMode, final String indentCharacte * @param outputMode the output mode * @param indentCharacters the indent characters * @param newLineCharacters the new line character(s) to use + * @deprecated Use the {@link Builder} instead */ + @Deprecated public JsonWriterSettings(final JsonMode outputMode, final String indentCharacters, final String newLineCharacters) { - this(outputMode, true, indentCharacters, newLineCharacters); + this(builder().outputMode(outputMode).indent(true).indentCharacters(indentCharacters).newLineCharacters(newLineCharacters)); } - private JsonWriterSettings(final JsonMode outputMode, final boolean indent, final String indentCharacters, - final String newLineCharacters) { - if (indent) { - if (indentCharacters == null) { - throw new IllegalArgumentException("indent characters can not be null if indent is enabled"); - } + @SuppressWarnings("deprecation") + private JsonWriterSettings(final Builder builder) { + indent = builder.indent; + newLineCharacters = builder.newLineCharacters != null ? builder.newLineCharacters : System.getProperty("line.separator"); + indentCharacters = builder.indentCharacters; + outputMode = builder.outputMode; + maxLength = builder.maxLength; + + if (builder.nullConverter != null) { + nullConverter = builder.nullConverter; + } else { + nullConverter = JSON_NULL_CONVERTER; + } + + if (builder.stringConverter != null) { + stringConverter = builder.stringConverter; + } else { + stringConverter = JSON_STRING_CONVERTER; + } + + if (builder.booleanConverter != null) { + booleanConverter = builder.booleanConverter; + } else { + booleanConverter = JSON_BOOLEAN_CONVERTER; + } + + if (builder.doubleConverter != null) { + doubleConverter = builder.doubleConverter; + } else if (outputMode == JsonMode.EXTENDED) { + doubleConverter = EXTENDED_JSON_DOUBLE_CONVERTER; + } else if (outputMode == JsonMode.RELAXED) { + doubleConverter = RELAXED_EXTENDED_JSON_DOUBLE_CONVERTER; + } else { + doubleConverter = JSON_DOUBLE_CONVERTER; + } + + if (builder.int32Converter != null) { + int32Converter = builder.int32Converter; + } else if (outputMode == JsonMode.EXTENDED) { + int32Converter = EXTENDED_JSON_INT_32_CONVERTER; + } + else { + int32Converter = JSON_INT_32_CONVERTER; + } + + if (builder.symbolConverter != null) { + symbolConverter = builder.symbolConverter; + } else { + symbolConverter = JSON_SYMBOL_CONVERTER; + } + + if (builder.javaScriptConverter != null) { + javaScriptConverter = builder.javaScriptConverter; + } else { + javaScriptConverter = new JsonJavaScriptConverter(); + } + + if (builder.minKeyConverter != null) { + minKeyConverter = builder.minKeyConverter; + } else if (outputMode == JsonMode.STRICT || outputMode == JsonMode.EXTENDED || outputMode == JsonMode.RELAXED) { + minKeyConverter = EXTENDED_JSON_MIN_KEY_CONVERTER; + } else { + minKeyConverter = SHELL_MIN_KEY_CONVERTER; + } + + if (builder.maxKeyConverter != null) { + maxKeyConverter = builder.maxKeyConverter; + } else if (outputMode == JsonMode.STRICT || outputMode == JsonMode.EXTENDED || outputMode == JsonMode.RELAXED) { + maxKeyConverter = EXTENDED_JSON_MAX_KEY_CONVERTER; + } else { + maxKeyConverter = SHELL_MAX_KEY_CONVERTER; + } + + if (builder.undefinedConverter != null) { + undefinedConverter = builder.undefinedConverter; + } else if (outputMode == JsonMode.STRICT || outputMode == JsonMode.EXTENDED || outputMode == JsonMode.RELAXED) { + undefinedConverter = EXTENDED_JSON_UNDEFINED_CONVERTER; + } else { + undefinedConverter = SHELL_UNDEFINED_CONVERTER; + } + + if (builder.dateTimeConverter != null) { + dateTimeConverter = builder.dateTimeConverter; + } else if (outputMode == JsonMode.STRICT) { + dateTimeConverter = LEGACY_EXTENDED_JSON_DATE_TIME_CONVERTER; + } else if (outputMode == JsonMode.EXTENDED) { + dateTimeConverter = EXTENDED_JSON_DATE_TIME_CONVERTER; + } else if (outputMode == JsonMode.RELAXED) { + dateTimeConverter = RELAXED_EXTENDED_JSON_DATE_TIME_CONVERTER; + } else { + dateTimeConverter = SHELL_DATE_TIME_CONVERTER; + } + + if (builder.binaryConverter != null) { + binaryConverter = builder.binaryConverter; + } else if (outputMode == JsonMode.STRICT) { + binaryConverter = LEGACY_EXTENDED_JSON_BINARY_CONVERTER; + } else if (outputMode == JsonMode.EXTENDED || outputMode == JsonMode.RELAXED) { + binaryConverter = EXTENDED_JSON_BINARY_CONVERTER; + } else { + binaryConverter = SHELL_BINARY_CONVERTER; + } + + if (builder.int64Converter != null) { + int64Converter = builder.int64Converter; + } else if (outputMode == JsonMode.STRICT || outputMode == JsonMode.EXTENDED) { + int64Converter = EXTENDED_JSON_INT_64_CONVERTER; + } else if (outputMode == JsonMode.RELAXED) { + int64Converter = RELAXED_JSON_INT_64_CONVERTER; + } else { + int64Converter = SHELL_INT_64_CONVERTER; + } + + if (builder.decimal128Converter != null) { + decimal128Converter = builder.decimal128Converter; + } else if (outputMode == JsonMode.STRICT || outputMode == JsonMode.EXTENDED || outputMode == JsonMode.RELAXED) { + decimal128Converter = EXTENDED_JSON_DECIMAL_128_CONVERTER; + } else { + decimal128Converter = SHELL_DECIMAL_128_CONVERTER; + } + + if (builder.objectIdConverter != null) { + objectIdConverter = builder.objectIdConverter; + } else if (outputMode == JsonMode.STRICT || outputMode == JsonMode.EXTENDED || outputMode == JsonMode.RELAXED) { + objectIdConverter = EXTENDED_JSON_OBJECT_ID_CONVERTER; } else { - if (newLineCharacters != null) { - throw new IllegalArgumentException("new line characters can not be null if indent is disabled."); - } - if (indentCharacters != null) { - throw new IllegalArgumentException("indent characters can not be null if indent is disabled."); - } + objectIdConverter = SHELL_OBJECT_ID_CONVERTER; } - if (outputMode == null) { - throw new IllegalArgumentException("output mode can not be null"); + + if (builder.timestampConverter != null) { + timestampConverter = builder.timestampConverter; + } else if (outputMode == JsonMode.STRICT || outputMode == JsonMode.EXTENDED || outputMode == JsonMode.RELAXED) { + timestampConverter = EXTENDED_JSON_TIMESTAMP_CONVERTER; + } else { + timestampConverter = SHELL_TIMESTAMP_CONVERTER; } - this.indent = indent; - this.newLineCharacters = newLineCharacters != null ? newLineCharacters : System.getProperty("line.separator"); - this.indentCharacters = indentCharacters; - this.outputMode = outputMode; + if (builder.regularExpressionConverter != null) { + regularExpressionConverter = builder.regularExpressionConverter; + } else if (outputMode == JsonMode.EXTENDED || outputMode == JsonMode.RELAXED) { + regularExpressionConverter = EXTENDED_JSON_REGULAR_EXPRESSION_CONVERTER; + } else if (outputMode == JsonMode.STRICT) { + regularExpressionConverter = LEGACY_EXTENDED_JSON_REGULAR_EXPRESSION_CONVERTER; + } else { + regularExpressionConverter = SHELL_REGULAR_EXPRESSION_CONVERTER; + } } /** @@ -133,7 +357,7 @@ public String getNewLineCharacters() { /** * The indent characters to use if indent mode is enabled. The default value is two spaces. * - * @return the indent characters to use. + * @return the indent character(s) to use. */ public String getIndentCharacters() { return indentCharacters; @@ -147,4 +371,475 @@ public String getIndentCharacters() { public JsonMode getOutputMode() { return outputMode; } + + /** + * The maximum length of the JSON string. The string will be truncated at this length. + * + * @return the maximum length of the JSON string + * @since 3.7 + */ + public int getMaxLength() { + return maxLength; + } + + /** + * A converter from BSON Null values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getNullConverter() { + return nullConverter; + } + + /** + * A converter from BSON String values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getStringConverter() { + return stringConverter; + } + + /** + * A converter from BSON Binary values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getBinaryConverter() { + return binaryConverter; + } + + /** + * A converter from BSON Boolean values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getBooleanConverter() { + return booleanConverter; + } + + /** + * A converter from BSON DateTime values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getDateTimeConverter() { + return dateTimeConverter; + } + + /** + * A converter from BSON Double values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getDoubleConverter() { + return doubleConverter; + } + + /** + * A converter from BSON Int32 values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getInt32Converter() { + return int32Converter; + } + + /** + * A converter from BSON Int64 values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getInt64Converter() { + return int64Converter; + } + + /** + * A converter from BSON Decimal128 values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getDecimal128Converter() { + return decimal128Converter; + } + + /** + * A converter from BSON ObjectId values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getObjectIdConverter() { + return objectIdConverter; + } + + /** + * A converter from BSON RegularExpression values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getRegularExpressionConverter() { + return regularExpressionConverter; + } + + /** + * A converter from BSON Timestamp values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getTimestampConverter() { + return timestampConverter; + } + + /** + * A converter from BSON Symbol values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getSymbolConverter() { + return symbolConverter; + } + + /** + * A converter from BSON MinKey values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getMinKeyConverter() { + return minKeyConverter; + } + + /** + * A converter from BSON MaxKey values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getMaxKeyConverter() { + return maxKeyConverter; + } + + /** + * A converter from BSON Undefined values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getUndefinedConverter() { + return undefinedConverter; + } + + /** + * A converter from BSON JavaScript values to JSON. + * + * @return this + * @since 3.5 + */ + public Converter getJavaScriptConverter() { + return javaScriptConverter; + } + + /** + * A builder for JsonWriterSettings + * + * @since 3.5 + */ + @SuppressWarnings("deprecation") + public static final class Builder { + private boolean indent; + private String newLineCharacters = System.getProperty("line.separator"); + private String indentCharacters = " "; + private JsonMode outputMode = JsonMode.RELAXED; + private int maxLength; + private Converter nullConverter; + private Converter stringConverter; + private Converter dateTimeConverter; + private Converter binaryConverter; + private Converter booleanConverter; + private Converter doubleConverter; + private Converter int32Converter; + private Converter int64Converter; + private Converter decimal128Converter; + private Converter objectIdConverter; + private Converter timestampConverter; + private Converter regularExpressionConverter; + private Converter symbolConverter; + private Converter undefinedConverter; + private Converter minKeyConverter; + private Converter maxKeyConverter; + private Converter javaScriptConverter; + + /** + * Build a JsonWriterSettings instance. + * + * @return a JsonWriterSettings instance + */ + public JsonWriterSettings build() { + return new JsonWriterSettings(this); + } + + /** + * Sets whether indentation is enabled, which defaults to false. + * + * @param indent whether indentation is enabled + * @return this + */ + public Builder indent(final boolean indent) { + this.indent = indent; + return this; + } + + /** + * Sets the new line character string to use when indentation is enabled, which defaults to + * {@code System.getProperty("line.separator")}. + * + * @param newLineCharacters the non-null new line character string + * @return this + */ + public Builder newLineCharacters(final String newLineCharacters) { + notNull("newLineCharacters", newLineCharacters); + this.newLineCharacters = newLineCharacters; + return this; + } + + /** + * Sets the indent character string to use when indentation is enabled, which defaults to two spaces. + * + * @param indentCharacters the non-null indent character string + * @return this + */ + public Builder indentCharacters(final String indentCharacters) { + notNull("indentCharacters", indentCharacters); + this.indentCharacters = indentCharacters; + return this; + } + + /** + * Sets the output mode, which defaults to {@link JsonMode#RELAXED}. + * + * @param outputMode the non-null output mode + * @return this + */ + public Builder outputMode(final JsonMode outputMode) { + notNull("outputMode", outputMode); + this.outputMode = outputMode; + return this; + } + + /** + * Sets the maximum length of the JSON string. The string will be truncated at this length. + * + * @param maxLength the maximum length, which must be >= 0 where 0 indicate no maximum length + * @return the maximum length of the JSON string + * @since 3.7 + */ + public Builder maxLength(final int maxLength) { + isTrueArgument("maxLength >= 0", maxLength >= 0); + this.maxLength = maxLength; + return this; + } + + /** + * Sets the converter from BSON Null values to JSON. + * + * @param nullConverter the converter + * @return this + */ + public Builder nullConverter(final Converter nullConverter) { + this.nullConverter = nullConverter; + return this; + } + + /** + * Sets the converter from BSON String values to JSON. + * + * @param stringConverter the converter + * @return this + */ + public Builder stringConverter(final Converter stringConverter) { + this.stringConverter = stringConverter; + return this; + } + + /** + * Sets the converter from BSON DateTime values to JSON. + * + * @param dateTimeConverter the converter + * @return this + */ + public Builder dateTimeConverter(final Converter dateTimeConverter) { + this.dateTimeConverter = dateTimeConverter; + return this; + } + + /** + * Sets the converter from BSON Binary values to JSON. + * + * @param binaryConverter the converter + * @return this + */ + public Builder binaryConverter(final Converter binaryConverter) { + this.binaryConverter = binaryConverter; + return this; + } + + /** + * Sets the converter from BSON Boolean values to JSON. + * + * @param booleanConverter the converter + * @return this + */ + public Builder booleanConverter(final Converter booleanConverter) { + this.booleanConverter = booleanConverter; + return this; + } + + /** + * Sets the converter from BSON Double values to JSON. + * + * @param doubleConverter the converter + * @return this + */ + public Builder doubleConverter(final Converter doubleConverter) { + this.doubleConverter = doubleConverter; + return this; + } + + /** + * Sets the converter from BSON Int32 values to JSON. + * + * @param int32Converter the converter + * @return this + */ + public Builder int32Converter(final Converter int32Converter) { + this.int32Converter = int32Converter; + return this; + } + + /** + * Sets the converter from BSON Int64 values to JSON. + * + * @param int64Converter the converter + * @return this + */ + public Builder int64Converter(final Converter int64Converter) { + this.int64Converter = int64Converter; + return this; + } + + /** + * Sets the converter from BSON Decimal128 values to JSON. + * + * @param decimal128Converter the converter + * @return this + */ + public Builder decimal128Converter(final Converter decimal128Converter) { + this.decimal128Converter = decimal128Converter; + return this; + } + + /** + * Sets the converter from BSON ObjectId values to JSON. + * + * @param objectIdConverter the converter + * @return this + */ + public Builder objectIdConverter(final Converter objectIdConverter) { + this.objectIdConverter = objectIdConverter; + return this; + } + + /** + * Sets the converter from BSON Timestamp values to JSON. + * + * @param timestampConverter the converter + * @return this + */ + public Builder timestampConverter(final Converter timestampConverter) { + this.timestampConverter = timestampConverter; + return this; + } + + /** + * Sets the converter from BSON Regular Expression values to JSON. + * + * @param regularExpressionConverter the converter + * @return this + */ + public Builder regularExpressionConverter(final Converter regularExpressionConverter) { + this.regularExpressionConverter = regularExpressionConverter; + return this; + } + + /** + * Sets the converter from BSON Symbol values to JSON. + * + * @param symbolConverter the converter + * @return this + */ + public Builder symbolConverter(final Converter symbolConverter) { + this.symbolConverter = symbolConverter; + return this; + } + + /** + * Sets the converter from BSON MinKey values to JSON. + * + * @param minKeyConverter the converter + * @return this + */ + public Builder minKeyConverter(final Converter minKeyConverter) { + this.minKeyConverter = minKeyConverter; + return this; + } + + /** + * Sets the converter from BSON MaxKey values to JSON. + * + * @param maxKeyConverter the converter + * @return this + */ + public Builder maxKeyConverter(final Converter maxKeyConverter) { + this.maxKeyConverter = maxKeyConverter; + return this; + } + + /** + * Sets the converter from BSON Undefined values to JSON. + * + * @param undefinedConverter the converter + * @return this + */ + public Builder undefinedConverter(final Converter undefinedConverter) { + this.undefinedConverter = undefinedConverter; + return this; + } + + /** + * Sets the converter from BSON JavaScript values to JSON. + * + * @param javaScriptConverter the converter + * @return this + */ + public Builder javaScriptConverter(final Converter javaScriptConverter) { + this.javaScriptConverter = javaScriptConverter; + return this; + } + + private Builder() { + } + } } diff --git a/bson/src/main/org/bson/json/LegacyExtendedJsonBinaryConverter.java b/bson/src/main/org/bson/json/LegacyExtendedJsonBinaryConverter.java new file mode 100644 index 00000000000..7a02e62be18 --- /dev/null +++ b/bson/src/main/org/bson/json/LegacyExtendedJsonBinaryConverter.java @@ -0,0 +1,31 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonBinary; +import org.bson.internal.Base64; + +class LegacyExtendedJsonBinaryConverter implements Converter { + + @Override + public void convert(final BsonBinary value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeString("$binary", Base64.encode(value.getData())); + writer.writeString("$type", String.format("%02X", value.getType())); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/LegacyExtendedJsonDateTimeConverter.java b/bson/src/main/org/bson/json/LegacyExtendedJsonDateTimeConverter.java new file mode 100644 index 00000000000..2fa1692ed7d --- /dev/null +++ b/bson/src/main/org/bson/json/LegacyExtendedJsonDateTimeConverter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class LegacyExtendedJsonDateTimeConverter implements Converter { + @Override + public void convert(final Long value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeNumber("$date", Long.toString(value)); + writer.writeEndObject(); + } + +} diff --git a/bson/src/main/org/bson/json/LegacyExtendedJsonRegularExpressionConverter.java b/bson/src/main/org/bson/json/LegacyExtendedJsonRegularExpressionConverter.java new file mode 100644 index 00000000000..4cb8b0c9b0c --- /dev/null +++ b/bson/src/main/org/bson/json/LegacyExtendedJsonRegularExpressionConverter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonRegularExpression; + +class LegacyExtendedJsonRegularExpressionConverter implements Converter { + @Override + public void convert(final BsonRegularExpression value, final StrictJsonWriter writer) { + writer.writeStartObject(); + writer.writeString("$regex", value.getPattern()); + writer.writeString("$options", value.getOptions()); + writer.writeEndObject(); + } +} diff --git a/bson/src/main/org/bson/json/RelaxedExtendedJsonDateTimeConverter.java b/bson/src/main/org/bson/json/RelaxedExtendedJsonDateTimeConverter.java new file mode 100644 index 00000000000..002c7c1c089 --- /dev/null +++ b/bson/src/main/org/bson/json/RelaxedExtendedJsonDateTimeConverter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class RelaxedExtendedJsonDateTimeConverter implements Converter { + private static final Converter FALLBACK_CONVERTER = new ExtendedJsonDateTimeConverter(); + private static final long LAST_MS_OF_YEAR_9999 = 253402300799999L; + + @Override + public void convert(final Long value, final StrictJsonWriter writer) { + if (value < 0 || value > LAST_MS_OF_YEAR_9999) { + FALLBACK_CONVERTER.convert(value, writer); + } else { + writer.writeStartObject(); + writer.writeString("$date", DateTimeFormatter.format(value)); + writer.writeEndObject(); + } + } +} diff --git a/bson/src/main/org/bson/json/RelaxedExtendedJsonDoubleConverter.java b/bson/src/main/org/bson/json/RelaxedExtendedJsonDoubleConverter.java new file mode 100644 index 00000000000..ac845b2ecd0 --- /dev/null +++ b/bson/src/main/org/bson/json/RelaxedExtendedJsonDoubleConverter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class RelaxedExtendedJsonDoubleConverter implements Converter { + private static final Converter FALLBACK_CONVERTER = new ExtendedJsonDoubleConverter(); + + @Override + public void convert(final Double value, final StrictJsonWriter writer) { + if (value.isNaN() || value.isInfinite()) { + FALLBACK_CONVERTER.convert(value, writer); + } else { + writer.writeNumber(Double.toString(value)); + } + } +} diff --git a/bson/src/main/org/bson/json/RelaxedExtendedJsonInt64Converter.java b/bson/src/main/org/bson/json/RelaxedExtendedJsonInt64Converter.java new file mode 100644 index 00000000000..4a158e28495 --- /dev/null +++ b/bson/src/main/org/bson/json/RelaxedExtendedJsonInt64Converter.java @@ -0,0 +1,24 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +class RelaxedExtendedJsonInt64Converter implements Converter { + @Override + public void convert(final Long value, final StrictJsonWriter writer) { + writer.writeNumber(Long.toString(value)); + } +} diff --git a/bson/src/main/org/bson/json/ShellBinaryConverter.java b/bson/src/main/org/bson/json/ShellBinaryConverter.java new file mode 100644 index 00000000000..cea8fce9878 --- /dev/null +++ b/bson/src/main/org/bson/json/ShellBinaryConverter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonBinary; +import org.bson.internal.Base64; + +import static java.lang.String.format; + +class ShellBinaryConverter implements Converter { + @Override + public void convert(final BsonBinary value, final StrictJsonWriter writer) { + writer.writeRaw(format("new BinData(%s, \"%s\")", Integer.toString(value.getType() & 0xFF), + Base64.encode(value.getData()))); + } +} diff --git a/bson/src/main/org/bson/json/ShellDateTimeConverter.java b/bson/src/main/org/bson/json/ShellDateTimeConverter.java new file mode 100644 index 00000000000..ae024899853 --- /dev/null +++ b/bson/src/main/org/bson/json/ShellDateTimeConverter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import static java.lang.String.format; + + +class ShellDateTimeConverter implements Converter { + @Override + public void convert(final Long value, final StrictJsonWriter writer) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ss.SSS\'Z\'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + if (value >= -59014396800000L && value <= 253399536000000L) { + writer.writeRaw(format("ISODate(\"%s\")", dateFormat.format(new Date(value)))); + } else { + writer.writeRaw(format("new Date(%d)", value)); + } + } +} diff --git a/bson/src/main/org/bson/json/ShellDecimal128Converter.java b/bson/src/main/org/bson/json/ShellDecimal128Converter.java new file mode 100644 index 00000000000..43d235b7417 --- /dev/null +++ b/bson/src/main/org/bson/json/ShellDecimal128Converter.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.types.Decimal128; + +import static java.lang.String.format; + +class ShellDecimal128Converter implements Converter { + @Override + public void convert(final Decimal128 value, final StrictJsonWriter writer) { + writer.writeRaw(format("NumberDecimal(\"%s\")", value.toString())); + } +} diff --git a/bson/src/main/org/bson/json/ShellInt64Converter.java b/bson/src/main/org/bson/json/ShellInt64Converter.java new file mode 100644 index 00000000000..94c9884a744 --- /dev/null +++ b/bson/src/main/org/bson/json/ShellInt64Converter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import static java.lang.String.format; + +class ShellInt64Converter implements Converter { + @Override + public void convert(final Long value, final StrictJsonWriter writer) { + if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) { + writer.writeRaw(format("NumberLong(%d)", value)); + } else { + writer.writeRaw(format("NumberLong(\"%d\")", value)); + } + } +} diff --git a/bson/src/main/org/bson/json/ShellMaxKeyConverter.java b/bson/src/main/org/bson/json/ShellMaxKeyConverter.java new file mode 100644 index 00000000000..0f3b1ae23d1 --- /dev/null +++ b/bson/src/main/org/bson/json/ShellMaxKeyConverter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonMaxKey; + +class ShellMaxKeyConverter implements Converter { + @Override + public void convert(final BsonMaxKey value, final StrictJsonWriter writer) { + writer.writeRaw("MaxKey"); + } +} diff --git a/bson/src/main/org/bson/json/ShellMinKeyConverter.java b/bson/src/main/org/bson/json/ShellMinKeyConverter.java new file mode 100644 index 00000000000..f996f5b3e8c --- /dev/null +++ b/bson/src/main/org/bson/json/ShellMinKeyConverter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonMinKey; + +class ShellMinKeyConverter implements Converter { + @Override + public void convert(final BsonMinKey value, final StrictJsonWriter writer) { + writer.writeRaw("MinKey"); + } +} diff --git a/bson/src/main/org/bson/json/ShellObjectIdConverter.java b/bson/src/main/org/bson/json/ShellObjectIdConverter.java new file mode 100644 index 00000000000..9a8fc9e6f7b --- /dev/null +++ b/bson/src/main/org/bson/json/ShellObjectIdConverter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.types.ObjectId; + +import static java.lang.String.format; + +class ShellObjectIdConverter implements Converter { + @Override + public void convert(final ObjectId value, final StrictJsonWriter writer) { + writer.writeRaw(format("ObjectId(\"%s\")", value.toHexString())); + + } +} diff --git a/bson/src/main/org/bson/json/ShellRegularExpressionConverter.java b/bson/src/main/org/bson/json/ShellRegularExpressionConverter.java new file mode 100644 index 00000000000..2deb44213a6 --- /dev/null +++ b/bson/src/main/org/bson/json/ShellRegularExpressionConverter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonRegularExpression; + +class ShellRegularExpressionConverter implements Converter { + @Override + public void convert(final BsonRegularExpression value, final StrictJsonWriter writer) { + String escaped = (value.getPattern().equals("")) ? "(?:)" : value.getPattern().replace("/", "\\/"); + writer.writeRaw("/" + escaped + "/" + value.getOptions()); + } +} diff --git a/bson/src/main/org/bson/json/ShellTimestampConverter.java b/bson/src/main/org/bson/json/ShellTimestampConverter.java new file mode 100644 index 00000000000..1d767ed1372 --- /dev/null +++ b/bson/src/main/org/bson/json/ShellTimestampConverter.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonTimestamp; + +import static java.lang.String.format; + +class ShellTimestampConverter implements Converter { + @Override + public void convert(final BsonTimestamp value, final StrictJsonWriter writer) { + writer.writeRaw(format("Timestamp(%d, %d)", value.getTime(), value.getInc())); + } +} diff --git a/bson/src/main/org/bson/json/ShellUndefinedConverter.java b/bson/src/main/org/bson/json/ShellUndefinedConverter.java new file mode 100644 index 00000000000..297594dadc5 --- /dev/null +++ b/bson/src/main/org/bson/json/ShellUndefinedConverter.java @@ -0,0 +1,26 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BsonUndefined; + +class ShellUndefinedConverter implements Converter { + @Override + public void convert(final BsonUndefined value, final StrictJsonWriter writer) { + writer.writeRaw("undefined"); + } +} diff --git a/bson/src/main/org/bson/json/StrictCharacterStreamJsonWriter.java b/bson/src/main/org/bson/json/StrictCharacterStreamJsonWriter.java new file mode 100644 index 00000000000..cce8af2fa17 --- /dev/null +++ b/bson/src/main/org/bson/json/StrictCharacterStreamJsonWriter.java @@ -0,0 +1,402 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.bson.BSONException; +import org.bson.BsonInvalidOperationException; + +import java.io.IOException; +import java.io.Writer; + +import static org.bson.assertions.Assertions.notNull; + +/** + * A class that writes JSON texts as a character stream via a provided {@link Writer}. + * + * @since 3.5 + */ +public final class StrictCharacterStreamJsonWriter implements StrictJsonWriter { + private enum JsonContextType { + TOP_LEVEL, + DOCUMENT, + ARRAY, + } + + private enum State { + INITIAL, + NAME, + VALUE, + DONE + } + + private static class StrictJsonContext { + private final StrictJsonContext parentContext; + private final JsonContextType contextType; + private final String indentation; + private boolean hasElements; + + StrictJsonContext(final StrictJsonContext parentContext, final JsonContextType contextType, final String indentChars) { + this.parentContext = parentContext; + this.contextType = contextType; + this.indentation = (parentContext == null) ? indentChars : parentContext.indentation + indentChars; + } + } + + private final Writer writer; + private final StrictCharacterStreamJsonWriterSettings settings; + private StrictJsonContext context = new StrictJsonContext(null, JsonContextType.TOP_LEVEL, ""); + private State state = State.INITIAL; + private int curLength; + private boolean isTruncated; + + /** + * Construct an instance. + * + * @param writer the writer to write JSON to. + * @param settings the settings to apply to this writer. + */ + public StrictCharacterStreamJsonWriter(final Writer writer, final StrictCharacterStreamJsonWriterSettings settings) { + this.writer = writer; + this.settings = settings; + } + + /** + * Gets the current length of the JSON text. + * + * @return the current length of the JSON text + */ + public int getCurrentLength() { + return curLength; + } + + @Override + public void writeStartObject(final String name) { + writeName(name); + writeStartObject(); + } + + @Override + public void writeStartArray(final String name) { + writeName(name); + writeStartArray(); + } + + @Override + public void writeBoolean(final String name, final boolean value) { + notNull("name", name); + writeName(name); + writeBoolean(value); + } + + @Override + public void writeNumber(final String name, final String value) { + notNull("name", name); + notNull("value", value); + writeName(name); + writeNumber(value); + } + + @Override + public void writeString(final String name, final String value) { + notNull("name", name); + notNull("value", value); + writeName(name); + writeString(value); + } + + @Override + public void writeRaw(final String name, final String value) { + notNull("name", name); + notNull("value", value); + writeName(name); + writeRaw(value); + } + + @Override + public void writeNull(final String name) { + writeName(name); + writeNull(); + } + + @Override + public void writeName(final String name) { + notNull("name", name); + checkState(State.NAME); + + if (context.hasElements) { + write(","); + } + if (settings.isIndent()) { + write(settings.getNewLineCharacters()); + write(context.indentation); + } else if (context.hasElements){ + write(" "); + } + writeStringHelper(name); + write(": "); + + state = State.VALUE; + } + + @Override + public void writeBoolean(final boolean value) { + checkState(State.VALUE); + preWriteValue(); + write(value ? "true" : "false"); + setNextState(); + } + + @Override + public void writeNumber(final String value) { + notNull("value", value); + checkState(State.VALUE); + preWriteValue(); + write(value); + setNextState(); + } + + @Override + public void writeString(final String value) { + notNull("value", value); + checkState(State.VALUE); + preWriteValue(); + writeStringHelper(value); + setNextState(); + } + + @Override + public void writeRaw(final String value) { + notNull("value", value); + checkState(State.VALUE); + preWriteValue(); + write(value); + setNextState(); + } + + @Override + public void writeNull() { + checkState(State.VALUE); + preWriteValue(); + write("null"); + setNextState(); + } + + @Override + public void writeStartObject() { + if (state != State.INITIAL && state != State.VALUE) { + throw new BsonInvalidOperationException("Invalid state " + state); + } + preWriteValue(); + write("{"); + context = new StrictJsonContext(context, JsonContextType.DOCUMENT, settings.getIndentCharacters()); + state = State.NAME; + } + + @Override + public void writeStartArray() { + preWriteValue(); + write("["); + context = new StrictJsonContext(context, JsonContextType.ARRAY, settings.getIndentCharacters()); + state = State.VALUE; + } + + @Override + public void writeEndObject() { + checkState(State.NAME); + + if (settings.isIndent() && context.hasElements) { + write(settings.getNewLineCharacters()); + write(context.parentContext.indentation); + } + write("}"); + context = context.parentContext; + if (context.contextType == JsonContextType.TOP_LEVEL) { + state = State.DONE; + } else { + setNextState(); + } + } + + @Override + public void writeEndArray() { + checkState(State.VALUE); + + if (context.contextType != JsonContextType.ARRAY) { + throw new BsonInvalidOperationException("Can't end an array if not in an array"); + } + + if (settings.isIndent() && context.hasElements) { + write(settings.getNewLineCharacters()); + write(context.parentContext.indentation); + } + write("]"); + context = context.parentContext; + if (context.contextType == JsonContextType.TOP_LEVEL) { + state = State.DONE; + } else { + setNextState(); + } + } + + /** + * Return true if the output has been truncated due to exceeding the length specified in + * {@link StrictCharacterStreamJsonWriterSettings#getMaxLength()}. + * + * @return true if the output has been truncated + * @since 3.7 + * @see StrictCharacterStreamJsonWriterSettings#getMaxLength() + */ + public boolean isTruncated() { + return isTruncated; + } + + void flush() { + try { + writer.flush(); + } catch (IOException e) { + throwBSONException(e); + } + } + + Writer getWriter() { + return writer; + } + + private void preWriteValue() { + if (context.contextType == JsonContextType.ARRAY) { + if (context.hasElements) { + write(","); + } + if (settings.isIndent()) { + write(settings.getNewLineCharacters()); + write(context.indentation); + } else if (context.hasElements) { + write(" "); + } + } + context.hasElements = true; + } + + private void setNextState() { + if (context.contextType == JsonContextType.ARRAY) { + state = State.VALUE; + } else { + state = State.NAME; + } + } + + private void writeStringHelper(final String str) { + write('"'); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + switch (c) { + case '"': + write("\\\""); + break; + case '\\': + write("\\\\"); + break; + case '\b': + write("\\b"); + break; + case '\f': + write("\\f"); + break; + case '\n': + write("\\n"); + break; + case '\r': + write("\\r"); + break; + case '\t': + write("\\t"); + break; + default: + switch (Character.getType(c)) { + case Character.UPPERCASE_LETTER: + case Character.LOWERCASE_LETTER: + case Character.TITLECASE_LETTER: + case Character.OTHER_LETTER: + case Character.DECIMAL_DIGIT_NUMBER: + case Character.LETTER_NUMBER: + case Character.OTHER_NUMBER: + case Character.SPACE_SEPARATOR: + case Character.CONNECTOR_PUNCTUATION: + case Character.DASH_PUNCTUATION: + case Character.START_PUNCTUATION: + case Character.END_PUNCTUATION: + case Character.INITIAL_QUOTE_PUNCTUATION: + case Character.FINAL_QUOTE_PUNCTUATION: + case Character.OTHER_PUNCTUATION: + case Character.MATH_SYMBOL: + case Character.CURRENCY_SYMBOL: + case Character.MODIFIER_SYMBOL: + case Character.OTHER_SYMBOL: + write(c); + break; + default: + write("\\u"); + write(Integer.toHexString((c & 0xf000) >> 12)); + write(Integer.toHexString((c & 0x0f00) >> 8)); + write(Integer.toHexString((c & 0x00f0) >> 4)); + write(Integer.toHexString(c & 0x000f)); + break; + } + break; + } + } + write('"'); + } + + private void write(final String str) { + try { + if (settings.getMaxLength() == 0 || str.length() + curLength < settings.getMaxLength()) { + writer.write(str); + curLength += str.length(); + } else { + writer.write(str.substring(0, settings.getMaxLength() - curLength)); + curLength = settings.getMaxLength(); + isTruncated = true; + } + } catch (IOException e) { + throwBSONException(e); + } + } + + private void write(final char c) { + try { + if (settings.getMaxLength() == 0 || curLength < settings.getMaxLength()) { + writer.write(c); + curLength++; + } else { + isTruncated = true; + } + } catch (IOException e) { + throwBSONException(e); + } + } + + private void checkState(final State requiredState) { + if (state != requiredState) { + throw new BsonInvalidOperationException("Invalid state " + state); + } + } + + private void throwBSONException(final IOException e) { + throw new BSONException("Wrapping IOException", e); + } +} diff --git a/bson/src/main/org/bson/json/StrictCharacterStreamJsonWriterSettings.java b/bson/src/main/org/bson/json/StrictCharacterStreamJsonWriterSettings.java new file mode 100644 index 00000000000..e395e3ee52c --- /dev/null +++ b/bson/src/main/org/bson/json/StrictCharacterStreamJsonWriterSettings.java @@ -0,0 +1,158 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import static org.bson.assertions.Assertions.notNull; + +/** + * Settings to control the behavior of a {@code JSONWriter} instance. + * + * @see StrictCharacterStreamJsonWriter + * @since 3.5 + */ +public final class StrictCharacterStreamJsonWriterSettings { + + private final boolean indent; + private final String newLineCharacters; + private final String indentCharacters; + private final int maxLength; + + /** + * Create a builder for StrictCharacterStreamJsonWriterSettings, which are immutable. + * + * @return a Builder instance + */ + public static Builder builder() { + return new Builder(); + } + + private StrictCharacterStreamJsonWriterSettings(final Builder builder) { + indent = builder.indent; + newLineCharacters = builder.newLineCharacters != null ? builder.newLineCharacters : System.getProperty("line.separator"); + indentCharacters = builder.indentCharacters; + maxLength = builder.maxLength; + } + + /** + * The indentation mode. If true, output will be indented. Otherwise, it will all be on the same line. The default value is {@code + * false}. + * + * @return whether output should be indented. + */ + public boolean isIndent() { + return indent; + } + + /** + * The new line character(s) to use if indent mode is enabled. The default value is {@code System.getProperty("line.separator")}. + * + * @return the new line character(s) to use. + */ + public String getNewLineCharacters() { + return newLineCharacters; + } + + /** + * The indent characters to use if indent mode is enabled. The default value is two spaces. + * + * @return the indent character(s) to use. + */ + public String getIndentCharacters() { + return indentCharacters; + } + + /** + * The maximum length of the JSON string. The string will be truncated at this length. + * + * @return the maximum length of the JSON string + * @since 3.7 + */ + public int getMaxLength() { + return maxLength; + } + + /** + * A builder for StrictCharacterStreamJsonWriterSettings + * + * @since 3.4 + */ + public static final class Builder { + private boolean indent; + private String newLineCharacters = System.getProperty("line.separator"); + private String indentCharacters = " "; + private int maxLength; + + /** + * Build a JsonWriterSettings instance. + * + * @return a JsonWriterSettings instance + */ + public StrictCharacterStreamJsonWriterSettings build() { + return new StrictCharacterStreamJsonWriterSettings(this); + } + + /** + * Sets whether indentation is enabled. + * + * @param indent whether indentation is enabled + * @return this + */ + public Builder indent(final boolean indent) { + this.indent = indent; + return this; + } + + /** + * Sets the new line character string to use when indentation is enabled. + * + * @param newLineCharacters the non-null new line character string + * @return this + */ + public Builder newLineCharacters(final String newLineCharacters) { + notNull("newLineCharacters", newLineCharacters); + this.newLineCharacters = newLineCharacters; + return this; + } + + /** + * Sets the indent character string to use when indentation is enabled. + * + * @param indentCharacters the non-null indent character string + * @return this + */ + public Builder indentCharacters(final String indentCharacters) { + notNull("indentCharacters", indentCharacters); + this.indentCharacters = indentCharacters; + return this; + } + + /** + * Sets the maximum length of the JSON string. The string will be truncated at this length. + * + * @param maxLength the maximum length, which must be >= 0 where 0 indicate no maximum length + * @return the maximum length of the JSON string + * @since 3.7 + */ + public Builder maxLength(final int maxLength) { + this.maxLength = maxLength; + return this; + } + + private Builder() { + } + } +} diff --git a/bson/src/main/org/bson/json/StrictJsonWriter.java b/bson/src/main/org/bson/json/StrictJsonWriter.java new file mode 100644 index 00000000000..7c75849025b --- /dev/null +++ b/bson/src/main/org/bson/json/StrictJsonWriter.java @@ -0,0 +1,184 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +/** + * An interface for creating JSON texts that largely conform to RFC 7159. + * + * @since 3.5 + */ +public interface StrictJsonWriter { + /** + * Writes the name of a member to the writer. + * + * @param name the member name + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a member name + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeName(String name); + + /** + * Writes a boolean to the writer. + * + * @param value the boolean value. + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a value + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeBoolean(boolean value); + + /** + * Writes a a member with a boolean value to the writer. + * + * @param name the member name + * @param value the boolean value + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a member + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeBoolean(String name, boolean value); + + /** + * Writes a number to the writer. + * + * @param value the Double value, as a String so that clients can take full control over formatting + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a value + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeNumber(String value); + + /** + * Writes a member with a numeric value to the writer. + * + * @param name the member name + * @param value the Double value, as a String so that clients can take full control over formatting + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a member + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeNumber(String name, String value); + + /** + * Writes a String to the writer. + * + * @param value the String value + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a value + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeString(String value); + + /** + * Writes a member with a string value to the writer. + * + * @param name the member name + * @param value the String value + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a member + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeString(String name, String value); + + /** + * Writes a raw value without quoting or escaping. + * + * @param value the String value + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a value + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeRaw(String value); + + /** + * Writes a member with a raw value without quoting or escaping. + * + * @param name the member name + * @param value the raw value + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a member + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeRaw(String name, String value); + + /** + * Writes a null value to the writer. + * + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a value + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeNull(); + + /** + * Writes a member with a null value to the writer. + * + * @param name the member name + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a member + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeNull(String name); + + /** + * Writes the start of a array to the writer. + * + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a value + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeStartArray(); + + /** + * Writes the start of JSON array member to the writer. + * + * @param name the member name + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a member + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeStartArray(String name); + + /** + * Writes the start of a JSON object to the writer. + * + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a value + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeStartObject(); + + /** + * Writes the start of a JSON object member to the writer. + * + * @param name the member name + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write a member + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeStartObject(String name); + + /** + * Writes the end of a JSON array to the writer. + * + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write the end of an array + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeEndArray(); + + /** + * Writes the end of a JSON object to the writer. + * + * @throws org.bson.BsonInvalidOperationException if not in the correct state to write the end of an object + * @throws org.bson.BSONException if the underlying Writer throws an IOException + */ + void writeEndObject(); + + /** + * Return true if the output has been truncated due to exceeding any maximum length specified in settings. + * + * @return true if the output has been truncated + * @since 3.7 + */ + boolean isTruncated(); +} diff --git a/bson/src/main/org/bson/json/package-info.java b/bson/src/main/org/bson/json/package-info.java index 5b0aadf45ad..05c872db6c7 100644 --- a/bson/src/main/org/bson/json/package-info.java +++ b/bson/src/main/org/bson/json/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/package-info.java b/bson/src/main/org/bson/package-info.java index 879333c10a1..d02567e5d9d 100644 --- a/bson/src/main/org/bson/package-info.java +++ b/bson/src/main/org/bson/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/types/BSONTimestamp.java b/bson/src/main/org/bson/types/BSONTimestamp.java similarity index 98% rename from driver/src/main/org/bson/types/BSONTimestamp.java rename to bson/src/main/org/bson/types/BSONTimestamp.java index d3dcd742cdf..8979f777867 100644 --- a/driver/src/main/org/bson/types/BSONTimestamp.java +++ b/bson/src/main/org/bson/types/BSONTimestamp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/types/BasicBSONList.java b/bson/src/main/org/bson/types/BasicBSONList.java similarity index 99% rename from driver/src/main/org/bson/types/BasicBSONList.java rename to bson/src/main/org/bson/types/BasicBSONList.java index 394b4b102ae..9a76f0cc975 100644 --- a/driver/src/main/org/bson/types/BasicBSONList.java +++ b/bson/src/main/org/bson/types/BasicBSONList.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/types/Binary.java b/bson/src/main/org/bson/types/Binary.java index c073ee36365..ac67bb878f4 100644 --- a/bson/src/main/org/bson/types/Binary.java +++ b/bson/src/main/org/bson/types/Binary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/types/Code.java b/bson/src/main/org/bson/types/Code.java index b779ef09cdc..1f8cbb01cbf 100644 --- a/bson/src/main/org/bson/types/Code.java +++ b/bson/src/main/org/bson/types/Code.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/types/CodeWScope.java b/bson/src/main/org/bson/types/CodeWScope.java similarity index 97% rename from driver/src/main/org/bson/types/CodeWScope.java rename to bson/src/main/org/bson/types/CodeWScope.java index 1e011b81be2..b46591b2efa 100644 --- a/driver/src/main/org/bson/types/CodeWScope.java +++ b/bson/src/main/org/bson/types/CodeWScope.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/types/CodeWithScope.java b/bson/src/main/org/bson/types/CodeWithScope.java index 36ee1e21bba..7c5fe75f8bd 100644 --- a/bson/src/main/org/bson/types/CodeWithScope.java +++ b/bson/src/main/org/bson/types/CodeWithScope.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/types/Decimal128.java b/bson/src/main/org/bson/types/Decimal128.java new file mode 100644 index 00000000000..f88b07a80ce --- /dev/null +++ b/bson/src/main/org/bson/types/Decimal128.java @@ -0,0 +1,624 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.types; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashSet; +import java.util.Set; + +import static java.math.MathContext.DECIMAL128; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +/** + * A binary integer decimal representation of a 128-bit decimal value, supporting 34 decimal digits of significand and an exponent range + * of -6143 to +6144. + * + * @since 3.4 + * @see BSON Decimal128 + * specification + * @see binary integer decimal + * @see decimal128 floating-point format + * @see 754-2008 - IEEE Standard for Floating-Point Arithmetic + */ +public final class Decimal128 extends Number implements Comparable { + + private static final long serialVersionUID = 4570973266503637887L; + + private static final long INFINITY_MASK = 0x7800000000000000L; + private static final long NaN_MASK = 0x7c00000000000000L; + private static final long SIGN_BIT_MASK = 1L << 63; + private static final int MIN_EXPONENT = -6176; + private static final int MAX_EXPONENT = 6111; + + private static final int EXPONENT_OFFSET = 6176; + private static final int MAX_BIT_LENGTH = 113; + + private static final BigInteger BIG_INT_TEN = new BigInteger("10"); + private static final BigInteger BIG_INT_ONE = new BigInteger("1"); + private static final BigInteger BIG_INT_ZERO = new BigInteger("0"); + + private static final Set NaN_STRINGS = new HashSet(singletonList("nan")); + private static final Set NEGATIVE_NaN_STRINGS = new HashSet(singletonList("-nan")); + private static final Set POSITIVE_INFINITY_STRINGS = new HashSet(asList("inf", "+inf", "infinity", "+infinity")); + private static final Set NEGATIVE_INFINITY_STRINGS = new HashSet(asList("-inf", "-infinity")); + + /** + * A constant holding the positive infinity of type {@code Decimal128}. It is equal to the value return by + * {@code Decimal128.valueOf("Infinity")}. + */ + public static final Decimal128 POSITIVE_INFINITY = fromIEEE754BIDEncoding(INFINITY_MASK, 0); + + /** + * A constant holding the negative infinity of type {@code Decimal128}. It is equal to the value return by + * {@code Decimal128.valueOf("-Infinity")}. + */ + public static final Decimal128 NEGATIVE_INFINITY = fromIEEE754BIDEncoding(INFINITY_MASK | SIGN_BIT_MASK, 0); + + /** + * A constant holding a negative Not-a-Number (-NaN) value of type {@code Decimal128}. It is equal to the value return by + * {@code Decimal128.valueOf("-NaN")}. + */ + public static final Decimal128 NEGATIVE_NaN = fromIEEE754BIDEncoding(NaN_MASK | SIGN_BIT_MASK, 0); + + /** + * A constant holding a Not-a-Number (NaN) value of type {@code Decimal128}. It is equal to the value return by + * {@code Decimal128.valueOf("NaN")}. + */ + public static final Decimal128 NaN = fromIEEE754BIDEncoding(NaN_MASK, 0); + + /** + * A constant holding a postive zero value of type {@code Decimal128}. It is equal to the value return by + * {@code Decimal128.valueOf("0")}. + */ + public static final Decimal128 POSITIVE_ZERO = fromIEEE754BIDEncoding(0x3040000000000000L, 0x0000000000000000L); + + /** + * A constant holding a negative zero value of type {@code Decimal128}. It is equal to the value return by + * {@code Decimal128.valueOf("-0")}. + */ + public static final Decimal128 NEGATIVE_ZERO = fromIEEE754BIDEncoding(0xb040000000000000L, 0x0000000000000000L); + + private final long high; + private final long low; + + /** + * Returns a Decimal128 value representing the given String. + * + * @param value the Decimal128 value represented as a String + * @return the Decimal128 value representing the given String + * @throws NumberFormatException if the value is out of the Decimal128 range + * @see + * + * From-String Specification + */ + public static Decimal128 parse(final String value) { + String lowerCasedValue = value.toLowerCase(); + + if (NaN_STRINGS.contains(lowerCasedValue)) { + return NaN; + } + if (NEGATIVE_NaN_STRINGS.contains(lowerCasedValue)) { + return NEGATIVE_NaN; + } + if (POSITIVE_INFINITY_STRINGS.contains(lowerCasedValue)) { + return POSITIVE_INFINITY; + } + if (NEGATIVE_INFINITY_STRINGS.contains(lowerCasedValue)) { + return NEGATIVE_INFINITY; + } + return new Decimal128(new BigDecimal(value), value.charAt(0) == '-'); + } + + /** + * Create an instance with the given high and low order bits representing this Decimal128 as an IEEE 754-2008 128-bit decimal + * floating point using the BID encoding scheme. + * + * @param high the high-order 64 bits + * @param low the low-order 64 bits + * @return the Decimal128 value representing the given high and low order bits + */ + public static Decimal128 fromIEEE754BIDEncoding(final long high, final long low) { + return new Decimal128(high, low); + } + + /** + * Constructs a Decimal128 value representing the given long. + * + * @param value the Decimal128 value represented as a long + */ + public Decimal128(final long value) { + this(new BigDecimal(value, DECIMAL128)); + } + + /** + * Constructs a Decimal128 value representing the given BigDecimal. + * + * @param value the Decimal128 value represented as a BigDecimal + * @throws NumberFormatException if the value is out of the Decimal128 range + */ + public Decimal128(final BigDecimal value) { + this(value, value.signum() == -1); + } + + private Decimal128(final long high, final long low) { + this.high = high; + this.low = low; + } + + // isNegative is necessary to detect -0, which can't be represented with a BigDecimal + private Decimal128(final BigDecimal initialValue, final boolean isNegative) { + long localHigh = 0; + long localLow = 0; + + BigDecimal value = clampAndRound(initialValue); + + long exponent = -value.scale(); + + if ((exponent < MIN_EXPONENT) || (exponent > MAX_EXPONENT)) { + throw new AssertionError("Exponent is out of range for Decimal128 encoding: " + exponent); } + + if (value.unscaledValue().bitLength() > MAX_BIT_LENGTH) { + throw new AssertionError("Unscaled roundedValue is out of range for Decimal128 encoding:" + value.unscaledValue()); + } + + BigInteger significand = value.unscaledValue().abs(); + int bitLength = significand.bitLength(); + + for (int i = 0; i < Math.min(64, bitLength); i++) { + if (significand.testBit(i)) { + localLow |= 1L << i; + } + } + + for (int i = 64; i < bitLength; i++) { + if (significand.testBit(i)) { + localHigh |= 1L << (i - 64); + } + } + + long biasedExponent = exponent + EXPONENT_OFFSET; + + localHigh |= biasedExponent << 49; + + if (value.signum() == -1 || isNegative) { + localHigh |= SIGN_BIT_MASK; + } + + high = localHigh; + low = localLow; + } + + private BigDecimal clampAndRound(final BigDecimal initialValue) { + BigDecimal value; + if (-initialValue.scale() > MAX_EXPONENT) { + int diff = -initialValue.scale() - MAX_EXPONENT; + if (initialValue.unscaledValue().equals(BIG_INT_ZERO)) { + value = new BigDecimal(initialValue.unscaledValue(), -MAX_EXPONENT); + } else if (diff + initialValue.precision() > 34) { + throw new NumberFormatException("Exponent is out of range for Decimal128 encoding of " + initialValue); + } else { + BigInteger multiplier = BIG_INT_TEN.pow(diff); + value = new BigDecimal(initialValue.unscaledValue().multiply(multiplier), initialValue.scale() + diff); + } + } else if (-initialValue.scale() < MIN_EXPONENT) { + // Increasing a very negative exponent may require decreasing precision, which is rounding + // Only round exactly (by removing precision that is all zeroes). An exception is thrown if the rounding would be inexact: + // Exact: .000...0011000 => 11000E-6177 => 1100E-6176 => .000001100 + // Inexact: .000...0011001 => 11001E-6177 => 1100E-6176 => .000001100 + int diff = initialValue.scale() + MIN_EXPONENT; + int undiscardedPrecision = ensureExactRounding(initialValue, diff); + BigInteger divisor = undiscardedPrecision == 0 ? BIG_INT_ONE : BIG_INT_TEN.pow(diff); + value = new BigDecimal(initialValue.unscaledValue().divide(divisor), initialValue.scale() - diff); + } else { + value = initialValue.round(DECIMAL128); + int extraPrecision = initialValue.precision() - value.precision(); + if (extraPrecision > 0) { + // Again, only round exactly + ensureExactRounding(initialValue, extraPrecision); + } + } + return value; + } + + private int ensureExactRounding(final BigDecimal initialValue, final int extraPrecision) { + String significand = initialValue.unscaledValue().abs().toString(); + int undiscardedPrecision = Math.max(0, significand.length() - extraPrecision); + for (int i = undiscardedPrecision; i < significand.length(); i++) { + if (significand.charAt(i) != '0') { + throw new NumberFormatException("Conversion to Decimal128 would require inexact rounding of " + initialValue); + } + } + return undiscardedPrecision; + } + + /** + * Gets the high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding + * scheme. + * + * @return the high-order 64 bits of this Decimal128 + */ + public long getHigh() { + return high; + } + + /** + * Gets the low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding + * scheme. + * + * @return the low-order 64 bits of this Decimal128 + */ + public long getLow() { + return low; + } + + /** + * Gets a BigDecimal that is equivalent to this Decimal128. + * + * @return a BigDecimal that is equivalent to this Decimal128 + * @throws ArithmeticException if the Decimal128 value is NaN, Infinity, -Infinity, or -0, none of which can be represented as a + * BigDecimal + */ + public BigDecimal bigDecimalValue() { + + if (isNaN()) { + throw new ArithmeticException("NaN can not be converted to a BigDecimal"); + } + + if (isInfinite()) { + throw new ArithmeticException("Infinity can not be converted to a BigDecimal"); + } + + BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck(); + + // If the BigDecimal is 0, but the Decimal128 is negative, that means we have -0. + if (isNegative() && bigDecimal.signum() == 0) { + throw new ArithmeticException("Negative zero can not be converted to a BigDecimal"); + } + + return bigDecimal; + } + + // Make sure that the argument comes from a call to bigDecimalValueNoNegativeZeroCheck on this instance + private boolean hasDifferentSign(final BigDecimal bigDecimal) { + return isNegative() && bigDecimal.signum() == 0; + } + + private boolean isZero(final BigDecimal bigDecimal) { + return !isNaN() && !isInfinite() && bigDecimal.compareTo(BigDecimal.ZERO) == 0; + } + + private BigDecimal bigDecimalValueNoNegativeZeroCheck() { + int scale = -getExponent(); + + if (twoHighestCombinationBitsAreSet()) { + return BigDecimal.valueOf(0, scale); + } + + return new BigDecimal(new BigInteger(isNegative() ? -1 : 1, getBytes()), scale); + } + + // May have leading zeros. Strip them before considering making this method public + private byte[] getBytes() { + byte[] bytes = new byte[15]; + + long mask = 0x00000000000000ff; + for (int i = 14; i >= 7; i--) { + bytes[i] = (byte) ((low & mask) >>> ((14 - i) << 3)); + mask = mask << 8; + } + + mask = 0x00000000000000ff; + for (int i = 6; i >= 1; i--) { + bytes[i] = (byte) ((high & mask) >>> ((6 - i) << 3)); + mask = mask << 8; + } + + mask = 0x0001000000000000L; + bytes[0] = (byte) ((high & mask) >>> 48); + return bytes; + } + + private int getExponent() { + if (twoHighestCombinationBitsAreSet()) { + return (int) ((high & 0x1fffe00000000000L) >>> 47) - EXPONENT_OFFSET; + } else { + return (int) ((high & 0x7fff800000000000L) >>> 49) - EXPONENT_OFFSET; + } + } + + private boolean twoHighestCombinationBitsAreSet() { + return (high & 3L << 61) == 3L << 61; + } + + /** + * Returns true if this Decimal128 is negative. + * + * @return true if this Decimal128 is negative + */ + public boolean isNegative() { + return (high & SIGN_BIT_MASK) == SIGN_BIT_MASK; + } + + /** + * Returns true if this Decimal128 is infinite. + * + * @return true if this Decimal128 is infinite + */ + public boolean isInfinite() { + return (high & INFINITY_MASK) == INFINITY_MASK; + } + + /** + * Returns true if this Decimal128 is finite. + * + * @return true if this Decimal128 is finite + */ + public boolean isFinite() { + return !isInfinite(); + } + + /** + * Returns true if this Decimal128 is Not-A-Number (NaN). + * + * @return true if this Decimal128 is Not-A-Number + */ + public boolean isNaN() { + return (high & NaN_MASK) == NaN_MASK; + } + + + @Override + public int compareTo(final Decimal128 o) { + if (isNaN()) { + return o.isNaN() ? 0 : 1; + } + if (isInfinite()) { + if (isNegative()) { + if (o.isInfinite() && o.isNegative()) { + return 0; + } else { + return -1; + } + } else { + if (o.isNaN()) { + return -1; + } else if (o.isInfinite() && !o.isNegative()) { + return 0; + } else { + return 1; + } + } + } + BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck(); + BigDecimal otherBigDecimal = o.bigDecimalValueNoNegativeZeroCheck(); + + if (isZero(bigDecimal) && o.isZero(otherBigDecimal)) { + if (hasDifferentSign(bigDecimal)) { + if (o.hasDifferentSign(otherBigDecimal)) { + return 0; + } + else { + return -1; + } + } else if (o.hasDifferentSign(otherBigDecimal)) { + return 1; + } + } + + if (o.isNaN()) { + return -1; + } else if (o.isInfinite()) { + if (o.isNegative()) { + return 1; + } else { + return -1; + } + } else { + return bigDecimal.compareTo(otherBigDecimal); + } + } + + /** + * Converts this {@code Decimal128} to a {@code int}. This conversion is analogous to the narrowing primitive conversion from + * {@code double} to {@code int} as defined in The Java™ Language Specification: any fractional part of this + * {@code Decimal128} will be discarded, and if the resulting integral value is too big to fit in a {@code int}, only the + * low-order 32 bits are returned. Note that this conversion can lose information about the overall magnitude and precision of this + * {@code Decimal128} value as well as return a result with the opposite sign. Note that {@code #NEGATIVE_ZERO} is converted to + * {@code 0}. + * + * @return this {@code Decimal128} converted to a {@code int}. + * @since 3.10 + */ + @Override + public int intValue() { + return (int) doubleValue(); + } + + /** + * Converts this {@code Decimal128} to a {@code long}. This conversion is analogous to the narrowing primitive conversion from + * {@code double} to {@code long} as defined in The Java™ Language Specification: any fractional part of this + * {@code Decimal128} will be discarded, and if the resulting integral value is too big to fit in a {@code long}, only the + * low-order 64 bits are returned. Note that this conversion can lose information about the overall magnitude and precision of this + * {@code Decimal128} value as well as return a result with the opposite sign. Note that {@code #NEGATIVE_ZERO} is converted to + * {@code 0L}. + * + * @return this {@code Decimal128} converted to a {@code long}. + * @since 3.10 + */ + @Override + public long longValue() { + return (long) doubleValue(); + } + + /** + * Converts this {@code Decimal128} to a {@code float}. This conversion is similar to the narrowing primitive conversion from + * {@code double} to {@code float} as defined in The Java™ Language Specification: if this {@code Decimal128} has + * too great a magnitude to represent as a {@code float}, it will be converted to {@link Float#NEGATIVE_INFINITY} or + * {@link Float#POSITIVE_INFINITY} as appropriate. Note that even when the return value is finite, this conversion can lose + * information about the precision of the {@code Decimal128} value. Note that {@code #NEGATIVE_ZERO} is converted to {@code 0.0f}. + * + * @return this {@code Decimal128} converted to a {@code float}. + * @since 3.10 + */ + @Override + public float floatValue() { + return (float) doubleValue(); + } + + /** + * Converts this {@code Decimal128} to a {@code double}. This conversion is similar to the narrowing primitive conversion from + * {@code double} to {@code float} as defined in The Java™ Language Specification: if this {@code Decimal128} has + * too great a magnitude to represent as a {@code double}, it will be converted to {@link Double#NEGATIVE_INFINITY} or + * {@link Double#POSITIVE_INFINITY} as appropriate. Note that even when the return value is finite, this conversion can lose + * information about the precision of the {@code Decimal128} value. Note that {@code #NEGATIVE_ZERO} is converted to {@code 0.0d}. + * + * @return this {@code Decimal128} converted to a {@code double}. + * @since 3.10 + */ + @Override + public double doubleValue() { + if (isNaN()) { + return Double.NaN; + } + if (isInfinite()) { + if (isNegative()) { + return Double.NEGATIVE_INFINITY; + } else { + return Double.POSITIVE_INFINITY; + } + } + + BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck(); + + if (hasDifferentSign(bigDecimal)) { + return -0.0d; + } + + return bigDecimal.doubleValue(); + } + + /** + * Returns true if the encoded representation of this instance is the same as the encoded representation of {@code o}. + *

    + * One consequence is that, whereas {@code Double.NaN != Double.NaN}, + * {@code new Decimal128("NaN").equals(new Decimal128("NaN")} returns true. + *

    + *

    + * Another consequence is that, as with BigDecimal, {@code new Decimal128("1.0").equals(new Decimal128("1.00")} returns false, + * because the precision is not the same and therefore the representation is not the same. + *

    + * + * @param o the object to compare for equality + * @return true if the instances are equal + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Decimal128 that = (Decimal128) o; + + if (high != that.high) { + return false; + } + if (low != that.low) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = (int) (low ^ (low >>> 32)); + result = 31 * result + (int) (high ^ (high >>> 32)); + return result; + } + + /** + * Returns the String representation of the Decimal128 value. + * + * @return the String representation + * @see + * To-String Sprecification + */ + @Override + public String toString() { + if (isNaN()) { + return "NaN"; + } + if (isInfinite()) { + if (isNegative()) { + return "-Infinity"; + } else { + return "Infinity"; + } + } + return toStringWithBigDecimal(); + } + + private String toStringWithBigDecimal() { + StringBuilder buffer = new StringBuilder(); + + BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck(); + String significand = bigDecimal.unscaledValue().abs().toString(); + + if (isNegative()) { + buffer.append('-'); + } + + int exponent = -bigDecimal.scale(); + int adjustedExponent = exponent + (significand.length() - 1); + if (exponent <= 0 && adjustedExponent >= -6) { + if (exponent == 0) { + buffer.append(significand); + } else { + int pad = -exponent - significand.length(); + if (pad >= 0) { + buffer.append('0'); + buffer.append('.'); + for (int i = 0; i < pad; i++) { + buffer.append('0'); + } + buffer.append(significand, 0, significand.length()); + } else { + buffer.append(significand, 0, -pad); + buffer.append('.'); + buffer.append(significand, -pad, -pad - exponent); + } + } + } else { + buffer.append(significand.charAt(0)); + if (significand.length() > 1) { + buffer.append('.'); + buffer.append(significand, 1, significand.length()); + } + buffer.append('E'); + if (adjustedExponent > 0) { + buffer.append('+'); + } + buffer.append(adjustedExponent); + } + return buffer.toString(); + } +} diff --git a/bson/src/main/org/bson/types/MaxKey.java b/bson/src/main/org/bson/types/MaxKey.java index 54c262ef678..f27cc75d629 100644 --- a/bson/src/main/org/bson/types/MaxKey.java +++ b/bson/src/main/org/bson/types/MaxKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/types/MinKey.java b/bson/src/main/org/bson/types/MinKey.java index c4bb7490c33..207934c827e 100644 --- a/bson/src/main/org/bson/types/MinKey.java +++ b/bson/src/main/org/bson/types/MinKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/types/ObjectId.java b/bson/src/main/org/bson/types/ObjectId.java index fd28f1b4fb5..1e24497545b 100644 --- a/bson/src/main/org/bson/types/ObjectId.java +++ b/bson/src/main/org/bson/types/ObjectId.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,14 @@ package org.bson.types; -import org.bson.diagnostics.Loggers; - import java.io.Serializable; -import java.net.NetworkInterface; -import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.Date; -import java.util.Enumeration; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; + +import static org.bson.assertions.Assertions.isTrueArgument; +import static org.bson.assertions.Assertions.notNull; /** *

    A globally unique identifier for objects.

    @@ -39,7 +35,7 @@ * 01234567891011 * * - * timemachine pidinc + * timerandom valueinc * * * @@ -51,22 +47,23 @@ public final class ObjectId implements Comparable, Serializable { private static final long serialVersionUID = 3670079982654483072L; - static final Logger LOGGER = Loggers.getLogger("ObjectId"); - + private static final int OBJECT_ID_LENGTH = 12; private static final int LOW_ORDER_THREE_BYTES = 0x00ffffff; - private static final int MACHINE_IDENTIFIER; - private static final short PROCESS_IDENTIFIER; + // Use primitives to represent the 5-byte random value. + private static final int RANDOM_VALUE1; + private static final short RANDOM_VALUE2; + private static final AtomicInteger NEXT_COUNTER = new AtomicInteger(new SecureRandom().nextInt()); private static final char[] HEX_CHARS = new char[] { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private final int timestamp; - private final int machineIdentifier; - private final short processIdentifier; private final int counter; + private final int randomValue1; + private final short randomValue2; /** * Gets a new object id. @@ -112,54 +109,6 @@ public static boolean isValid(final String hexString) { return true; } - /** - * Gets the generated machine identifier. - * - * @return an int representing the machine identifier - */ - public static int getGeneratedMachineIdentifier() { - return MACHINE_IDENTIFIER; - } - - /** - * Gets the generated process identifier. - * - * @return the process id - */ - public static int getGeneratedProcessIdentifier() { - return PROCESS_IDENTIFIER; - } - - /** - * Gets the current value of the auto-incrementing counter. - * - * @return the current counter value. - */ - public static int getCurrentCounter() { - return NEXT_COUNTER.get(); - } - - /** - *

    Creates an ObjectId using time, machine and inc values. The Java driver used to create all ObjectIds this way, but it does not - * match the ObjectId specification, which requires four values, not - * three. This major release of the Java driver conforms to the specification, but still supports clients that are relying on the - * behavior of the previous major release by providing this explicit factory method that takes three parameters instead of four.

    - * - *

    Ordinary users of the driver will not need this method. It's only for those that have written there own BSON decoders.

    - * - *

    NOTE: This will not break any application that use ObjectIds. The 12-byte representation will be round-trippable from old to new - * driver releases.

    - * - * @param time time in seconds - * @param machine machine ID - * @param inc incremental value - * @return a new {@code ObjectId} created from the given values - * @since 2.12.0 - */ - public static ObjectId createFromLegacyFormat(final int time, final int machine, final int inc) { - return new ObjectId(time, machine, inc); - } - /** * Create a new object id. */ @@ -173,7 +122,7 @@ public ObjectId() { * @param date the date */ public ObjectId(final Date date) { - this(dateToTimestampSeconds(date), MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, NEXT_COUNTER.getAndIncrement(), false); + this(dateToTimestampSeconds(date), NEXT_COUNTER.getAndIncrement() & LOW_ORDER_THREE_BYTES, false); } /** @@ -184,7 +133,7 @@ public ObjectId(final Date date) { * @throws IllegalArgumentException if the high order byte of counter is not zero */ public ObjectId(final Date date, final int counter) { - this(date, MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, counter); + this(dateToTimestampSeconds(date), counter, true); } /** @@ -195,7 +144,9 @@ public ObjectId(final Date date, final int counter) { * @param processIdentifier the process identifier * @param counter the counter * @throws IllegalArgumentException if the high order byte of machineIdentifier or counter is not zero + * @deprecated Use {@link #ObjectId(Date, int)} instead */ + @Deprecated public ObjectId(final Date date, final int machineIdentifier, final short processIdentifier, final int counter) { this(dateToTimestampSeconds(date), machineIdentifier, processIdentifier, counter); } @@ -208,23 +159,40 @@ public ObjectId(final Date date, final int machineIdentifier, final short proces * @param processIdentifier the process identifier * @param counter the counter * @throws IllegalArgumentException if the high order byte of machineIdentifier or counter is not zero + * @deprecated Use {@link #ObjectId(int, int)} instead */ + @Deprecated public ObjectId(final int timestamp, final int machineIdentifier, final short processIdentifier, final int counter) { this(timestamp, machineIdentifier, processIdentifier, counter, true); } - private ObjectId(final int timestamp, final int machineIdentifier, final short processIdentifier, final int counter, + /** + * Creates an ObjectId using the given time, machine identifier, process identifier, and counter. + * + * @param timestamp the time in seconds + * @param counter the counter + * @throws IllegalArgumentException if the high order byte of counter is not zero + */ + public ObjectId(final int timestamp, final int counter) { + this(timestamp, counter, true); + } + + private ObjectId(final int timestamp, final int counter, final boolean checkCounter) { + this(timestamp, RANDOM_VALUE1, RANDOM_VALUE2, counter, checkCounter); + } + + private ObjectId(final int timestamp, final int randomValue1, final short randomValue2, final int counter, final boolean checkCounter) { - if ((machineIdentifier & 0xff000000) != 0) { + if ((randomValue1 & 0xff000000) != 0) { throw new IllegalArgumentException("The machine identifier must be between 0 and 16777215 (it must fit in three bytes)."); } if (checkCounter && ((counter & 0xff000000) != 0)) { throw new IllegalArgumentException("The counter must be between 0 and 16777215 (it must fit in three bytes)."); } this.timestamp = timestamp; - this.machineIdentifier = machineIdentifier; - this.processIdentifier = processIdentifier; this.counter = counter & LOW_ORDER_THREE_BYTES; + this.randomValue1 = randomValue1; + this.randomValue2 = randomValue2; } /** @@ -244,17 +212,7 @@ public ObjectId(final String hexString) { * @throws IllegalArgumentException if array is null or not of length 12 */ public ObjectId(final byte[] bytes) { - if (bytes == null) { - throw new IllegalArgumentException(); - } - if (bytes.length != 12) { - throw new IllegalArgumentException("need 12 bytes"); - } - - timestamp = makeInt(bytes[0], bytes[1], bytes[2], bytes[3]); - machineIdentifier = makeInt((byte) 0, bytes[4], bytes[5], bytes[6]); - processIdentifier = (short) makeInt((byte) 0, (byte) 0, bytes[7], bytes[8]); - counter = makeInt((byte) 0, bytes[9], bytes[10], bytes[11]); + this(ByteBuffer.wrap(isTrueArgument("bytes has length of 12", bytes, notNull("bytes", bytes).length == 12))); } /** @@ -268,8 +226,27 @@ public ObjectId(final byte[] bytes) { this(legacyToBytes(timestamp, machineAndProcessIdentifier, counter)); } + /** + * Constructs a new instance from the given ByteBuffer + * + * @param buffer the ByteBuffer + * @throws IllegalArgumentException if the buffer is null or does not have at least 12 bytes remaining + * @since 3.4 + */ + public ObjectId(final ByteBuffer buffer) { + notNull("buffer", buffer); + isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH); + + // Note: Cannot use ByteBuffer.getInt because it depends on tbe buffer's byte order + // and ObjectId's are always in big-endian order. + timestamp = makeInt(buffer.get(), buffer.get(), buffer.get(), buffer.get()); + randomValue1 = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get()); + randomValue2 = makeShort(buffer.get(), buffer.get()); + counter = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get()); + } + private static byte[] legacyToBytes(final int timestamp, final int machineAndProcessIdentifier, final int counter) { - byte[] bytes = new byte[12]; + byte[] bytes = new byte[OBJECT_ID_LENGTH]; bytes[0] = int3(timestamp); bytes[1] = int2(timestamp); bytes[2] = int1(timestamp); @@ -291,56 +268,44 @@ private static byte[] legacyToBytes(final int timestamp, final int machineAndPro * @return the byte array */ public byte[] toByteArray() { - byte[] bytes = new byte[12]; - bytes[0] = int3(timestamp); - bytes[1] = int2(timestamp); - bytes[2] = int1(timestamp); - bytes[3] = int0(timestamp); - bytes[4] = int2(machineIdentifier); - bytes[5] = int1(machineIdentifier); - bytes[6] = int0(machineIdentifier); - bytes[7] = short1(processIdentifier); - bytes[8] = short0(processIdentifier); - bytes[9] = int2(counter); - bytes[10] = int1(counter); - bytes[11] = int0(counter); - return bytes; + ByteBuffer buffer = ByteBuffer.allocate(OBJECT_ID_LENGTH); + putToByteBuffer(buffer); + return buffer.array(); // using .allocate ensures there is a backing array that can be returned } /** - * Gets the timestamp (number of seconds since the Unix epoch). - * - * @return the timestamp - */ - public int getTimestamp() { - return timestamp; - } - - /** - * Gets the machine identifier. - * - * @return the machine identifier - */ - public int getMachineIdentifier() { - return machineIdentifier; - } - - /** - * Gets the process identifier. + * Convert to bytes and put those bytes to the provided ByteBuffer. + * Note that the numbers are stored in big-endian order. * - * @return the process identifier + * @param buffer the ByteBuffer + * @throws IllegalArgumentException if the buffer is null or does not have at least 12 bytes remaining + * @since 3.4 */ - public short getProcessIdentifier() { - return processIdentifier; + public void putToByteBuffer(final ByteBuffer buffer) { + notNull("buffer", buffer); + isTrueArgument("buffer.remaining() >=12", buffer.remaining() >= OBJECT_ID_LENGTH); + + buffer.put(int3(timestamp)); + buffer.put(int2(timestamp)); + buffer.put(int1(timestamp)); + buffer.put(int0(timestamp)); + buffer.put(int2(randomValue1)); + buffer.put(int1(randomValue1)); + buffer.put(int0(randomValue1)); + buffer.put(short1(randomValue2)); + buffer.put(short0(randomValue2)); + buffer.put(int2(counter)); + buffer.put(int1(counter)); + buffer.put(int0(counter)); } /** - * Gets the counter. + * Gets the timestamp (number of seconds since the Unix epoch). * - * @return the counter + * @return the timestamp */ - public int getCounter() { - return counter; + public int getTimestamp() { + return timestamp; } /** @@ -349,7 +314,7 @@ public int getCounter() { * @return the Date */ public Date getDate() { - return new Date(timestamp * 1000L); + return new Date((timestamp & 0xFFFFFFFFL) * 1000L); } /** @@ -358,13 +323,13 @@ public Date getDate() { * @return a string representation of the ObjectId in hexadecimal format */ public String toHexString() { - char[] chars = new char[24]; - int i = 0; - for (byte b : toByteArray()) { - chars[i++] = HEX_CHARS[b >> 4 & 0xF]; - chars[i++] = HEX_CHARS[b & 0xF]; - } - return new String(chars); + char[] chars = new char[OBJECT_ID_LENGTH * 2]; + int i = 0; + for (byte b : toByteArray()) { + chars[i++] = HEX_CHARS[b >> 4 & 0xF]; + chars[i++] = HEX_CHARS[b & 0xF]; + } + return new String(chars); } @Override @@ -381,13 +346,15 @@ public boolean equals(final Object o) { if (counter != objectId.counter) { return false; } - if (machineIdentifier != objectId.machineIdentifier) { + if (timestamp != objectId.timestamp) { return false; } - if (processIdentifier != objectId.processIdentifier) { + + if (randomValue1 != objectId.randomValue1) { return false; } - if (timestamp != objectId.timestamp) { + + if (randomValue2 != objectId.randomValue2) { return false; } @@ -397,9 +364,9 @@ public boolean equals(final Object o) { @Override public int hashCode() { int result = timestamp; - result = 31 * result + machineIdentifier; - result = 31 * result + (int) processIdentifier; result = 31 * result + counter; + result = 31 * result + randomValue1; + result = 31 * result + randomValue2; return result; } @@ -411,7 +378,7 @@ public int compareTo(final ObjectId other) { byte[] byteArray = toByteArray(); byte[] otherByteArray = other.toByteArray(); - for (int i = 0; i < 12; i++) { + for (int i = 0; i < OBJECT_ID_LENGTH; i++) { if (byteArray[i] != otherByteArray[i]) { return ((byteArray[i] & 0xff) < (otherByteArray[i] & 0xff)) ? -1 : 1; } @@ -426,11 +393,100 @@ public String toString() { // Deprecated methods + /** + *

    Creates an ObjectId using time, machine and inc values. The Java driver used to create all ObjectIds this way, but it does not + * match the ObjectId specification, which requires four values, not + * three. This major release of the Java driver conforms to the specification, but still supports clients that are relying on the + * behavior of the previous major release by providing this explicit factory method that takes three parameters instead of four.

    + * + *

    Ordinary users of the driver will not need this method. It's only for those that have written there own BSON decoders.

    + * + *

    NOTE: This will not break any application that use ObjectIds. The 12-byte representation will be round-trippable from old to new + * driver releases.

    + * + * @param time time in seconds + * @param machine machine ID + * @param inc incremental value + * @return a new {@code ObjectId} created from the given values + * @since 2.12.0 + * @deprecated Use {@link #ObjectId(int, int)} instead + */ + @Deprecated + public static ObjectId createFromLegacyFormat(final int time, final int machine, final int inc) { + return new ObjectId(time, machine, inc); + } + + /** + * Gets the current value of the auto-incrementing counter. + * + * @return the current counter value. + * @deprecated + */ + @Deprecated + public static int getCurrentCounter() { + return NEXT_COUNTER.get() & LOW_ORDER_THREE_BYTES; + } + + /** + * Gets the generated machine identifier. + * + * @return an int representing the machine identifier + * @deprecated + */ + @Deprecated + public static int getGeneratedMachineIdentifier() { + return RANDOM_VALUE1; + } + + /** + * Gets the generated process identifier. + * + * @return the process id + * @deprecated + */ + @Deprecated + public static int getGeneratedProcessIdentifier() { + return RANDOM_VALUE2; + } + + /** + * Gets the machine identifier. + * + * @return the machine identifier + * @deprecated + */ + @Deprecated + public int getMachineIdentifier() { + return randomValue1; + } + + /** + * Gets the process identifier. + * + * @return the process identifier + * @deprecated + */ + @Deprecated + public short getProcessIdentifier() { + return randomValue2; + } + + /** + * Gets the counter. + * + * @return the counter + * @deprecated + */ + @Deprecated + public int getCounter() { + return counter; + } + /** * Gets the time of this ID, in seconds. * - * @deprecated Use #getTimestamp instead * @return the time component of this ID in seconds + * @deprecated Use #getTimestamp instead */ @Deprecated public int getTimeSecond() { @@ -440,12 +496,12 @@ public int getTimeSecond() { /** * Gets the time of this instance, in milliseconds. * - * @deprecated Use #getDate instead * @return the time component of this ID in milliseconds + * @deprecated Use #getDate instead */ @Deprecated public long getTime() { - return timestamp * 1000L; + return (timestamp & 0xFFFFFFFFL) * 1000L; } /** @@ -460,70 +516,20 @@ public String toStringMongod() { static { try { - MACHINE_IDENTIFIER = createMachineIdentifier(); - PROCESS_IDENTIFIER = createProcessIdentifier(); + SecureRandom secureRandom = new SecureRandom(); + RANDOM_VALUE1 = secureRandom.nextInt(0x01000000); + RANDOM_VALUE2 = (short) secureRandom.nextInt(0x00008000); } catch (Exception e) { throw new RuntimeException(e); } } - private static int createMachineIdentifier() { - // build a 2-byte machine piece based on NICs info - int machinePiece; - try { - StringBuilder sb = new StringBuilder(); - Enumeration e = NetworkInterface.getNetworkInterfaces(); - while (e.hasMoreElements()) { - NetworkInterface ni = e.nextElement(); - sb.append(ni.toString()); - byte[] mac = ni.getHardwareAddress(); - if (mac != null) { - ByteBuffer bb = ByteBuffer.wrap(mac); - try { - sb.append(bb.getChar()); - sb.append(bb.getChar()); - sb.append(bb.getChar()); - } catch (BufferUnderflowException shortHardwareAddressException) { //NOPMD - // mac with less than 6 bytes. continue - } - } - } - machinePiece = sb.toString().hashCode(); - } catch (Throwable t) { - // exception sometimes happens with IBM JVM, use random - machinePiece = (new SecureRandom().nextInt()); - LOGGER.log(Level.WARNING, "Failed to get machine identifier from network interface, using random number instead", t); - } - machinePiece = machinePiece & LOW_ORDER_THREE_BYTES; - return machinePiece; - } - - // Creates the process identifier. This does not have to be unique per class loader because - // NEXT_COUNTER will provide the uniqueness. - private static short createProcessIdentifier() { - short processId; - try { - String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); - if (processName.contains("@")) { - processId = (short) Integer.parseInt(processName.substring(0, processName.indexOf('@'))); - } else { - processId = (short) java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode(); - } - - } catch (Throwable t) { - processId = (short) new SecureRandom().nextInt(); - LOGGER.log(Level.WARNING, "Failed to get process identifier from JMX, using random number instead", t); - } - - return processId; - } - private static byte[] parseHexString(final String s) { if (!isValid(s)) { throw new IllegalArgumentException("invalid hexadecimal representation of an ObjectId: [" + s + "]"); } - byte[] b = new byte[12]; + byte[] b = new byte[OBJECT_ID_LENGTH]; for (int i = 0; i < b.length; i++) { b[i] = (byte) Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16); } @@ -545,6 +551,12 @@ private static int makeInt(final byte b3, final byte b2, final byte b1, final by // CHECKSTYLE:ON } + private static short makeShort(final byte b1, final byte b0) { + // CHECKSTYLE:OFF + return (short) (((b1 & 0xff) << 8) | ((b0 & 0xff))); + // CHECKSTYLE:ON + } + private static byte int3(final int x) { return (byte) (x >> 24); } @@ -568,5 +580,5 @@ private static byte short1(final short x) { private static byte short0(final short x) { return (byte) (x); } -} +} diff --git a/driver/src/main/org/bson/types/StringRangeSet.java b/bson/src/main/org/bson/types/StringRangeSet.java similarity index 93% rename from driver/src/main/org/bson/types/StringRangeSet.java rename to bson/src/main/org/bson/types/StringRangeSet.java index 20b31c0e7fc..4daca26cc03 100644 --- a/driver/src/main/org/bson/types/StringRangeSet.java +++ b/bson/src/main/org/bson/types/StringRangeSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,8 @@ import java.util.NoSuchElementException; import java.util.Set; -import static com.mongodb.assertions.Assertions.isTrue; +import static org.bson.assertions.Assertions.isTrue; + class StringRangeSet implements Set { @@ -55,8 +56,12 @@ public boolean contains(final Object o) { if (!(o instanceof String)) { return false; } - int i = Integer.parseInt((String) o); - return i >= 0 && i < size(); + try { + int i = Integer.parseInt((String) o); + return i >= 0 && i < size(); + } catch (NumberFormatException e) { + return false; + } } @Override diff --git a/bson/src/main/org/bson/types/Symbol.java b/bson/src/main/org/bson/types/Symbol.java index cf63e5235dd..67960e443bc 100644 --- a/bson/src/main/org/bson/types/Symbol.java +++ b/bson/src/main/org/bson/types/Symbol.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/types/package-info.java b/bson/src/main/org/bson/types/package-info.java index 3dda24fd65f..a82a0da608b 100644 --- a/bson/src/main/org/bson/types/package-info.java +++ b/bson/src/main/org/bson/types/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/main/org/bson/util/AbstractCopyOnWriteMap.java b/bson/src/main/org/bson/util/AbstractCopyOnWriteMap.java similarity index 98% rename from driver/src/main/org/bson/util/AbstractCopyOnWriteMap.java rename to bson/src/main/org/bson/util/AbstractCopyOnWriteMap.java index a05dfb72239..e023f0538c0 100644 --- a/driver/src/main/org/bson/util/AbstractCopyOnWriteMap.java +++ b/bson/src/main/org/bson/util/AbstractCopyOnWriteMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * Copyright (c) 2008-2014 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ package org.bson.util; -import com.mongodb.annotations.ThreadSafe; import java.util.Collection; import java.util.Collections; @@ -28,9 +27,9 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import static com.mongodb.assertions.Assertions.notNull; import static java.util.Collections.unmodifiableCollection; import static java.util.Collections.unmodifiableSet; +import static org.bson.assertions.Assertions.notNull; /** * Abstract base class for COW {@link java.util.Map} implementations that delegate to an internal map. @@ -39,7 +38,6 @@ * @param The value type * @param the internal {@link java.util.Map} or extension for things like sorted and navigable maps. */ -@ThreadSafe abstract class AbstractCopyOnWriteMap> implements ConcurrentMap { // @GuardedBy("lock") @@ -459,7 +457,7 @@ public boolean retainAll(final Collection c) { private static class UnmodifiableIterator implements Iterator { private final Iterator delegate; - public UnmodifiableIterator(final Iterator delegate) { + UnmodifiableIterator(final Iterator delegate) { this.delegate = delegate; } diff --git a/driver/src/main/org/bson/util/ClassAncestry.java b/bson/src/main/org/bson/util/ClassAncestry.java similarity index 95% rename from driver/src/main/org/bson/util/ClassAncestry.java rename to bson/src/main/org/bson/util/ClassAncestry.java index e67451541e2..9f4a7008d51 100644 --- a/driver/src/main/org/bson/util/ClassAncestry.java +++ b/bson/src/main/org/bson/util/ClassAncestry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ import java.util.concurrent.ConcurrentMap; import static java.util.Collections.unmodifiableList; -import static org.bson.util.CopyOnWriteMap.newHashMap; +@SuppressWarnings("deprecation") class ClassAncestry { /** @@ -81,6 +81,6 @@ private static ConcurrentMap, List>> getClassAncestryCache() { return (_ancestryCache); } - private static final ConcurrentMap, List>> _ancestryCache = newHashMap(); + private static final ConcurrentMap, List>> _ancestryCache = CopyOnWriteMap.newHashMap(); } diff --git a/driver/src/main/org/bson/util/ClassMap.java b/bson/src/main/org/bson/util/ClassMap.java similarity index 97% rename from driver/src/main/org/bson/util/ClassMap.java rename to bson/src/main/org/bson/util/ClassMap.java index 622733f4423..d42ecbf5db6 100644 --- a/driver/src/main/org/bson/util/ClassMap.java +++ b/bson/src/main/org/bson/util/ClassMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,9 @@ * (assuming Dog.class < Animal.class) * * @param the type of the value in this map + * @deprecated there is no replacement for this class */ +@Deprecated public class ClassMap { /** * Helper method that walks superclass and interface graph, superclasses first, then interfaces, to compute an ancestry list. Super diff --git a/driver/src/main/org/bson/util/ComputingMap.java b/bson/src/main/org/bson/util/ComputingMap.java similarity index 96% rename from driver/src/main/org/bson/util/ComputingMap.java rename to bson/src/main/org/bson/util/ComputingMap.java index 607f1de4f2c..347531c9f55 100644 --- a/driver/src/main/org/bson/util/ComputingMap.java +++ b/bson/src/main/org/bson/util/ComputingMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,9 @@ import java.util.Set; import java.util.concurrent.ConcurrentMap; -import static com.mongodb.assertions.Assertions.notNull; +import static org.bson.assertions.Assertions.notNull; +@SuppressWarnings("deprecation") final class ComputingMap implements Map, Function { public static Map create(final Function function) { diff --git a/driver/src/main/org/bson/util/CopyOnWriteMap.java b/bson/src/main/org/bson/util/CopyOnWriteMap.java similarity index 96% rename from driver/src/main/org/bson/util/CopyOnWriteMap.java rename to bson/src/main/org/bson/util/CopyOnWriteMap.java index f04a3e3e914..cecb200d418 100644 --- a/driver/src/main/org/bson/util/CopyOnWriteMap.java +++ b/bson/src/main/org/bson/util/CopyOnWriteMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * Copyright (c) 2008-2014 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,6 @@ package org.bson.util; -import com.mongodb.annotations.ThreadSafe; - import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -32,7 +30,7 @@ * mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent * threads. The "snapshot" style iterators on the collections returned by {@link #entrySet()}, {@link #keySet()} and {@link #values()} use a * reference to the internal map at the point that the iterator was created. This map never changes during the lifetime of the iterator, so - * interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException. The iterators will not + * interference is impossible and the iterator is guaranteed not to throw {@code ConcurrentModificationException}. The iterators will not * reflect additions, removals, or changes to the list since the iterator was created. Removing elements via these iterators is not * supported. The mutable operations on these collections (remove, retain etc.) are supported but as with the {@link java.util.Map} * interface, add and addAll are not and throw {@link UnsupportedOperationException}.

    The actual copy is performed by an abstract @@ -58,7 +56,6 @@ * @param the value type * @author Jed Wesley-Smith */ -@ThreadSafe abstract class CopyOnWriteMap extends AbstractCopyOnWriteMap> { private static final long serialVersionUID = 7935514534647505917L; @@ -169,19 +166,14 @@ public static CopyOnWriteMap newLinkedMap(final Map map) { this(map, View.Type.LIVE); } /** * Create a new empty {@link CopyOnWriteMap}. - * - * @deprecated since 0.0.12 use the versions that explicitly specify View.Type */ - @Deprecated protected CopyOnWriteMap() { this(Collections.emptyMap(), View.Type.LIVE); } diff --git a/driver/src/main/org/bson/util/Function.java b/bson/src/main/org/bson/util/Function.java similarity index 93% rename from driver/src/main/org/bson/util/Function.java rename to bson/src/main/org/bson/util/Function.java index dc49f1fd07b..b4cc3d66b09 100644 --- a/driver/src/main/org/bson/util/Function.java +++ b/bson/src/main/org/bson/util/Function.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/main/org/bson/util/package-info.java b/bson/src/main/org/bson/util/package-info.java new file mode 100644 index 00000000000..9226065ec35 --- /dev/null +++ b/bson/src/main/org/bson/util/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Contains helper classes for working with the BSON protocol. + */ +package org.bson.util; diff --git a/bson/src/test/resources/bson/array.json b/bson/src/test/resources/bson/array.json index 60c4e2f97d7..9ff953e5ae7 100644 --- a/bson/src/test/resources/bson/array.json +++ b/bson/src/test/resources/bson/array.json @@ -1,37 +1,49 @@ { - "description": "Array type", - "documents": [ - { - "decoded": { - "a": [] - }, - "encoded": "0D000000046100050000000000" - }, - { - "decoded": { - "a": [ - 10 - ] - }, - "encoded": "140000000461000C0000001030000A0000000000" - }, - { - "decodeOnly": true, - "decoded": { - "a": [ - 10 - ] - }, - "encoded": "130000000461000B00000010000A0000000000" - }, - { - "decodeOnly": true, - "decoded": { - "a": [ - 10 - ] - }, - "encoded": "150000000461000D000000106162000A0000000000" + "description": "Array", + "bson_type": "0x04", + "test_key": "a", + "valid": [ + { + "description": "Empty", + "canonical_bson": "0D000000046100050000000000", + "canonical_extjson": "{\"a\" : []}" + }, + { + "description": "Single Element Array", + "canonical_bson": "140000000461000C0000001030000A0000000000", + "canonical_extjson": "{\"a\" : [{\"$numberInt\": \"10\"}]}" + }, + { + "description": "Single Element Array with index set incorrectly to empty string", + "degenerate_bson": "130000000461000B00000010000A0000000000", + "canonical_bson": "140000000461000C0000001030000A0000000000", + "canonical_extjson": "{\"a\" : [{\"$numberInt\": \"10\"}]}" + }, + { + "description": "Single Element Array with index set incorrectly to ab", + "degenerate_bson": "150000000461000D000000106162000A0000000000", + "canonical_bson": "140000000461000C0000001030000A0000000000", + "canonical_extjson": "{\"a\" : [{\"$numberInt\": \"10\"}]}" + }, + { + "description": "Multi Element Array with duplicate indexes", + "degenerate_bson": "1b000000046100130000001030000a000000103000140000000000", + "canonical_bson": "1b000000046100130000001030000a000000103100140000000000", + "canonical_extjson": "{\"a\" : [{\"$numberInt\": \"10\"}, {\"$numberInt\": \"20\"}]}" + } + ], + "decodeErrors": [ + { + "description": "Array length too long: eats outer terminator", + "bson": "140000000461000D0000001030000A0000000000" + }, + { + "description": "Array length too short: leaks terminator", + "bson": "140000000461000B0000001030000A0000000000" + }, + { + "description": "Invalid Array: bad string length in field", + "bson": "1A00000004666F6F00100000000230000500000062617A000000" } ] } diff --git a/bson/src/test/resources/bson/binary.json b/bson/src/test/resources/bson/binary.json new file mode 100644 index 00000000000..90a15c1a1c4 --- /dev/null +++ b/bson/src/test/resources/bson/binary.json @@ -0,0 +1,85 @@ +{ + "description": "Binary type", + "bson_type": "0x05", + "test_key": "x", + "valid": [ + { + "description": "subtype 0x00 (Zero-length)", + "canonical_bson": "0D000000057800000000000000", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"\", \"subType\" : \"00\"}}}" + }, + { + "description": "subtype 0x00 (Zero-length, keys reversed)", + "canonical_bson": "0D000000057800000000000000", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"\", \"subType\" : \"00\"}}}", + "degenerate_extjson": "{\"x\" : { \"$binary\" : {\"subType\" : \"00\", \"base64\" : \"\"}}}" + }, + { + "description": "subtype 0x00", + "canonical_bson": "0F0000000578000200000000FFFF00", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"//8=\", \"subType\" : \"00\"}}}" + }, + { + "description": "subtype 0x01", + "canonical_bson": "0F0000000578000200000001FFFF00", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"//8=\", \"subType\" : \"01\"}}}" + }, + { + "description": "subtype 0x02", + "canonical_bson": "13000000057800060000000202000000FFFF00", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"//8=\", \"subType\" : \"02\"}}}" + }, + { + "description": "subtype 0x03", + "canonical_bson": "1D000000057800100000000373FFD26444B34C6990E8E7D1DFC035D400", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"03\"}}}" + }, + { + "description": "subtype 0x04", + "canonical_bson": "1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}" + }, + { + "description": "subtype 0x05", + "canonical_bson": "1D000000057800100000000573FFD26444B34C6990E8E7D1DFC035D400", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"05\"}}}" + }, + { + "description": "subtype 0x80", + "canonical_bson": "0F0000000578000200000080FFFF00", + "canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"//8=\", \"subType\" : \"80\"}}}" + }, + { + "description": "$type query operator (conflicts with legacy $binary form with $type field)", + "canonical_bson": "1F000000037800170000000224747970650007000000737472696E67000000", + "canonical_extjson": "{\"x\" : { \"$type\" : \"string\"}}" + }, + { + "description": "$type query operator (conflicts with legacy $binary form with $type field)", + "canonical_bson": "180000000378001000000010247479706500020000000000", + "canonical_extjson": "{\"x\" : { \"$type\" : {\"$numberInt\": \"2\"}}}" + } + ], + "decodeErrors": [ + { + "description": "Length longer than document", + "bson": "1D000000057800FF0000000573FFD26444B34C6990E8E7D1DFC035D400" + }, + { + "description": "Negative length", + "bson": "0D000000057800FFFFFFFF0000" + }, + { + "description": "subtype 0x02 length too long ", + "bson": "13000000057800060000000203000000FFFF00" + }, + { + "description": "subtype 0x02 length too short", + "bson": "13000000057800060000000201000000FFFF00" + }, + { + "description": "subtype 0x02 length negative one", + "bson": "130000000578000600000002FFFFFFFFFFFF00" + } + ] +} diff --git a/bson/src/test/resources/bson/boolean.json b/bson/src/test/resources/bson/boolean.json index 0feff4c198c..84c282299a1 100644 --- a/bson/src/test/resources/bson/boolean.json +++ b/bson/src/test/resources/bson/boolean.json @@ -1,17 +1,27 @@ { - "description": "Boolean type", - "documents": [ + "description": "Boolean", + "bson_type": "0x08", + "test_key": "b", + "valid": [ { - "decoded": { - "b": true - }, - "encoded": "090000000862000100" - }, + "description": "True", + "canonical_bson": "090000000862000100", + "canonical_extjson": "{\"b\" : true}" + }, { - "decoded": { - "b": false - }, - "encoded": "090000000862000000" + "description": "False", + "canonical_bson": "090000000862000000", + "canonical_extjson": "{\"b\" : false}" + } + ], + "decodeErrors": [ + { + "description": "Invalid boolean value of 2", + "bson": "090000000862000200" + }, + { + "description": "Invalid boolean value of -1", + "bson": "09000000086200FF00" } ] } diff --git a/bson/src/test/resources/bson/code.json b/bson/src/test/resources/bson/code.json new file mode 100644 index 00000000000..6f37349ad0b --- /dev/null +++ b/bson/src/test/resources/bson/code.json @@ -0,0 +1,67 @@ +{ + "description": "Javascript Code", + "bson_type": "0x0D", + "test_key": "a", + "valid": [ + { + "description": "Empty string", + "canonical_bson": "0D0000000D6100010000000000", + "canonical_extjson": "{\"a\" : {\"$code\" : \"\"}}" + }, + { + "description": "Single character", + "canonical_bson": "0E0000000D610002000000620000", + "canonical_extjson": "{\"a\" : {\"$code\" : \"b\"}}" + }, + { + "description": "Multi-character", + "canonical_bson": "190000000D61000D0000006162616261626162616261620000", + "canonical_extjson": "{\"a\" : {\"$code\" : \"abababababab\"}}" + }, + { + "description": "two-byte UTF-8 (\u00e9)", + "canonical_bson": "190000000261000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", + "canonical_extjson": "{\"a\" : \"\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\"}" + }, + { + "description": "three-byte UTF-8 (\u2606)", + "canonical_bson": "190000000261000D000000E29886E29886E29886E298860000", + "canonical_extjson": "{\"a\" : \"\\u2606\\u2606\\u2606\\u2606\"}" + }, + { + "description": "Embedded nulls", + "canonical_bson": "190000000261000D0000006162006261620062616261620000", + "canonical_extjson": "{\"a\" : \"ab\\u0000bab\\u0000babab\"}" + } + ], + "decodeErrors": [ + { + "description": "bad code string length: 0 (but no 0x00 either)", + "bson": "0C0000000261000000000000" + }, + { + "description": "bad code string length: -1", + "bson": "0C000000026100FFFFFFFF00" + }, + { + "description": "bad code string length: eats terminator", + "bson": "10000000026100050000006200620000" + }, + { + "description": "bad code string length: longer than rest of document", + "bson": "120000000200FFFFFF00666F6F6261720000" + }, + { + "description": "code string is not null-terminated", + "bson": "1000000002610004000000616263FF00" + }, + { + "description": "empty code string, but extra null", + "bson": "0E00000002610001000000000000" + }, + { + "description": "invalid UTF-8", + "bson": "0E00000002610002000000E90000" + } + ] +} diff --git a/bson/src/test/resources/bson/code_w_scope.json b/bson/src/test/resources/bson/code_w_scope.json new file mode 100644 index 00000000000..f956bcd54f6 --- /dev/null +++ b/bson/src/test/resources/bson/code_w_scope.json @@ -0,0 +1,78 @@ +{ + "description": "Javascript Code with Scope", + "bson_type": "0x0F", + "test_key": "a", + "valid": [ + { + "description": "Empty code string, empty scope", + "canonical_bson": "160000000F61000E0000000100000000050000000000", + "canonical_extjson": "{\"a\" : {\"$code\" : \"\", \"$scope\" : {}}}" + }, + { + "description": "Non-empty code string, empty scope", + "canonical_bson": "1A0000000F610012000000050000006162636400050000000000", + "canonical_extjson": "{\"a\" : {\"$code\" : \"abcd\", \"$scope\" : {}}}" + }, + { + "description": "Empty code string, non-empty scope", + "canonical_bson": "1D0000000F61001500000001000000000C000000107800010000000000", + "canonical_extjson": "{\"a\" : {\"$code\" : \"\", \"$scope\" : {\"x\" : {\"$numberInt\": \"1\"}}}}" + }, + { + "description": "Non-empty code string and non-empty scope", + "canonical_bson": "210000000F6100190000000500000061626364000C000000107800010000000000", + "canonical_extjson": "{\"a\" : {\"$code\" : \"abcd\", \"$scope\" : {\"x\" : {\"$numberInt\": \"1\"}}}}" + }, + { + "description": "Unicode and embedded null in code string, empty scope", + "canonical_bson": "1A0000000F61001200000005000000C3A9006400050000000000", + "canonical_extjson": "{\"a\" : {\"$code\" : \"\\u00e9\\u0000d\", \"$scope\" : {}}}" + } + ], + "decodeErrors": [ + { + "description": "field length zero", + "bson": "280000000F6100000000000500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "field length negative", + "bson": "280000000F6100FFFFFFFF0500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "field length too short (less than minimum size)", + "bson": "160000000F61000D0000000100000000050000000000" + }, + { + "description": "field length too short (truncates scope)", + "bson": "280000000F61001F0000000500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "field length too long (clips outer doc)", + "bson": "280000000F6100210000000500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "field length too long (longer than outer doc)", + "bson": "280000000F6100FF0000000500000061626364001300000010780001000000107900010000000000" + }, + { + "description": "bad code string: length too short", + "bson": "280000000F6100200000000400000061626364001300000010780001000000107900010000000000" + }, + { + "description": "bad code string: length too long (clips scope)", + "bson": "280000000F6100200000000600000061626364001300000010780001000000107900010000000000" + }, + { + "description": "bad code string: negative length", + "bson": "280000000F610020000000FFFFFFFF61626364001300000010780001000000107900010000000000" + }, + { + "description": "bad code string: length longer than field", + "bson": "280000000F610020000000FF00000061626364001300000010780001000000107900010000000000" + }, + { + "description": "bad scope doc (field has bad string length)", + "bson": "1C0000000F001500000001000000000C000000020000000000000000" + } + ] +} diff --git a/bson/src/test/resources/bson/corrupted.json b/bson/src/test/resources/bson/corrupted.json deleted file mode 100644 index f14cae03516..00000000000 --- a/bson/src/test/resources/bson/corrupted.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "description": "Corrupted BSON", - "documents": [ - { - "encoded": "09000000016600", - "error": "truncated double" - }, - { - "encoded": "09000000026600", - "error": "truncated string" - }, - { - "encoded": "09000000036600", - "error": "truncated document" - }, - { - "encoded": "09000000046600", - "error": "truncated array" - }, - { - "encoded": "09000000056600", - "error": "truncated binary" - }, - { - "encoded": "09000000076600", - "error": "truncated objectid" - }, - { - "encoded": "09000000086600", - "error": "truncated boolean" - }, - { - "encoded": "09000000096600", - "error": "truncated date" - }, - { - "encoded": "090000000b6600", - "error": "truncated regex" - }, - { - "encoded": "090000000c6600", - "error": "truncated db pointer" - }, - { - "encoded": "0C0000000d6600", - "error": "truncated javascript" - }, - { - "encoded": "0C0000000e6600", - "error": "truncated symbol" - }, - { - "encoded": "0C0000000f6600", - "error": "truncated javascript with scope" - }, - { - "encoded": "0C000000106600", - "error": "truncated int32" - }, - { - "encoded": "0C000000116600", - "error": "truncated timestamp" - }, - { - "encoded": "0C000000126600", - "error": "truncated int64" - }, - { - "encoded": "0400000000", - "error": "basic" - }, - { - "encoded": "0500000001", - "error": "basic" - }, - { - "encoded": "05000000", - "error": "basic" - }, - { - "encoded": "0700000002610078563412", - "error": "basic" - }, - { - "encoded": "090000001061000500", - "error": "basic" - }, - { - "encoded": "00000000000000000000", - "error": "basic" - }, - { - "encoded": "1300000002666f6f00040000006261720000", - "error": "basic" - }, - { - "encoded": "1800000003666f6f000f0000001062617200ffffff7f0000", - "error": "basic" - }, - { - "encoded": "1500000003666f6f000c0000000862617200010000", - "error": "basic" - }, - { - "encoded": "1c00000003666f6f001200000002626172000500000062617a000000", - "error": "basic" - }, - { - "encoded": "1000000002610004000000616263ff00", - "error": "string is not null-terminated" - }, - { - "encoded": "0c0000000200000000000000", - "error": "bad_string_length" - }, - { - "encoded": "120000000200ffffffff666f6f6261720000", - "error": "bad_string_length" - }, - { - "encoded": "0c0000000e00000000000000", - "error": "bad_string_length" - }, - { - "encoded": "120000000e00ffffffff666f6f6261720000", - "error": "bad_string_length" - }, - { - "encoded": "180000000c00fa5bd841d6585d9900", - "error": "" - }, - { - "encoded": "1e0000000c00ffffffff666f6f626172005259b56afa5bd841d6585d9900", - "error": "bad_string_length" - }, - { - "encoded": "0c0000000d00000000000000", - "error": "bad_string_length" - }, - { - "encoded": "0c0000000d00ffffffff0000", - "error": "bad_string_length" - }, - { - "encoded": "1c0000000f001500000000000000000c000000020001000000000000", - "error": "bad_string_length" - }, - { - "encoded": "1c0000000f0015000000ffffffff000c000000020001000000000000", - "error": "bad_string_length" - }, - { - "encoded": "1c0000000f001500000001000000000c000000020000000000000000", - "error": "bad_string_length" - }, - { - "encoded": "1c0000000f001500000001000000000c0000000200ffffffff000000", - "error": "bad_string_length" - }, - { - "encoded": "0E00000008616263646566676869707172737475", - "error": "Run-on CString" - }, - { - "encoded": "0100000000", - "error": "An object size that's too small to even include the object size, but is correctly encoded, along with a correct EOO (and no data)" - }, - { - "encoded": "1a0000000e74657374000c00000068656c6c6f20776f726c6400000500000000", - "error": "One object, but with object size listed smaller than it is in the data" - }, - { - "encoded": "05000000", - "error": "One object, missing the EOO at the end" - }, - { - "encoded": "0500000001", - "error": "One object, sized correctly, with a spot for an EOO, but the EOO is 0x01" - }, - { - "encoded": "05000000ff", - "error": "One object, sized correctly, with a spot for an EOO, but the EOO is 0xff" - }, - { - "encoded": "0500000070", - "error": "One object, sized correctly, with a spot for an EOO, but the EOO is 0x70" - }, - { - "encoded": "07000000000000", - "error": "Invalid BSON type low range" - }, - { - "encoded": "07000000800000", - "error": "Invalid BSON type high range" - }, - { - "encoded": "090000000862000200", - "error": "Invalid boolean value of 2" - }, - { - "encoded": "09000000086200ff00", - "error": "Invalid boolean value of -1" - } - ] -} diff --git a/bson/src/test/resources/bson/datetime.json b/bson/src/test/resources/bson/datetime.json new file mode 100644 index 00000000000..60506ce1749 --- /dev/null +++ b/bson/src/test/resources/bson/datetime.json @@ -0,0 +1,36 @@ +{ + "description": "DateTime", + "bson_type": "0x09", + "test_key": "a", + "valid": [ + { + "description": "epoch", + "canonical_bson": "10000000096100000000000000000000", + "relaxed_extjson": "{\"a\" : {\"$date\" : \"1970-01-01T00:00:00Z\"}}", + "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"0\"}}}" + }, + { + "description": "positive ms", + "canonical_bson": "10000000096100C5D8D6CC3B01000000", + "relaxed_extjson": "{\"a\" : {\"$date\" : \"2012-12-24T12:15:30.501Z\"}}", + "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"1356351330501\"}}}" + }, + { + "description": "negative", + "canonical_bson": "10000000096100C33CE7B9BDFFFFFF00", + "relaxed_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"-284643869501\"}}}", + "canonical_extjson": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"-284643869501\"}}}" + }, + { + "description" : "Y10K", + "canonical_bson" : "1000000009610000DC1FD277E6000000", + "canonical_extjson" : "{\"a\":{\"$date\":{\"$numberLong\":\"253402300800000\"}}}" + } + ], + "decodeErrors": [ + { + "description": "datetime field truncated", + "bson": "0C0000000961001234567800" + } + ] +} diff --git a/bson/src/test/resources/bson/dbpointer.json b/bson/src/test/resources/bson/dbpointer.json new file mode 100644 index 00000000000..377e556a0ad --- /dev/null +++ b/bson/src/test/resources/bson/dbpointer.json @@ -0,0 +1,56 @@ +{ + "description": "DBPointer type (deprecated)", + "bson_type": "0x0C", + "deprecated": true, + "test_key": "a", + "valid": [ + { + "description": "DBpointer", + "canonical_bson": "1A0000000C610002000000620056E1FC72E0C917E9C471416100", + "canonical_extjson": "{\"a\": {\"$dbPointer\": {\"$ref\": \"b\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}}", + "converted_bson": "2a00000003610022000000022472656600020000006200072469640056e1fc72e0c917e9c47141610000", + "converted_extjson": "{\"a\": {\"$ref\": \"b\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}" + }, + { + "description": "DBpointer with opposite key order", + "canonical_bson": "1A0000000C610002000000620056E1FC72E0C917E9C471416100", + "canonical_extjson": "{\"a\": {\"$dbPointer\": {\"$ref\": \"b\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}}", + "degenerate_extjson": "{\"a\": {\"$dbPointer\": {\"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}, \"$ref\": \"b\"}}}", + "converted_bson": "2a00000003610022000000022472656600020000006200072469640056e1fc72e0c917e9c47141610000", + "converted_extjson": "{\"a\": {\"$ref\": \"b\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}" + }, + { + "description": "With two-byte UTF-8", + "canonical_bson": "1B0000000C610003000000C3A90056E1FC72E0C917E9C471416100", + "canonical_extjson": "{\"a\": {\"$dbPointer\": {\"$ref\": \"é\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}}", + "converted_bson": "2B0000000361002300000002247265660003000000C3A900072469640056E1FC72E0C917E9C47141610000", + "converted_extjson": "{\"a\": {\"$ref\": \"é\", \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}}}" + } + ], + "decodeErrors": [ + { + "description": "String with negative length", + "bson": "1A0000000C6100FFFFFFFF620056E1FC72E0C917E9C471416100" + }, + { + "description": "String with zero length", + "bson": "1A0000000C610000000000620056E1FC72E0C917E9C471416100" + }, + { + "description": "String not null terminated", + "bson": "1A0000000C610002000000626256E1FC72E0C917E9C471416100" + }, + { + "description": "short OID (less than minimum length for field)", + "bson": "160000000C61000300000061620056E1FC72E0C91700" + }, + { + "description": "short OID (greater than minimum, but truncated)", + "bson": "1A0000000C61000300000061620056E1FC72E0C917E9C4716100" + }, + { + "description": "String with bad UTF-8", + "bson": "1A0000000C610002000000E90056E1FC72E0C917E9C471416100" + } + ] +} diff --git a/bson/src/test/resources/bson/dbref.json b/bson/src/test/resources/bson/dbref.json new file mode 100644 index 00000000000..1fe12c6f68d --- /dev/null +++ b/bson/src/test/resources/bson/dbref.json @@ -0,0 +1,31 @@ +{ + "description": "DBRef", + "bson_type": "0x03", + "valid": [ + { + "description": "DBRef", + "canonical_bson": "37000000036462726566002b0000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e0000", + "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}}}" + }, + { + "description": "DBRef with database", + "canonical_bson": "4300000003646272656600370000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e0224646200030000006462000000", + "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}, \"$db\": \"db\"}}" + }, + { + "description": "DBRef with database and additional fields", + "canonical_bson": "48000000036462726566003c0000000224726566000b000000636f6c6c656374696f6e0010246964002a00000002246462000300000064620002666f6f0004000000626172000000", + "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$numberInt\": \"42\"}, \"$db\": \"db\", \"foo\": \"bar\"}}" + }, + { + "description": "DBRef with additional fields", + "canonical_bson": "4400000003646272656600380000000224726566000b000000636f6c6c656374696f6e00072469640058921b3e6e32ab156a22b59e02666f6f0004000000626172000000", + "canonical_extjson": "{\"dbref\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}, \"foo\": \"bar\"}}" + }, + { + "description": "Document with key names similar to those of a DBRef", + "canonical_bson": "3e0000000224726566000c0000006e6f742d612d646272656600072469640058921b3e6e32ab156a22b59e022462616e616e6100050000007065656c0000", + "canonical_extjson": "{\"$ref\": \"not-a-dbref\", \"$id\": {\"$oid\": \"58921b3e6e32ab156a22b59e\"}, \"$banana\": \"peel\"}" + } + ] +} diff --git a/bson/src/test/resources/bson/decimal128-1.json b/bson/src/test/resources/bson/decimal128-1.json new file mode 100644 index 00000000000..7eefec6bf79 --- /dev/null +++ b/bson/src/test/resources/bson/decimal128-1.json @@ -0,0 +1,317 @@ +{ + "description": "Decimal128", + "bson_type": "0x13", + "test_key": "d", + "valid": [ + { + "description": "Special - Canonical NaN", + "canonical_bson": "180000001364000000000000000000000000000000007C00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}" + }, + { + "description": "Special - Negative NaN", + "canonical_bson": "18000000136400000000000000000000000000000000FC00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", + "lossy": true + }, + { + "description": "Special - Negative NaN", + "canonical_bson": "18000000136400000000000000000000000000000000FC00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-NaN\"}}", + "lossy": true + }, + { + "description": "Special - Canonical SNaN", + "canonical_bson": "180000001364000000000000000000000000000000007E00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", + "lossy": true + }, + { + "description": "Special - Negative SNaN", + "canonical_bson": "18000000136400000000000000000000000000000000FE00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", + "lossy": true + }, + { + "description": "Special - NaN with a payload", + "canonical_bson": "180000001364001200000000000000000000000000007E00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}", + "lossy": true + }, + { + "description": "Special - Canonical Positive Infinity", + "canonical_bson": "180000001364000000000000000000000000000000007800", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" + }, + { + "description": "Special - Canonical Negative Infinity", + "canonical_bson": "18000000136400000000000000000000000000000000F800", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" + }, + { + "description": "Special - Invalid representation treated as 0", + "canonical_bson": "180000001364000000000000000000000000000000106C00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}", + "lossy": true + }, + { + "description": "Special - Invalid representation treated as -0", + "canonical_bson": "18000000136400DCBA9876543210DEADBEEF00000010EC00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}", + "lossy": true + }, + { + "description": "Special - Invalid representation treated as 0E3", + "canonical_bson": "18000000136400FFFFFFFFFFFFFFFFFFFFFFFFFFFF116C00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}", + "lossy": true + }, + { + "description": "Regular - Adjusted Exponent Limit", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CF22F00", + "canonical_extjson": "{\"d\": { \"$numberDecimal\": \"0.000001234567890123456789012345678901234\" }}" + }, + { + "description": "Regular - Smallest", + "canonical_bson": "18000000136400D204000000000000000000000000343000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001234\"}}" + }, + { + "description": "Regular - Smallest with Trailing Zeros", + "canonical_bson": "1800000013640040EF5A07000000000000000000002A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00123400000\"}}" + }, + { + "description": "Regular - 0.1", + "canonical_bson": "1800000013640001000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1\"}}" + }, + { + "description": "Regular - 0.1234567890123456789012345678901234", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CFC2F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1234567890123456789012345678901234\"}}" + }, + { + "description": "Regular - 0", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "Regular - -0", + "canonical_bson": "18000000136400000000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" + }, + { + "description": "Regular - -0.0", + "canonical_bson": "1800000013640000000000000000000000000000003EB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}" + }, + { + "description": "Regular - 2", + "canonical_bson": "180000001364000200000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2\"}}" + }, + { + "description": "Regular - 2.000", + "canonical_bson": "18000000136400D0070000000000000000000000003A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2.000\"}}" + }, + { + "description": "Regular - Largest", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3C403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1234567890123456789012345678901234\"}}" + }, + { + "description": "Scientific - Tiniest", + "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09ED010000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9.999999999999999999999999999999999E-6143\"}}" + }, + { + "description": "Scientific - Tiny", + "canonical_bson": "180000001364000100000000000000000000000000000000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" + }, + { + "description": "Scientific - Negative Tiny", + "canonical_bson": "180000001364000100000000000000000000000000008000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}" + }, + { + "description": "Scientific - Adjusted Exponent Limit", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CF02F00", + "canonical_extjson": "{\"d\": { \"$numberDecimal\": \"1.234567890123456789012345678901234E-7\" }}" + }, + { + "description": "Scientific - Fractional", + "canonical_bson": "1800000013640064000000000000000000000000002CB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00E-8\"}}" + }, + { + "description": "Scientific - 0 with Exponent", + "canonical_bson": "180000001364000000000000000000000000000000205F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6000\"}}" + }, + { + "description": "Scientific - 0 with Negative Exponent", + "canonical_bson": "1800000013640000000000000000000000000000007A2B00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-611\"}}" + }, + { + "description": "Scientific - No Decimal with Signed Exponent", + "canonical_bson": "180000001364000100000000000000000000000000463000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+3\"}}" + }, + { + "description": "Scientific - Trailing Zero", + "canonical_bson": "180000001364001A04000000000000000000000000423000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.050E+4\"}}" + }, + { + "description": "Scientific - With Decimal", + "canonical_bson": "180000001364006900000000000000000000000000423000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.05E+3\"}}" + }, + { + "description": "Scientific - Full", + "canonical_bson": "18000000136400FFFFFFFFFFFFFFFFFFFFFFFFFFFF403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"5192296858534827628530496329220095\"}}" + }, + { + "description": "Scientific - Large", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}" + }, + { + "description": "Scientific - Largest", + "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09EDFF5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9.999999999999999999999999999999999E+6144\"}}" + }, + { + "description": "Non-Canonical Parsing - Exponent Normalization", + "canonical_bson": "1800000013640064000000000000000000000000002CB000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-100E-10\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00E-8\"}}" + }, + { + "description": "Non-Canonical Parsing - Unsigned Positive Exponent", + "canonical_bson": "180000001364000100000000000000000000000000463000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+3\"}}" + }, + { + "description": "Non-Canonical Parsing - Lowercase Exponent Identifier", + "canonical_bson": "180000001364000100000000000000000000000000463000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e+3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+3\"}}" + }, + { + "description": "Non-Canonical Parsing - Long Significand with Exponent", + "canonical_bson": "1800000013640079D9E0F9763ADA429D0200000000583000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12345689012345789012345E+12\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.2345689012345789012345E+34\"}}" + }, + { + "description": "Non-Canonical Parsing - Positive Sign", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3C403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+1234567890123456789012345678901234\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1234567890123456789012345678901234\"}}" + }, + { + "description": "Non-Canonical Parsing - Long Decimal String", + "canonical_bson": "180000001364000100000000000000000000000000722800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \".000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-999\"}}" + }, + { + "description": "Non-Canonical Parsing - nan", + "canonical_bson": "180000001364000000000000000000000000000000007C00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"nan\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}" + }, + { + "description": "Non-Canonical Parsing - nAn", + "canonical_bson": "180000001364000000000000000000000000000000007C00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"nAn\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}" + }, + { + "description": "Non-Canonical Parsing - +infinity", + "canonical_bson": "180000001364000000000000000000000000000000007800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+infinity\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - infinity", + "canonical_bson": "180000001364000000000000000000000000000000007800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"infinity\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - infiniTY", + "canonical_bson": "180000001364000000000000000000000000000000007800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"infiniTY\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - inf", + "canonical_bson": "180000001364000000000000000000000000000000007800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"inf\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - inF", + "canonical_bson": "180000001364000000000000000000000000000000007800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"inF\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - -infinity", + "canonical_bson": "18000000136400000000000000000000000000000000F800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-infinity\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - -infiniTy", + "canonical_bson": "18000000136400000000000000000000000000000000F800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-infiniTy\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - -Inf", + "canonical_bson": "18000000136400000000000000000000000000000000F800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - -inf", + "canonical_bson": "18000000136400000000000000000000000000000000F800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-inf\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" + }, + { + "description": "Non-Canonical Parsing - -inF", + "canonical_bson": "18000000136400000000000000000000000000000000F800", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-inF\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" + }, + { + "description": "Rounded Subnormal number", + "canonical_bson": "180000001364000100000000000000000000000000000000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10E-6177\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" + }, + { + "description": "Clamped", + "canonical_bson": "180000001364000a00000000000000000000000000fe5f00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E6112\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+6112\"}}" + }, + { + "description": "Exact rounding", + "canonical_bson": "18000000136400000000000a5bc138938d44c64d31cc3700", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+999\"}}" + } + ] +} diff --git a/bson/src/test/resources/bson/decimal128-2.json b/bson/src/test/resources/bson/decimal128-2.json new file mode 100644 index 00000000000..316d3b0e618 --- /dev/null +++ b/bson/src/test/resources/bson/decimal128-2.json @@ -0,0 +1,793 @@ +{ + "description": "Decimal128", + "bson_type": "0x13", + "test_key": "d", + "valid": [ + { + "description": "[decq021] Normality", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3C40B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1234567890123456789012345678901234\"}}" + }, + { + "description": "[decq823] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "18000000136400010000800000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-2147483649\"}}" + }, + { + "description": "[decq822] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "18000000136400000000800000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-2147483648\"}}" + }, + { + "description": "[decq821] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "18000000136400FFFFFF7F0000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-2147483647\"}}" + }, + { + "description": "[decq820] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "18000000136400FEFFFF7F0000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-2147483646\"}}" + }, + { + "description": "[decq152] fold-downs (more below)", + "canonical_bson": "18000000136400393000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-12345\"}}" + }, + { + "description": "[decq154] fold-downs (more below)", + "canonical_bson": "18000000136400D20400000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1234\"}}" + }, + { + "description": "[decq006] derivative canonical plain strings", + "canonical_bson": "18000000136400EE0200000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-750\"}}" + }, + { + "description": "[decq164] fold-downs (more below)", + "canonical_bson": "1800000013640039300000000000000000000000003CB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-123.45\"}}" + }, + { + "description": "[decq156] fold-downs (more below)", + "canonical_bson": "180000001364007B0000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-123\"}}" + }, + { + "description": "[decq008] derivative canonical plain strings", + "canonical_bson": "18000000136400EE020000000000000000000000003EB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-75.0\"}}" + }, + { + "description": "[decq158] fold-downs (more below)", + "canonical_bson": "180000001364000C0000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-12\"}}" + }, + { + "description": "[decq122] Nmax and similar", + "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09EDFFDF00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.999999999999999999999999999999999E+6144\"}}" + }, + { + "description": "[decq002] (mostly derived from the Strawman 4 document and examples)", + "canonical_bson": "18000000136400EE020000000000000000000000003CB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-7.50\"}}" + }, + { + "description": "[decq004] derivative canonical plain strings", + "canonical_bson": "18000000136400EE0200000000000000000000000042B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-7.50E+3\"}}" + }, + { + "description": "[decq018] derivative canonical plain strings", + "canonical_bson": "18000000136400EE020000000000000000000000002EB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-7.50E-7\"}}" + }, + { + "description": "[decq125] Nmax and similar", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CFEDF00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.234567890123456789012345678901234E+6144\"}}" + }, + { + "description": "[decq131] fold-downs (more below)", + "canonical_bson": "18000000136400000000807F1BCF85B27059C8A43CFEDF00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.230000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq162] fold-downs (more below)", + "canonical_bson": "180000001364007B000000000000000000000000003CB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.23\"}}" + }, + { + "description": "[decq176] Nmin and below", + "canonical_bson": "18000000136400010000000A5BC138938D44C64D31008000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.000000000000000000000000000000001E-6143\"}}" + }, + { + "description": "[decq174] Nmin and below", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31008000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.000000000000000000000000000000000E-6143\"}}" + }, + { + "description": "[decq133] fold-downs (more below)", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FEDF00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.000000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq160] fold-downs (more below)", + "canonical_bson": "18000000136400010000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1\"}}" + }, + { + "description": "[decq172] Nmin and below", + "canonical_bson": "180000001364000100000000000000000000000000428000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6143\"}}" + }, + { + "description": "[decq010] derivative canonical plain strings", + "canonical_bson": "18000000136400EE020000000000000000000000003AB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.750\"}}" + }, + { + "description": "[decq012] derivative canonical plain strings", + "canonical_bson": "18000000136400EE0200000000000000000000000038B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0750\"}}" + }, + { + "description": "[decq014] derivative canonical plain strings", + "canonical_bson": "18000000136400EE0200000000000000000000000034B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000750\"}}" + }, + { + "description": "[decq016] derivative canonical plain strings", + "canonical_bson": "18000000136400EE0200000000000000000000000030B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000750\"}}" + }, + { + "description": "[decq404] zeros", + "canonical_bson": "180000001364000000000000000000000000000000000000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" + }, + { + "description": "[decq424] negative zeros", + "canonical_bson": "180000001364000000000000000000000000000000008000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" + }, + { + "description": "[decq407] zeros", + "canonical_bson": "1800000013640000000000000000000000000000003C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" + }, + { + "description": "[decq427] negative zeros", + "canonical_bson": "1800000013640000000000000000000000000000003CB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" + }, + { + "description": "[decq409] zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[decq428] negative zeros", + "canonical_bson": "18000000136400000000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" + }, + { + "description": "[decq700] Selected DPD codes", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[decq406] zeros", + "canonical_bson": "1800000013640000000000000000000000000000003C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" + }, + { + "description": "[decq426] negative zeros", + "canonical_bson": "1800000013640000000000000000000000000000003CB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" + }, + { + "description": "[decq410] zeros", + "canonical_bson": "180000001364000000000000000000000000000000463000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}" + }, + { + "description": "[decq431] negative zeros", + "canonical_bson": "18000000136400000000000000000000000000000046B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+3\"}}" + }, + { + "description": "[decq419] clamped zeros...", + "canonical_bson": "180000001364000000000000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" + }, + { + "description": "[decq432] negative zeros", + "canonical_bson": "180000001364000000000000000000000000000000FEDF00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" + }, + { + "description": "[decq405] zeros", + "canonical_bson": "180000001364000000000000000000000000000000000000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" + }, + { + "description": "[decq425] negative zeros", + "canonical_bson": "180000001364000000000000000000000000000000008000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" + }, + { + "description": "[decq508] Specials", + "canonical_bson": "180000001364000000000000000000000000000000007800", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"Infinity\"}}" + }, + { + "description": "[decq528] Specials", + "canonical_bson": "18000000136400000000000000000000000000000000F800", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-Infinity\"}}" + }, + { + "description": "[decq541] Specials", + "canonical_bson": "180000001364000000000000000000000000000000007C00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"NaN\"}}" + }, + { + "description": "[decq074] Nmin and below", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31000000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E-6143\"}}" + }, + { + "description": "[decq602] fold-down full sequence", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq604] fold-down full sequence", + "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000000E+6143\"}}" + }, + { + "description": "[decq606] fold-down full sequence", + "canonical_bson": "1800000013640000000080264B91C02220BE377E00FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000000000E+6142\"}}" + }, + { + "description": "[decq608] fold-down full sequence", + "canonical_bson": "1800000013640000000040EAED7446D09C2C9F0C00FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000E+6141\"}}" + }, + { + "description": "[decq610] fold-down full sequence", + "canonical_bson": "18000000136400000000A0CA17726DAE0F1E430100FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000E+6140\"}}" + }, + { + "description": "[decq612] fold-down full sequence", + "canonical_bson": "18000000136400000000106102253E5ECE4F200000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000000E+6139\"}}" + }, + { + "description": "[decq614] fold-down full sequence", + "canonical_bson": "18000000136400000000E83C80D09F3C2E3B030000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000E+6138\"}}" + }, + { + "description": "[decq616] fold-down full sequence", + "canonical_bson": "18000000136400000000E4D20CC8DCD2B752000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000E+6137\"}}" + }, + { + "description": "[decq618] fold-down full sequence", + "canonical_bson": "180000001364000000004A48011416954508000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000E+6136\"}}" + }, + { + "description": "[decq620] fold-down full sequence", + "canonical_bson": "18000000136400000000A1EDCCCE1BC2D300000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000E+6135\"}}" + }, + { + "description": "[decq622] fold-down full sequence", + "canonical_bson": "18000000136400000080F64AE1C7022D1500000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000E+6134\"}}" + }, + { + "description": "[decq624] fold-down full sequence", + "canonical_bson": "18000000136400000040B2BAC9E0191E0200000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000E+6133\"}}" + }, + { + "description": "[decq626] fold-down full sequence", + "canonical_bson": "180000001364000000A0DEC5ADC935360000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000E+6132\"}}" + }, + { + "description": "[decq628] fold-down full sequence", + "canonical_bson": "18000000136400000010632D5EC76B050000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000E+6131\"}}" + }, + { + "description": "[decq630] fold-down full sequence", + "canonical_bson": "180000001364000000E8890423C78A000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000E+6130\"}}" + }, + { + "description": "[decq632] fold-down full sequence", + "canonical_bson": "18000000136400000064A7B3B6E00D000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000E+6129\"}}" + }, + { + "description": "[decq634] fold-down full sequence", + "canonical_bson": "1800000013640000008A5D78456301000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000E+6128\"}}" + }, + { + "description": "[decq636] fold-down full sequence", + "canonical_bson": "180000001364000000C16FF2862300000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000E+6127\"}}" + }, + { + "description": "[decq638] fold-down full sequence", + "canonical_bson": "180000001364000080C6A47E8D0300000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000E+6126\"}}" + }, + { + "description": "[decq640] fold-down full sequence", + "canonical_bson": "1800000013640000407A10F35A0000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000E+6125\"}}" + }, + { + "description": "[decq642] fold-down full sequence", + "canonical_bson": "1800000013640000A0724E18090000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000E+6124\"}}" + }, + { + "description": "[decq644] fold-down full sequence", + "canonical_bson": "180000001364000010A5D4E8000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000E+6123\"}}" + }, + { + "description": "[decq646] fold-down full sequence", + "canonical_bson": "1800000013640000E8764817000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000E+6122\"}}" + }, + { + "description": "[decq648] fold-down full sequence", + "canonical_bson": "1800000013640000E40B5402000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000E+6121\"}}" + }, + { + "description": "[decq650] fold-down full sequence", + "canonical_bson": "1800000013640000CA9A3B00000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000E+6120\"}}" + }, + { + "description": "[decq652] fold-down full sequence", + "canonical_bson": "1800000013640000E1F50500000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000E+6119\"}}" + }, + { + "description": "[decq654] fold-down full sequence", + "canonical_bson": "180000001364008096980000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000E+6118\"}}" + }, + { + "description": "[decq656] fold-down full sequence", + "canonical_bson": "1800000013640040420F0000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000E+6117\"}}" + }, + { + "description": "[decq658] fold-down full sequence", + "canonical_bson": "18000000136400A086010000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000E+6116\"}}" + }, + { + "description": "[decq660] fold-down full sequence", + "canonical_bson": "180000001364001027000000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000E+6115\"}}" + }, + { + "description": "[decq662] fold-down full sequence", + "canonical_bson": "18000000136400E803000000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000E+6114\"}}" + }, + { + "description": "[decq664] fold-down full sequence", + "canonical_bson": "180000001364006400000000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+6113\"}}" + }, + { + "description": "[decq666] fold-down full sequence", + "canonical_bson": "180000001364000A00000000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+6112\"}}" + }, + { + "description": "[decq060] fold-downs (more below)", + "canonical_bson": "180000001364000100000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1\"}}" + }, + { + "description": "[decq670] fold-down full sequence", + "canonical_bson": "180000001364000100000000000000000000000000FC5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6110\"}}" + }, + { + "description": "[decq668] fold-down full sequence", + "canonical_bson": "180000001364000100000000000000000000000000FE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6111\"}}" + }, + { + "description": "[decq072] Nmin and below", + "canonical_bson": "180000001364000100000000000000000000000000420000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6143\"}}" + }, + { + "description": "[decq076] Nmin and below", + "canonical_bson": "18000000136400010000000A5BC138938D44C64D31000000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000001E-6143\"}}" + }, + { + "description": "[decq036] fold-downs (more below)", + "canonical_bson": "18000000136400000000807F1BCF85B27059C8A43CFE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.230000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq062] fold-downs (more below)", + "canonical_bson": "180000001364007B000000000000000000000000003C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.23\"}}" + }, + { + "description": "[decq034] Nmax and similar", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3CFE5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.234567890123456789012345678901234E+6144\"}}" + }, + { + "description": "[decq441] exponent lengths", + "canonical_bson": "180000001364000700000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7\"}}" + }, + { + "description": "[decq449] exponent lengths", + "canonical_bson": "1800000013640007000000000000000000000000001E5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+5999\"}}" + }, + { + "description": "[decq447] exponent lengths", + "canonical_bson": "1800000013640007000000000000000000000000000E3800", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+999\"}}" + }, + { + "description": "[decq445] exponent lengths", + "canonical_bson": "180000001364000700000000000000000000000000063100", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+99\"}}" + }, + { + "description": "[decq443] exponent lengths", + "canonical_bson": "180000001364000700000000000000000000000000523000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+9\"}}" + }, + { + "description": "[decq842] VG testcase", + "canonical_bson": "180000001364000000FED83F4E7C9FE4E269E38A5BCD1700", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7.049000000000010795488000000000000E-3097\"}}" + }, + { + "description": "[decq841] VG testcase", + "canonical_bson": "180000001364000000203B9DB5056F000000000000002400", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"8.000000000000000000E-1550\"}}" + }, + { + "description": "[decq840] VG testcase", + "canonical_bson": "180000001364003C17258419D710C42F0000000000002400", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"8.81125000000001349436E-1548\"}}" + }, + { + "description": "[decq701] Selected DPD codes", + "canonical_bson": "180000001364000900000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9\"}}" + }, + { + "description": "[decq032] Nmax and similar", + "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09EDFF5F00", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9.999999999999999999999999999999999E+6144\"}}" + }, + { + "description": "[decq702] Selected DPD codes", + "canonical_bson": "180000001364000A00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10\"}}" + }, + { + "description": "[decq057] fold-downs (more below)", + "canonical_bson": "180000001364000C00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12\"}}" + }, + { + "description": "[decq703] Selected DPD codes", + "canonical_bson": "180000001364001300000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"19\"}}" + }, + { + "description": "[decq704] Selected DPD codes", + "canonical_bson": "180000001364001400000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"20\"}}" + }, + { + "description": "[decq705] Selected DPD codes", + "canonical_bson": "180000001364001D00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"29\"}}" + }, + { + "description": "[decq706] Selected DPD codes", + "canonical_bson": "180000001364001E00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"30\"}}" + }, + { + "description": "[decq707] Selected DPD codes", + "canonical_bson": "180000001364002700000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"39\"}}" + }, + { + "description": "[decq708] Selected DPD codes", + "canonical_bson": "180000001364002800000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"40\"}}" + }, + { + "description": "[decq709] Selected DPD codes", + "canonical_bson": "180000001364003100000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"49\"}}" + }, + { + "description": "[decq710] Selected DPD codes", + "canonical_bson": "180000001364003200000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"50\"}}" + }, + { + "description": "[decq711] Selected DPD codes", + "canonical_bson": "180000001364003B00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"59\"}}" + }, + { + "description": "[decq712] Selected DPD codes", + "canonical_bson": "180000001364003C00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"60\"}}" + }, + { + "description": "[decq713] Selected DPD codes", + "canonical_bson": "180000001364004500000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"69\"}}" + }, + { + "description": "[decq714] Selected DPD codes", + "canonical_bson": "180000001364004600000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"70\"}}" + }, + { + "description": "[decq715] Selected DPD codes", + "canonical_bson": "180000001364004700000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"71\"}}" + }, + { + "description": "[decq716] Selected DPD codes", + "canonical_bson": "180000001364004800000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"72\"}}" + }, + { + "description": "[decq717] Selected DPD codes", + "canonical_bson": "180000001364004900000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"73\"}}" + }, + { + "description": "[decq718] Selected DPD codes", + "canonical_bson": "180000001364004A00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"74\"}}" + }, + { + "description": "[decq719] Selected DPD codes", + "canonical_bson": "180000001364004B00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"75\"}}" + }, + { + "description": "[decq720] Selected DPD codes", + "canonical_bson": "180000001364004C00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"76\"}}" + }, + { + "description": "[decq721] Selected DPD codes", + "canonical_bson": "180000001364004D00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"77\"}}" + }, + { + "description": "[decq722] Selected DPD codes", + "canonical_bson": "180000001364004E00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"78\"}}" + }, + { + "description": "[decq723] Selected DPD codes", + "canonical_bson": "180000001364004F00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"79\"}}" + }, + { + "description": "[decq056] fold-downs (more below)", + "canonical_bson": "180000001364007B00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123\"}}" + }, + { + "description": "[decq064] fold-downs (more below)", + "canonical_bson": "1800000013640039300000000000000000000000003C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123.45\"}}" + }, + { + "description": "[decq732] Selected DPD codes", + "canonical_bson": "180000001364000802000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"520\"}}" + }, + { + "description": "[decq733] Selected DPD codes", + "canonical_bson": "180000001364000902000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"521\"}}" + }, + { + "description": "[decq740] DPD: one of each of the huffman groups", + "canonical_bson": "180000001364000903000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"777\"}}" + }, + { + "description": "[decq741] DPD: one of each of the huffman groups", + "canonical_bson": "180000001364000A03000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"778\"}}" + }, + { + "description": "[decq742] DPD: one of each of the huffman groups", + "canonical_bson": "180000001364001303000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"787\"}}" + }, + { + "description": "[decq746] DPD: one of each of the huffman groups", + "canonical_bson": "180000001364001F03000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"799\"}}" + }, + { + "description": "[decq743] DPD: one of each of the huffman groups", + "canonical_bson": "180000001364006D03000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"877\"}}" + }, + { + "description": "[decq753] DPD all-highs cases (includes the 24 redundant codes)", + "canonical_bson": "180000001364007803000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"888\"}}" + }, + { + "description": "[decq754] DPD all-highs cases (includes the 24 redundant codes)", + "canonical_bson": "180000001364007903000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"889\"}}" + }, + { + "description": "[decq760] DPD all-highs cases (includes the 24 redundant codes)", + "canonical_bson": "180000001364008203000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"898\"}}" + }, + { + "description": "[decq764] DPD all-highs cases (includes the 24 redundant codes)", + "canonical_bson": "180000001364008303000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"899\"}}" + }, + { + "description": "[decq745] DPD: one of each of the huffman groups", + "canonical_bson": "18000000136400D303000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"979\"}}" + }, + { + "description": "[decq770] DPD all-highs cases (includes the 24 redundant codes)", + "canonical_bson": "18000000136400DC03000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"988\"}}" + }, + { + "description": "[decq774] DPD all-highs cases (includes the 24 redundant codes)", + "canonical_bson": "18000000136400DD03000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"989\"}}" + }, + { + "description": "[decq730] Selected DPD codes", + "canonical_bson": "18000000136400E203000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"994\"}}" + }, + { + "description": "[decq731] Selected DPD codes", + "canonical_bson": "18000000136400E303000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"995\"}}" + }, + { + "description": "[decq744] DPD: one of each of the huffman groups", + "canonical_bson": "18000000136400E503000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"997\"}}" + }, + { + "description": "[decq780] DPD all-highs cases (includes the 24 redundant codes)", + "canonical_bson": "18000000136400E603000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"998\"}}" + }, + { + "description": "[decq787] DPD all-highs cases (includes the 24 redundant codes)", + "canonical_bson": "18000000136400E703000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"999\"}}" + }, + { + "description": "[decq053] fold-downs (more below)", + "canonical_bson": "18000000136400D204000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1234\"}}" + }, + { + "description": "[decq052] fold-downs (more below)", + "canonical_bson": "180000001364003930000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12345\"}}" + }, + { + "description": "[decq792] Miscellaneous (testers' queries, etc.)", + "canonical_bson": "180000001364003075000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"30000\"}}" + }, + { + "description": "[decq793] Miscellaneous (testers' queries, etc.)", + "canonical_bson": "1800000013640090940D0000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"890000\"}}" + }, + { + "description": "[decq824] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "18000000136400FEFFFF7F00000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2147483646\"}}" + }, + { + "description": "[decq825] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "18000000136400FFFFFF7F00000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2147483647\"}}" + }, + { + "description": "[decq826] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "180000001364000000008000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2147483648\"}}" + }, + { + "description": "[decq827] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "180000001364000100008000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2147483649\"}}" + }, + { + "description": "[decq828] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "18000000136400FEFFFFFF00000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4294967294\"}}" + }, + { + "description": "[decq829] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "18000000136400FFFFFFFF00000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4294967295\"}}" + }, + { + "description": "[decq830] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "180000001364000000000001000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4294967296\"}}" + }, + { + "description": "[decq831] values around [u]int32 edges (zeros done earlier)", + "canonical_bson": "180000001364000100000001000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4294967297\"}}" + }, + { + "description": "[decq022] Normality", + "canonical_bson": "18000000136400C7711CC7B548F377DC80A131C836403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1111111111111111111111111111111111\"}}" + }, + { + "description": "[decq020] Normality", + "canonical_bson": "18000000136400F2AF967ED05C82DE3297FF6FDE3C403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1234567890123456789012345678901234\"}}" + }, + { + "description": "[decq550] Specials", + "canonical_bson": "18000000136400FFFFFFFF638E8D37C087ADBE09ED413000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9999999999999999999999999999999999\"}}" + } + ] +} + diff --git a/bson/src/test/resources/bson/decimal128-3.json b/bson/src/test/resources/bson/decimal128-3.json new file mode 100644 index 00000000000..9b015343ce7 --- /dev/null +++ b/bson/src/test/resources/bson/decimal128-3.json @@ -0,0 +1,1771 @@ +{ + "description": "Decimal128", + "bson_type": "0x13", + "test_key": "d", + "valid": [ + { + "description": "[basx066] strings without E cannot generate E in result", + "canonical_bson": "18000000136400185C0ACE0000000000000000000038B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-00345678.5432\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-345678.5432\"}}" + }, + { + "description": "[basx065] strings without E cannot generate E in result", + "canonical_bson": "18000000136400185C0ACE0000000000000000000038B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0345678.5432\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-345678.5432\"}}" + }, + { + "description": "[basx064] strings without E cannot generate E in result", + "canonical_bson": "18000000136400185C0ACE0000000000000000000038B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-345678.5432\"}}" + }, + { + "description": "[basx041] strings without E cannot generate E in result", + "canonical_bson": "180000001364004C0000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-76\"}}" + }, + { + "description": "[basx027] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364000F270000000000000000000000003AB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.999\"}}" + }, + { + "description": "[basx026] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364009F230000000000000000000000003AB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.119\"}}" + }, + { + "description": "[basx025] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364008F030000000000000000000000003CB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.11\"}}" + }, + { + "description": "[basx024] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364005B000000000000000000000000003EB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.1\"}}" + }, + { + "description": "[dqbsr531] negatives (Rounded)", + "canonical_bson": "1800000013640099761CC7B548F377DC80A131C836FEAF00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.1111111111111111111111111111123450\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.111111111111111111111111111112345\"}}" + }, + { + "description": "[basx022] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364000A000000000000000000000000003EB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.0\"}}" + }, + { + "description": "[basx021] conform to rules and exponent will be in permitted range).", + "canonical_bson": "18000000136400010000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1\"}}" + }, + { + "description": "[basx601] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000002E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-9\"}}" + }, + { + "description": "[basx622] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000002EB000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-9\"}}" + }, + { + "description": "[basx602] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000303000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-8\"}}" + }, + { + "description": "[basx621] Zeros", + "canonical_bson": "18000000136400000000000000000000000000000030B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-8\"}}" + }, + { + "description": "[basx603] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" + }, + { + "description": "[basx620] Zeros", + "canonical_bson": "18000000136400000000000000000000000000000032B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-7\"}}" + }, + { + "description": "[basx604] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000343000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" + }, + { + "description": "[basx619] Zeros", + "canonical_bson": "18000000136400000000000000000000000000000034B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000\"}}" + }, + { + "description": "[basx605] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000363000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" + }, + { + "description": "[basx618] Zeros", + "canonical_bson": "18000000136400000000000000000000000000000036B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000\"}}" + }, + { + "description": "[basx680] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"000000.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx606] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000383000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" + }, + { + "description": "[basx617] Zeros", + "canonical_bson": "18000000136400000000000000000000000000000038B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000\"}}" + }, + { + "description": "[basx681] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00000.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx686] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+00000.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx687] Zeros", + "canonical_bson": "18000000136400000000000000000000000000000040B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-00000.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" + }, + { + "description": "[basx019] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640000000000000000000000000000003CB000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-00.00\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" + }, + { + "description": "[basx607] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000\"}}" + }, + { + "description": "[basx616] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003AB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000\"}}" + }, + { + "description": "[basx682] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0000.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx155] Numbers with E", + "canonical_bson": "1800000013640000000000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000e+0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000\"}}" + }, + { + "description": "[basx130] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" + }, + { + "description": "[basx290] some more negative zeros [systematic tests below]", + "canonical_bson": "18000000136400000000000000000000000000000038B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000\"}}" + }, + { + "description": "[basx131] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" + }, + { + "description": "[basx291] some more negative zeros [systematic tests below]", + "canonical_bson": "18000000136400000000000000000000000000000036B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000\"}}" + }, + { + "description": "[basx132] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" + }, + { + "description": "[basx292] some more negative zeros [systematic tests below]", + "canonical_bson": "18000000136400000000000000000000000000000034B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000\"}}" + }, + { + "description": "[basx133] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" + }, + { + "description": "[basx293] some more negative zeros [systematic tests below]", + "canonical_bson": "18000000136400000000000000000000000000000032B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-7\"}}" + }, + { + "description": "[basx608] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" + }, + { + "description": "[basx615] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003CB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" + }, + { + "description": "[basx683] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"000.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx630] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" + }, + { + "description": "[basx670] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" + }, + { + "description": "[basx631] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" + }, + { + "description": "[basx671] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000\"}}" + }, + { + "description": "[basx134] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" + }, + { + "description": "[basx294] some more negative zeros [systematic tests below]", + "canonical_bson": "18000000136400000000000000000000000000000038B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000\"}}" + }, + { + "description": "[basx632] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx672] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" + }, + { + "description": "[basx135] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" + }, + { + "description": "[basx295] some more negative zeros [systematic tests below]", + "canonical_bson": "18000000136400000000000000000000000000000036B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000\"}}" + }, + { + "description": "[basx633] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000423000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+1\"}}" + }, + { + "description": "[basx673] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" + }, + { + "description": "[basx136] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" + }, + { + "description": "[basx674] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" + }, + { + "description": "[basx634] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000443000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+2\"}}" + }, + { + "description": "[basx137] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" + }, + { + "description": "[basx635] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000463000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}" + }, + { + "description": "[basx675] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" + }, + { + "description": "[basx636] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000483000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+4\"}}" + }, + { + "description": "[basx676] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000303000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-8\"}}" + }, + { + "description": "[basx637] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+5\"}}" + }, + { + "description": "[basx677] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000002E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-9\"}}" + }, + { + "description": "[basx638] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6\"}}" + }, + { + "description": "[basx678] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000002C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-10\"}}" + }, + { + "description": "[basx149] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"000E+9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" + }, + { + "description": "[basx639] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E+9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+7\"}}" + }, + { + "description": "[basx679] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000002A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00E-9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-11\"}}" + }, + { + "description": "[basx063] strings without E cannot generate E in result", + "canonical_bson": "18000000136400185C0ACE00000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+00345678.5432\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.5432\"}}" + }, + { + "description": "[basx018] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640000000000000000000000000000003EB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}" + }, + { + "description": "[basx609] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" + }, + { + "description": "[basx614] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003EB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}" + }, + { + "description": "[basx684] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx640] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" + }, + { + "description": "[basx660] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" + }, + { + "description": "[basx641] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx661] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00\"}}" + }, + { + "description": "[basx296] some more negative zeros [systematic tests below]", + "canonical_bson": "1800000013640000000000000000000000000000003AB000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000\"}}" + }, + { + "description": "[basx642] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000423000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+1\"}}" + }, + { + "description": "[basx662] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000\"}}" + }, + { + "description": "[basx297] some more negative zeros [systematic tests below]", + "canonical_bson": "18000000136400000000000000000000000000000038B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0000\"}}" + }, + { + "description": "[basx643] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000443000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+2\"}}" + }, + { + "description": "[basx663] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000\"}}" + }, + { + "description": "[basx644] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000463000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}" + }, + { + "description": "[basx664] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000\"}}" + }, + { + "description": "[basx645] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000483000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+4\"}}" + }, + { + "description": "[basx665] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000\"}}" + }, + { + "description": "[basx646] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+5\"}}" + }, + { + "description": "[basx666] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-7\"}}" + }, + { + "description": "[basx647] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6\"}}" + }, + { + "description": "[basx667] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000303000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-8\"}}" + }, + { + "description": "[basx648] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+7\"}}" + }, + { + "description": "[basx668] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000002E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-9\"}}" + }, + { + "description": "[basx160] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00E+9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" + }, + { + "description": "[basx161] Numbers with E", + "canonical_bson": "1800000013640000000000000000000000000000002E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00E-9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-9\"}}" + }, + { + "description": "[basx649] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000503000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E+9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+8\"}}" + }, + { + "description": "[basx669] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000002C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0E-9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-10\"}}" + }, + { + "description": "[basx062] strings without E cannot generate E in result", + "canonical_bson": "18000000136400185C0ACE00000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+0345678.5432\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.5432\"}}" + }, + { + "description": "[basx001] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx017] conform to rules and exponent will be in permitted range).", + "canonical_bson": "18000000136400000000000000000000000000000040B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" + }, + { + "description": "[basx611] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx613] Zeros", + "canonical_bson": "18000000136400000000000000000000000000000040B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" + }, + { + "description": "[basx685] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx688] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+0.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx689] Zeros", + "canonical_bson": "18000000136400000000000000000000000000000040B000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0\"}}" + }, + { + "description": "[basx650] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0\"}}" + }, + { + "description": "[basx651] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000423000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+1\"}}" + }, + { + "description": "[basx298] some more negative zeros [systematic tests below]", + "canonical_bson": "1800000013640000000000000000000000000000003CB000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00\"}}" + }, + { + "description": "[basx652] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000443000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+2\"}}" + }, + { + "description": "[basx299] some more negative zeros [systematic tests below]", + "canonical_bson": "1800000013640000000000000000000000000000003AB000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000\"}}" + }, + { + "description": "[basx653] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000463000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+3\"}}" + }, + { + "description": "[basx654] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000483000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+4\"}}" + }, + { + "description": "[basx655] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+5\"}}" + }, + { + "description": "[basx656] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6\"}}" + }, + { + "description": "[basx657] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000004E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+7\"}}" + }, + { + "description": "[basx658] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000503000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+8\"}}" + }, + { + "description": "[basx138] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+0E+9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" + }, + { + "description": "[basx139] Numbers with E", + "canonical_bson": "18000000136400000000000000000000000000000052B000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+9\"}}" + }, + { + "description": "[basx144] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000523000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" + }, + { + "description": "[basx154] Numbers with E", + "canonical_bson": "180000001364000000000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" + }, + { + "description": "[basx659] Zeros", + "canonical_bson": "180000001364000000000000000000000000000000523000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+9\"}}" + }, + { + "description": "[basx042] strings without E cannot generate E in result", + "canonical_bson": "18000000136400FC040000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+12.76\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.76\"}}" + }, + { + "description": "[basx143] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+1E+009\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" + }, + { + "description": "[basx061] strings without E cannot generate E in result", + "canonical_bson": "18000000136400185C0ACE00000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+345678.5432\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.5432\"}}" + }, + { + "description": "[basx036] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640015CD5B0700000000000000000000203000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000000123456789\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.23456789E-8\"}}" + }, + { + "description": "[basx035] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640015CD5B0700000000000000000000223000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000123456789\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.23456789E-7\"}}" + }, + { + "description": "[basx034] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640015CD5B0700000000000000000000243000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000123456789\"}}" + }, + { + "description": "[basx053] strings without E cannot generate E in result", + "canonical_bson": "180000001364003200000000000000000000000000323000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000050\"}}" + }, + { + "description": "[basx033] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640015CD5B0700000000000000000000263000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000123456789\"}}" + }, + { + "description": "[basx016] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364000C000000000000000000000000003A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.012\"}}" + }, + { + "description": "[basx015] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364007B000000000000000000000000003A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.123\"}}" + }, + { + "description": "[basx037] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640078DF0D8648700000000000000000223000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.123456789012344\"}}" + }, + { + "description": "[basx038] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640079DF0D8648700000000000000000223000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.123456789012345\"}}" + }, + { + "description": "[basx250] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000383000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" + }, + { + "description": "[basx257] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" + }, + { + "description": "[basx256] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.01265\"}}" + }, + { + "description": "[basx258] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" + }, + { + "description": "[basx251] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000103000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-20\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-21\"}}" + }, + { + "description": "[basx263] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000603000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+20\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+19\"}}" + }, + { + "description": "[basx255] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001265\"}}" + }, + { + "description": "[basx259] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" + }, + { + "description": "[basx254] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0001265\"}}" + }, + { + "description": "[basx260] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" + }, + { + "description": "[basx253] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000303000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00001265\"}}" + }, + { + "description": "[basx261] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" + }, + { + "description": "[basx252] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000283000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E-8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-9\"}}" + }, + { + "description": "[basx262] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000483000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265E+8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+7\"}}" + }, + { + "description": "[basx159] Numbers with E", + "canonical_bson": "1800000013640049000000000000000000000000002E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.73e-7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7.3E-8\"}}" + }, + { + "description": "[basx004] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640064000000000000000000000000003C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00\"}}" + }, + { + "description": "[basx003] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364000A000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0\"}}" + }, + { + "description": "[basx002] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364000100000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1\"}}" + }, + { + "description": "[basx148] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+009\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" + }, + { + "description": "[basx153] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E009\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" + }, + { + "description": "[basx141] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e+09\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" + }, + { + "description": "[basx146] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+09\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" + }, + { + "description": "[basx151] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e09\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" + }, + { + "description": "[basx142] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000F43000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+90\"}}" + }, + { + "description": "[basx147] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000F43000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e+90\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+90\"}}" + }, + { + "description": "[basx152] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000F43000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E90\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+90\"}}" + }, + { + "description": "[basx140] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000523000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" + }, + { + "description": "[basx150] Numbers with E", + "canonical_bson": "180000001364000100000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+9\"}}" + }, + { + "description": "[basx014] conform to rules and exponent will be in permitted range).", + "canonical_bson": "18000000136400D2040000000000000000000000003A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.234\"}}" + }, + { + "description": "[basx170] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" + }, + { + "description": "[basx177] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" + }, + { + "description": "[basx176] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" + }, + { + "description": "[basx178] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" + }, + { + "description": "[basx171] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000123000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-20\"}}" + }, + { + "description": "[basx183] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000623000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+20\"}}" + }, + { + "description": "[basx175] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.01265\"}}" + }, + { + "description": "[basx179] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" + }, + { + "description": "[basx174] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001265\"}}" + }, + { + "description": "[basx180] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" + }, + { + "description": "[basx173] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0001265\"}}" + }, + { + "description": "[basx181] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000423000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+4\"}}" + }, + { + "description": "[basx172] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000002A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-8\"}}" + }, + { + "description": "[basx182] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000004A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+8\"}}" + }, + { + "description": "[basx157] Numbers with E", + "canonical_bson": "180000001364000400000000000000000000000000523000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4E+9\"}}" + }, + { + "description": "[basx067] examples", + "canonical_bson": "180000001364000500000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"5E-6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000005\"}}" + }, + { + "description": "[basx069] examples", + "canonical_bson": "180000001364000500000000000000000000000000323000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"5E-7\"}}" + }, + { + "description": "[basx385] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7\"}}" + }, + { + "description": "[basx365] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000543000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E10\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+10\"}}" + }, + { + "description": "[basx405] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000002C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-10\"}}" + }, + { + "description": "[basx363] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000563000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E11\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+11\"}}" + }, + { + "description": "[basx407] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000002A3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-11\"}}" + }, + { + "description": "[basx361] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000583000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E12\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+12\"}}" + }, + { + "description": "[basx409] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000283000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-12\"}}" + }, + { + "description": "[basx411] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000263000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-13\"}}" + }, + { + "description": "[basx383] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000423000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+1\"}}" + }, + { + "description": "[basx387] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.7\"}}" + }, + { + "description": "[basx381] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000443000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+2\"}}" + }, + { + "description": "[basx389] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.07\"}}" + }, + { + "description": "[basx379] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000463000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+3\"}}" + }, + { + "description": "[basx391] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.007\"}}" + }, + { + "description": "[basx377] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000483000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+4\"}}" + }, + { + "description": "[basx393] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0007\"}}" + }, + { + "description": "[basx375] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000004A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+5\"}}" + }, + { + "description": "[basx395] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00007\"}}" + }, + { + "description": "[basx373] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000004C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+6\"}}" + }, + { + "description": "[basx397] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000007\"}}" + }, + { + "description": "[basx371] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000004E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+7\"}}" + }, + { + "description": "[basx399] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000323000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-7\"}}" + }, + { + "description": "[basx369] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000503000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+8\"}}" + }, + { + "description": "[basx401] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000303000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-8\"}}" + }, + { + "description": "[basx367] Engineering notation tests", + "canonical_bson": "180000001364000700000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E+9\"}}" + }, + { + "description": "[basx403] Engineering notation tests", + "canonical_bson": "1800000013640007000000000000000000000000002E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"7E-9\"}}" + }, + { + "description": "[basx007] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640064000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.0\"}}" + }, + { + "description": "[basx005] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364000A00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10\"}}" + }, + { + "description": "[basx165] Numbers with E", + "canonical_bson": "180000001364000A00000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10E+009\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+10\"}}" + }, + { + "description": "[basx163] Numbers with E", + "canonical_bson": "180000001364000A00000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10E+09\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+10\"}}" + }, + { + "description": "[basx325] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10\"}}" + }, + { + "description": "[basx305] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000543000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e10\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+11\"}}" + }, + { + "description": "[basx345] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000002C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-10\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-9\"}}" + }, + { + "description": "[basx303] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000563000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e11\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+12\"}}" + }, + { + "description": "[basx347] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000002A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-11\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-10\"}}" + }, + { + "description": "[basx301] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000583000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e12\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+13\"}}" + }, + { + "description": "[basx349] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000283000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-12\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-11\"}}" + }, + { + "description": "[basx351] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000263000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-13\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-12\"}}" + }, + { + "description": "[basx323] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000423000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+2\"}}" + }, + { + "description": "[basx327] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0\"}}" + }, + { + "description": "[basx321] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000443000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+3\"}}" + }, + { + "description": "[basx329] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.10\"}}" + }, + { + "description": "[basx319] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000463000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+4\"}}" + }, + { + "description": "[basx331] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.010\"}}" + }, + { + "description": "[basx317] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000483000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+5\"}}" + }, + { + "description": "[basx333] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0010\"}}" + }, + { + "description": "[basx315] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000004A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+6\"}}" + }, + { + "description": "[basx335] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00010\"}}" + }, + { + "description": "[basx313] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000004C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+7\"}}" + }, + { + "description": "[basx337] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-6\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000010\"}}" + }, + { + "description": "[basx311] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000004E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+8\"}}" + }, + { + "description": "[basx339] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000010\"}}" + }, + { + "description": "[basx309] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000503000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+9\"}}" + }, + { + "description": "[basx341] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000303000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-7\"}}" + }, + { + "description": "[basx164] Numbers with E", + "canonical_bson": "180000001364000A00000000000000000000000000F43000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e+90\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+91\"}}" + }, + { + "description": "[basx162] Numbers with E", + "canonical_bson": "180000001364000A00000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10E+9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+10\"}}" + }, + { + "description": "[basx307] Engineering notation tests", + "canonical_bson": "180000001364000A00000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+10\"}}" + }, + { + "description": "[basx343] Engineering notation tests", + "canonical_bson": "180000001364000A000000000000000000000000002E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"10e-9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-8\"}}" + }, + { + "description": "[basx008] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640065000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.1\"}}" + }, + { + "description": "[basx009] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640068000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.4\"}}" + }, + { + "description": "[basx010] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640069000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.5\"}}" + }, + { + "description": "[basx011] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364006A000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.6\"}}" + }, + { + "description": "[basx012] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364006D000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"10.9\"}}" + }, + { + "description": "[basx013] conform to rules and exponent will be in permitted range).", + "canonical_bson": "180000001364006E000000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"11.0\"}}" + }, + { + "description": "[basx040] strings without E cannot generate E in result", + "canonical_bson": "180000001364000C00000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12\"}}" + }, + { + "description": "[basx190] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" + }, + { + "description": "[basx197] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" + }, + { + "description": "[basx196] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" + }, + { + "description": "[basx198] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" + }, + { + "description": "[basx191] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000143000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-20\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-19\"}}" + }, + { + "description": "[basx203] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000643000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+20\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+21\"}}" + }, + { + "description": "[basx195] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" + }, + { + "description": "[basx199] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" + }, + { + "description": "[basx194] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.01265\"}}" + }, + { + "description": "[basx200] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000423000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+4\"}}" + }, + { + "description": "[basx193] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000343000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.001265\"}}" + }, + { + "description": "[basx201] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000443000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+5\"}}" + }, + { + "description": "[basx192] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000002C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E-8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-7\"}}" + }, + { + "description": "[basx202] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000004C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65E+8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+9\"}}" + }, + { + "description": "[basx044] strings without E cannot generate E in result", + "canonical_bson": "18000000136400FC040000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"012.76\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.76\"}}" + }, + { + "description": "[basx042] strings without E cannot generate E in result", + "canonical_bson": "18000000136400FC040000000000000000000000003C3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.76\"}}" + }, + { + "description": "[basx046] strings without E cannot generate E in result", + "canonical_bson": "180000001364001100000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"17.\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"17\"}}" + }, + { + "description": "[basx049] strings without E cannot generate E in result", + "canonical_bson": "180000001364002C00000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0044\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"44\"}}" + }, + { + "description": "[basx048] strings without E cannot generate E in result", + "canonical_bson": "180000001364002C00000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"044\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"44\"}}" + }, + { + "description": "[basx158] Numbers with E", + "canonical_bson": "180000001364002C00000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"44E+9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"4.4E+10\"}}" + }, + { + "description": "[basx068] examples", + "canonical_bson": "180000001364003200000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"50E-7\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000050\"}}" + }, + { + "description": "[basx169] Numbers with E", + "canonical_bson": "180000001364006400000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"100e+009\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+11\"}}" + }, + { + "description": "[basx167] Numbers with E", + "canonical_bson": "180000001364006400000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"100e+09\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+11\"}}" + }, + { + "description": "[basx168] Numbers with E", + "canonical_bson": "180000001364006400000000000000000000000000F43000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"100E+90\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+92\"}}" + }, + { + "description": "[basx166] Numbers with E", + "canonical_bson": "180000001364006400000000000000000000000000523000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"100e+9\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+11\"}}" + }, + { + "description": "[basx210] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003E3000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" + }, + { + "description": "[basx217] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" + }, + { + "description": "[basx216] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" + }, + { + "description": "[basx218] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" + }, + { + "description": "[basx211] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000163000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-20\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-18\"}}" + }, + { + "description": "[basx223] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000663000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+20\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+22\"}}" + }, + { + "description": "[basx215] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" + }, + { + "description": "[basx219] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000423000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+4\"}}" + }, + { + "description": "[basx214] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" + }, + { + "description": "[basx220] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000443000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+5\"}}" + }, + { + "description": "[basx213] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.01265\"}}" + }, + { + "description": "[basx221] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000463000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+6\"}}" + }, + { + "description": "[basx212] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000002E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E-8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000001265\"}}" + }, + { + "description": "[basx222] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000004E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5E+8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+10\"}}" + }, + { + "description": "[basx006] conform to rules and exponent will be in permitted range).", + "canonical_bson": "18000000136400E803000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1000\"}}" + }, + { + "description": "[basx230] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" + }, + { + "description": "[basx237] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000403000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265\"}}" + }, + { + "description": "[basx236] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"126.5\"}}" + }, + { + "description": "[basx238] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000423000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+1\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+4\"}}" + }, + { + "description": "[basx231] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000183000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-20\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E-17\"}}" + }, + { + "description": "[basx243] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000683000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+20\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+23\"}}" + }, + { + "description": "[basx235] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.65\"}}" + }, + { + "description": "[basx239] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000443000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+2\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+5\"}}" + }, + { + "description": "[basx234] Numbers with E", + "canonical_bson": "18000000136400F1040000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265\"}}" + }, + { + "description": "[basx240] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000463000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+3\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+6\"}}" + }, + { + "description": "[basx233] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000383000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1265\"}}" + }, + { + "description": "[basx241] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000483000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+4\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+7\"}}" + }, + { + "description": "[basx232] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000303000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E-8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00001265\"}}" + }, + { + "description": "[basx242] Numbers with E", + "canonical_bson": "18000000136400F104000000000000000000000000503000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1265E+8\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.265E+11\"}}" + }, + { + "description": "[basx060] strings without E cannot generate E in result", + "canonical_bson": "18000000136400185C0ACE00000000000000000000383000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.5432\"}}" + }, + { + "description": "[basx059] strings without E cannot generate E in result", + "canonical_bson": "18000000136400F198670C08000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0345678.54321\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.54321\"}}" + }, + { + "description": "[basx058] strings without E cannot generate E in result", + "canonical_bson": "180000001364006AF90B7C50000000000000000000343000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"345678.543210\"}}" + }, + { + "description": "[basx057] strings without E cannot generate E in result", + "canonical_bson": "180000001364006A19562522020000000000000000343000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"2345678.543210\"}}" + }, + { + "description": "[basx056] strings without E cannot generate E in result", + "canonical_bson": "180000001364006AB9C8733A0B0000000000000000343000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12345678.543210\"}}" + }, + { + "description": "[basx031] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640040AF0D8648700000000000000000343000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123456789.000000\"}}" + }, + { + "description": "[basx030] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640080910F8648700000000000000000343000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123456789.123456\"}}" + }, + { + "description": "[basx032] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640080910F8648700000000000000000403000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"123456789123456\"}}" + } + ] +} diff --git a/bson/src/test/resources/bson/decimal128-4.json b/bson/src/test/resources/bson/decimal128-4.json new file mode 100644 index 00000000000..0957019351f --- /dev/null +++ b/bson/src/test/resources/bson/decimal128-4.json @@ -0,0 +1,165 @@ +{ + "description": "Decimal128", + "bson_type": "0x13", + "test_key": "d", + "valid": [ + { + "description": "[basx023] conform to rules and exponent will be in permitted range).", + "canonical_bson": "1800000013640001000000000000000000000000003EB000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.1\"}}" + }, + + { + "description": "[basx045] strings without E cannot generate E in result", + "canonical_bson": "1800000013640003000000000000000000000000003A3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+0.003\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.003\"}}" + }, + { + "description": "[basx610] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \".0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0\"}}" + }, + { + "description": "[basx612] Zeros", + "canonical_bson": "1800000013640000000000000000000000000000003EB000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-.0\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.0\"}}" + }, + { + "description": "[basx043] strings without E cannot generate E in result", + "canonical_bson": "18000000136400FC040000000000000000000000003C3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"+12.76\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"12.76\"}}" + }, + { + "description": "[basx055] strings without E cannot generate E in result", + "canonical_bson": "180000001364000500000000000000000000000000303000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000005\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"5E-8\"}}" + }, + { + "description": "[basx054] strings without E cannot generate E in result", + "canonical_bson": "180000001364000500000000000000000000000000323000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0000005\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"5E-7\"}}" + }, + { + "description": "[basx052] strings without E cannot generate E in result", + "canonical_bson": "180000001364000500000000000000000000000000343000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000005\"}}" + }, + { + "description": "[basx051] strings without E cannot generate E in result", + "canonical_bson": "180000001364000500000000000000000000000000363000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"00.00005\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00005\"}}" + }, + { + "description": "[basx050] strings without E cannot generate E in result", + "canonical_bson": "180000001364000500000000000000000000000000383000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.0005\"}}" + }, + { + "description": "[basx047] strings without E cannot generate E in result", + "canonical_bson": "1800000013640005000000000000000000000000003E3000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \".5\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.5\"}}" + }, + { + "description": "[dqbsr431] check rounding modes heeded (Rounded)", + "canonical_bson": "1800000013640099761CC7B548F377DC80A131C836FE2F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.1111111111111111111111111111123450\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.111111111111111111111111111112345\"}}" + }, + { + "description": "OK2", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FC2F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \".100000000000000000000000000000000000000000000000000000000000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.1000000000000000000000000000000000\"}}" + } + ], + "parseErrors": [ + { + "description": "[basx564] Near-specials (Conversion_syntax)", + "string": "Infi" + }, + { + "description": "[basx565] Near-specials (Conversion_syntax)", + "string": "Infin" + }, + { + "description": "[basx566] Near-specials (Conversion_syntax)", + "string": "Infini" + }, + { + "description": "[basx567] Near-specials (Conversion_syntax)", + "string": "Infinit" + }, + { + "description": "[basx568] Near-specials (Conversion_syntax)", + "string": "-Infinit" + }, + { + "description": "[basx590] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": ".Infinity" + }, + { + "description": "[basx562] Near-specials (Conversion_syntax)", + "string": "NaNq" + }, + { + "description": "[basx563] Near-specials (Conversion_syntax)", + "string": "NaNs" + }, + { + "description": "[dqbas939] overflow results at different rounding modes (Overflow & Inexact & Rounded)", + "string": "-7e10000" + }, + { + "description": "[dqbsr534] negatives (Rounded & Inexact)", + "string": "-1.11111111111111111111111111111234650" + }, + { + "description": "[dqbsr535] negatives (Rounded & Inexact)", + "string": "-1.11111111111111111111111111111234551" + }, + { + "description": "[dqbsr533] negatives (Rounded & Inexact)", + "string": "-1.11111111111111111111111111111234550" + }, + { + "description": "[dqbsr532] negatives (Rounded & Inexact)", + "string": "-1.11111111111111111111111111111234549" + }, + { + "description": "[dqbsr432] check rounding modes heeded (Rounded & Inexact)", + "string": "1.11111111111111111111111111111234549" + }, + { + "description": "[dqbsr433] check rounding modes heeded (Rounded & Inexact)", + "string": "1.11111111111111111111111111111234550" + }, + { + "description": "[dqbsr435] check rounding modes heeded (Rounded & Inexact)", + "string": "1.11111111111111111111111111111234551" + }, + { + "description": "[dqbsr434] check rounding modes heeded (Rounded & Inexact)", + "string": "1.11111111111111111111111111111234650" + }, + { + "description": "[dqbas938] overflow results at different rounding modes (Overflow & Inexact & Rounded)", + "string": "7e10000" + }, + { + "description": "Inexact rounding#1", + "string": "100000000000000000000000000000000000000000000000000000000001" + }, + { + "description": "Inexact rounding#2", + "string": "1E-6177" + } + ] +} diff --git a/bson/src/test/resources/bson/decimal128-5.json b/bson/src/test/resources/bson/decimal128-5.json new file mode 100644 index 00000000000..e976eae4075 --- /dev/null +++ b/bson/src/test/resources/bson/decimal128-5.json @@ -0,0 +1,402 @@ +{ + "description": "Decimal128", + "bson_type": "0x13", + "test_key": "d", + "valid": [ + { + "description": "[decq035] fold-downs (more below) (Clamped)", + "canonical_bson": "18000000136400000000807F1BCF85B27059C8A43CFE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.23E+6144\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.230000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq037] fold-downs (more below) (Clamped)", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6144\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq077] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04000000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.100000000000000000000000000000000E-6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000000E-6144\"}}" + }, + { + "description": "[decq078] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04000000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000000E-6144\"}}" + }, + { + "description": "[decq079] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000A00000000000000000000000000000000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000000000000000000000000000010E-6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-6175\"}}" + }, + { + "description": "[decq080] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000A00000000000000000000000000000000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E-6175\"}}" + }, + { + "description": "[decq081] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000020000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.00000000000000000000000000000001E-6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6175\"}}" + }, + { + "description": "[decq082] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000020000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6175\"}}" + }, + { + "description": "[decq083] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000000000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0.000000000000000000000000000000001E-6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" + }, + { + "description": "[decq084] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000000000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" + }, + { + "description": "[decq090] underflows cannot be tested for simple copies, check edge cases (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000000000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1e-6176\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E-6176\"}}" + }, + { + "description": "[decq100] underflows cannot be tested for simple copies, check edge cases (Subnormal)", + "canonical_bson": "18000000136400FFFFFFFF095BC138938D44C64D31000000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"999999999999999999999999999999999e-6176\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"9.99999999999999999999999999999999E-6144\"}}" + }, + { + "description": "[decq130] fold-downs (more below) (Clamped)", + "canonical_bson": "18000000136400000000807F1BCF85B27059C8A43CFEDF00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.23E+6144\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.230000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq132] fold-downs (more below) (Clamped)", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FEDF00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E+6144\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.000000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq177] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04008000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.100000000000000000000000000000000E-6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00000000000000000000000000000000E-6144\"}}" + }, + { + "description": "[decq178] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04008000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.00000000000000000000000000000000E-6144\"}}" + }, + { + "description": "[decq179] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000A00000000000000000000000000008000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000000000000000000000000000010E-6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.0E-6175\"}}" + }, + { + "description": "[decq180] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000A00000000000000000000000000008000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1.0E-6175\"}}" + }, + { + "description": "[decq181] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000028000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.00000000000000000000000000000001E-6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6175\"}}" + }, + { + "description": "[decq182] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000028000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6175\"}}" + }, + { + "description": "[decq183] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000008000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0.000000000000000000000000000000001E-6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}" + }, + { + "description": "[decq184] Nmin and below (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000008000", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}" + }, + { + "description": "[decq190] underflow edge cases (Subnormal)", + "canonical_bson": "180000001364000100000000000000000000000000008000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1e-6176\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-1E-6176\"}}" + }, + { + "description": "[decq200] underflow edge cases (Subnormal)", + "canonical_bson": "18000000136400FFFFFFFF095BC138938D44C64D31008000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-999999999999999999999999999999999e-6176\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-9.99999999999999999999999999999999E-6144\"}}" + }, + { + "description": "[decq400] zeros (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000000000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-8000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" + }, + { + "description": "[decq401] zeros (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000000000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6177\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E-6176\"}}" + }, + { + "description": "[decq414] clamped zeros... (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6112\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" + }, + { + "description": "[decq416] clamped zeros... (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6144\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" + }, + { + "description": "[decq418] clamped zeros... (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+8000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"0E+6111\"}}" + }, + { + "description": "[decq420] negative zeros (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000008000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-8000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" + }, + { + "description": "[decq421] negative zeros (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000008000", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6177\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E-6176\"}}" + }, + { + "description": "[decq434] clamped zeros... (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000FEDF00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6112\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" + }, + { + "description": "[decq436] clamped zeros... (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000FEDF00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6144\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" + }, + { + "description": "[decq438] clamped zeros... (Clamped)", + "canonical_bson": "180000001364000000000000000000000000000000FEDF00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+8000\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"-0E+6111\"}}" + }, + { + "description": "[decq601] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000000000A5BC138938D44C64D31FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6144\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000000E+6144\"}}" + }, + { + "description": "[decq603] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364000000000081EFAC855B416D2DEE04FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6143\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000000E+6143\"}}" + }, + { + "description": "[decq605] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000000080264B91C02220BE377E00FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6142\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000000000E+6142\"}}" + }, + { + "description": "[decq607] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000000040EAED7446D09C2C9F0C00FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6141\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000000E+6141\"}}" + }, + { + "description": "[decq609] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000000A0CA17726DAE0F1E430100FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6140\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000000E+6140\"}}" + }, + { + "description": "[decq611] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000000106102253E5ECE4F200000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6139\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000000E+6139\"}}" + }, + { + "description": "[decq613] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000000E83C80D09F3C2E3B030000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6138\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000000E+6138\"}}" + }, + { + "description": "[decq615] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000000E4D20CC8DCD2B752000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6137\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000000E+6137\"}}" + }, + { + "description": "[decq617] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364000000004A48011416954508000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6136\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000000E+6136\"}}" + }, + { + "description": "[decq619] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000000A1EDCCCE1BC2D300000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6135\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000000E+6135\"}}" + }, + { + "description": "[decq621] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000080F64AE1C7022D1500000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6134\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000000E+6134\"}}" + }, + { + "description": "[decq623] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000040B2BAC9E0191E0200000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6133\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000000E+6133\"}}" + }, + { + "description": "[decq625] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364000000A0DEC5ADC935360000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6132\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000000E+6132\"}}" + }, + { + "description": "[decq627] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000010632D5EC76B050000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6131\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000000E+6131\"}}" + }, + { + "description": "[decq629] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364000000E8890423C78A000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6130\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000000E+6130\"}}" + }, + { + "description": "[decq631] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400000064A7B3B6E00D000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6129\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000000E+6129\"}}" + }, + { + "description": "[decq633] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000008A5D78456301000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6128\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000000E+6128\"}}" + }, + { + "description": "[decq635] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364000000C16FF2862300000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6127\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000000E+6127\"}}" + }, + { + "description": "[decq637] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364000080C6A47E8D0300000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6126\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000000E+6126\"}}" + }, + { + "description": "[decq639] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000407A10F35A0000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6125\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000000E+6125\"}}" + }, + { + "description": "[decq641] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000A0724E18090000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6124\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000000E+6124\"}}" + }, + { + "description": "[decq643] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364000010A5D4E8000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6123\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000000E+6123\"}}" + }, + { + "description": "[decq645] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000E8764817000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6122\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000000E+6122\"}}" + }, + { + "description": "[decq647] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000E40B5402000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6121\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000000E+6121\"}}" + }, + { + "description": "[decq649] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000CA9A3B00000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6120\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000000E+6120\"}}" + }, + { + "description": "[decq651] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640000E1F50500000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6119\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000000E+6119\"}}" + }, + { + "description": "[decq653] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364008096980000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6118\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000000E+6118\"}}" + }, + { + "description": "[decq655] fold-down full sequence (Clamped)", + "canonical_bson": "1800000013640040420F0000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6117\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000000E+6117\"}}" + }, + { + "description": "[decq657] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400A086010000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6116\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00000E+6116\"}}" + }, + { + "description": "[decq659] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364001027000000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6115\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0000E+6115\"}}" + }, + { + "description": "[decq661] fold-down full sequence (Clamped)", + "canonical_bson": "18000000136400E803000000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6114\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.000E+6114\"}}" + }, + { + "description": "[decq663] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364006400000000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6113\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.00E+6113\"}}" + }, + { + "description": "[decq665] fold-down full sequence (Clamped)", + "canonical_bson": "180000001364000A00000000000000000000000000FE5F00", + "degenerate_extjson": "{\"d\" : {\"$numberDecimal\" : \"1E+6112\"}}", + "canonical_extjson": "{\"d\" : {\"$numberDecimal\" : \"1.0E+6112\"}}" + } + ] +} + diff --git a/bson/src/test/resources/bson/decimal128-6.json b/bson/src/test/resources/bson/decimal128-6.json new file mode 100644 index 00000000000..eba6764e853 --- /dev/null +++ b/bson/src/test/resources/bson/decimal128-6.json @@ -0,0 +1,131 @@ +{ + "description": "Decimal128", + "bson_type": "0x13", + "test_key": "d", + "parseErrors": [ + { + "description": "Incomplete Exponent", + "string": "1e" + }, + { + "description": "Exponent at the beginning", + "string": "E01" + }, + { + "description": "Just a decimal place", + "string": "." + }, + { + "description": "2 decimal places", + "string": "..3" + }, + { + "description": "2 decimal places", + "string": ".13.3" + }, + { + "description": "2 decimal places", + "string": "1..3" + }, + { + "description": "2 decimal places", + "string": "1.3.4" + }, + { + "description": "2 decimal places", + "string": "1.34." + }, + { + "description": "Decimal with no digits", + "string": ".e" + }, + { + "description": "2 signs", + "string": "+-32.4" + }, + { + "description": "2 signs", + "string": "-+32.4" + }, + { + "description": "2 negative signs", + "string": "--32.4" + }, + { + "description": "2 negative signs", + "string": "-32.-4" + }, + { + "description": "End in negative sign", + "string": "32.0-" + }, + { + "description": "2 negative signs", + "string": "32.4E--21" + }, + { + "description": "2 negative signs", + "string": "32.4E-2-1" + }, + { + "description": "2 signs", + "string": "32.4E+-21" + }, + { + "description": "Empty string", + "string": "" + }, + { + "description": "leading white space positive number", + "string": " 1" + }, + { + "description": "leading white space negative number", + "string": " -1" + }, + { + "description": "trailing white space", + "string": "1 " + }, + { + "description": "Invalid", + "string": "E" + }, + { + "description": "Invalid", + "string": "invalid" + }, + { + "description": "Invalid", + "string": "i" + }, + { + "description": "Invalid", + "string": "in" + }, + { + "description": "Invalid", + "string": "-in" + }, + { + "description": "Invalid", + "string": "Na" + }, + { + "description": "Invalid", + "string": "-Na" + }, + { + "description": "Invalid", + "string": "1.23abc" + }, + { + "description": "Invalid", + "string": "1.23abcE+02" + }, + { + "description": "Invalid", + "string": "1.23E+0aabs2" + } + ] +} diff --git a/bson/src/test/resources/bson/decimal128-7.json b/bson/src/test/resources/bson/decimal128-7.json new file mode 100644 index 00000000000..0b78f1237b8 --- /dev/null +++ b/bson/src/test/resources/bson/decimal128-7.json @@ -0,0 +1,327 @@ +{ + "description": "Decimal128", + "bson_type": "0x13", + "test_key": "d", + "parseErrors": [ + { + "description": "[basx572] Near-specials (Conversion_syntax)", + "string": "-9Inf" + }, + { + "description": "[basx516] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "-1-" + }, + { + "description": "[basx533] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "0000.." + }, + { + "description": "[basx534] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": ".0000." + }, + { + "description": "[basx535] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "00..00" + }, + { + "description": "[basx569] Near-specials (Conversion_syntax)", + "string": "0Inf" + }, + { + "description": "[basx571] Near-specials (Conversion_syntax)", + "string": "-0Inf" + }, + { + "description": "[basx575] Near-specials (Conversion_syntax)", + "string": "0sNaN" + }, + { + "description": "[basx503] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "++1" + }, + { + "description": "[basx504] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "--1" + }, + { + "description": "[basx505] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "-+1" + }, + { + "description": "[basx506] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "+-1" + }, + { + "description": "[basx510] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": " +1" + }, + { + "description": "[basx513] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": " + 1" + }, + { + "description": "[basx514] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": " - 1" + }, + { + "description": "[basx501] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "." + }, + { + "description": "[basx502] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": ".." + }, + { + "description": "[basx519] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "" + }, + { + "description": "[basx525] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "e100" + }, + { + "description": "[basx549] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "e+1" + }, + { + "description": "[basx577] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": ".e+1" + }, + { + "description": "[basx578] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "+.e+1" + }, + { + "description": "[basx581] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "E+1" + }, + { + "description": "[basx582] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": ".E+1" + }, + { + "description": "[basx583] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "+.E+1" + }, + { + "description": "[basx579] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "-.e+" + }, + { + "description": "[basx580] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "-.e" + }, + { + "description": "[basx584] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "-.E+" + }, + { + "description": "[basx585] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "-.E" + }, + { + "description": "[basx589] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "+.Inf" + }, + { + "description": "[basx586] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": ".NaN" + }, + { + "description": "[basx587] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "-.NaN" + }, + { + "description": "[basx545] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "ONE" + }, + { + "description": "[basx561] Near-specials (Conversion_syntax)", + "string": "qNaN" + }, + { + "description": "[basx573] Near-specials (Conversion_syntax)", + "string": "-sNa" + }, + { + "description": "[basx588] some baddies with dots and Es and dots and specials (Conversion_syntax)", + "string": "+.sNaN" + }, + { + "description": "[basx544] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "ten" + }, + { + "description": "[basx527] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "u0b65" + }, + { + "description": "[basx526] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "u0e5a" + }, + { + "description": "[basx515] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "x" + }, + { + "description": "[basx574] Near-specials (Conversion_syntax)", + "string": "xNaN" + }, + { + "description": "[basx530] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": ".123.5" + }, + { + "description": "[basx500] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1..2" + }, + { + "description": "[basx542] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1e1.0" + }, + { + "description": "[basx553] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E+1.2.3" + }, + { + "description": "[basx543] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1e123e" + }, + { + "description": "[basx552] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E+1.2" + }, + { + "description": "[basx546] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1e.1" + }, + { + "description": "[basx547] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1e1." + }, + { + "description": "[basx554] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E++1" + }, + { + "description": "[basx555] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E--1" + }, + { + "description": "[basx556] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E+-1" + }, + { + "description": "[basx557] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E-+1" + }, + { + "description": "[basx558] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E'1" + }, + { + "description": "[basx559] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E\"1" + }, + { + "description": "[basx520] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1e-" + }, + { + "description": "[basx560] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1E" + }, + { + "description": "[basx548] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1ee" + }, + { + "description": "[basx551] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1.2.1" + }, + { + "description": "[basx550] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1.23.4" + }, + { + "description": "[basx529] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "1.34.5" + }, + { + "description": "[basx531] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "01.35." + }, + { + "description": "[basx532] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "01.35-" + }, + { + "description": "[basx518] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "3+" + }, + { + "description": "[basx521] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "7e99999a" + }, + { + "description": "[basx570] Near-specials (Conversion_syntax)", + "string": "9Inf" + }, + { + "description": "[basx512] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "12 " + }, + { + "description": "[basx517] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "12-" + }, + { + "description": "[basx507] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "12e" + }, + { + "description": "[basx508] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "12e++" + }, + { + "description": "[basx509] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "12f4" + }, + { + "description": "[basx536] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "111e*123" + }, + { + "description": "[basx537] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "111e123-" + }, + { + "description": "[basx540] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "111e1*23" + }, + { + "description": "[basx538] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "111e+12+" + }, + { + "description": "[basx539] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "111e1-3-" + }, + { + "description": "[basx541] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "111E1e+3" + }, + { + "description": "[basx528] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "123,65" + }, + { + "description": "[basx523] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "7e12356789012x" + }, + { + "description": "[basx522] The 'baddies' tests from DiagBigDecimal, plus some new ones (Conversion_syntax)", + "string": "7e123567890x" + } + ] +} diff --git a/bson/src/test/resources/bson/document.json b/bson/src/test/resources/bson/document.json new file mode 100644 index 00000000000..3ec9187044f --- /dev/null +++ b/bson/src/test/resources/bson/document.json @@ -0,0 +1,36 @@ +{ + "description": "Document type (sub-documents)", + "bson_type": "0x03", + "test_key": "x", + "valid": [ + { + "description": "Empty subdoc", + "canonical_bson": "0D000000037800050000000000", + "canonical_extjson": "{\"x\" : {}}" + }, + { + "description": "Empty-string key subdoc", + "canonical_bson": "150000000378000D00000002000200000062000000", + "canonical_extjson": "{\"x\" : {\"\" : \"b\"}}" + }, + { + "description": "Single-character key subdoc", + "canonical_bson": "160000000378000E0000000261000200000062000000", + "canonical_extjson": "{\"x\" : {\"a\" : \"b\"}}" + } + ], + "decodeErrors": [ + { + "description": "Subdocument length too long: eats outer terminator", + "bson": "1800000003666F6F000F0000001062617200FFFFFF7F0000" + }, + { + "description": "Subdocument length too short: leaks terminator", + "bson": "1500000003666F6F000A0000000862617200010000" + }, + { + "description": "Invalid subdocument: bad string length in field", + "bson": "1C00000003666F6F001200000002626172000500000062617A000000" + } + ] +} diff --git a/bson/src/test/resources/bson/double.json b/bson/src/test/resources/bson/double.json new file mode 100644 index 00000000000..a483f696761 --- /dev/null +++ b/bson/src/test/resources/bson/double.json @@ -0,0 +1,87 @@ +{ + "description": "Double type", + "bson_type": "0x01", + "test_key": "d", + "valid": [ + { + "description": "+1.0", + "canonical_bson": "10000000016400000000000000F03F00", + "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"1.0\"}}", + "relaxed_extjson": "{\"d\" : 1.0}" + }, + { + "description": "-1.0", + "canonical_bson": "10000000016400000000000000F0BF00", + "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"-1.0\"}}", + "relaxed_extjson": "{\"d\" : -1.0}" + }, + { + "description": "+1.0001220703125", + "canonical_bson": "10000000016400000000008000F03F00", + "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"1.0001220703125\"}}", + "relaxed_extjson": "{\"d\" : 1.0001220703125}" + }, + { + "description": "-1.0001220703125", + "canonical_bson": "10000000016400000000008000F0BF00", + "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"-1.0001220703125\"}}", + "relaxed_extjson": "{\"d\" : -1.0001220703125}" + }, + { + "description": "1.23456789012345677E18", + "canonical_bson": "1000000001640081E97DF41022B14300", + "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"1.23456789012345677E18\"}}", + "relaxed_extjson": "{\"d\" : 1.23456789012345677E18}" + }, + { + "description": "-1.23456789012345677E18", + "canonical_bson": "1000000001640081E97DF41022B1C300", + "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"-1.23456789012345677E18\"}}", + "relaxed_extjson": "{\"d\" : -1.23456789012345677E18}" + }, + { + "description": "0.0", + "canonical_bson": "10000000016400000000000000000000", + "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"0.0\"}}", + "relaxed_extjson": "{\"d\" : 0.0}" + }, + { + "description": "-0.0", + "canonical_bson": "10000000016400000000000000008000", + "canonical_extjson": "{\"d\" : {\"$numberDouble\": \"-0.0\"}}", + "relaxed_extjson": "{\"d\" : -0.0}" + }, + { + "description": "NaN", + "canonical_bson": "10000000016400000000000000F87F00", + "canonical_extjson": "{\"d\": {\"$numberDouble\": \"NaN\"}}", + "relaxed_extjson": "{\"d\": {\"$numberDouble\": \"NaN\"}}", + "lossy": true + }, + { + "description": "NaN with payload", + "canonical_bson": "10000000016400120000000000F87F00", + "canonical_extjson": "{\"d\": {\"$numberDouble\": \"NaN\"}}", + "relaxed_extjson": "{\"d\": {\"$numberDouble\": \"NaN\"}}", + "lossy": true + }, + { + "description": "Inf", + "canonical_bson": "10000000016400000000000000F07F00", + "canonical_extjson": "{\"d\": {\"$numberDouble\": \"Infinity\"}}", + "relaxed_extjson": "{\"d\": {\"$numberDouble\": \"Infinity\"}}" + }, + { + "description": "-Inf", + "canonical_bson": "10000000016400000000000000F0FF00", + "canonical_extjson": "{\"d\": {\"$numberDouble\": \"-Infinity\"}}", + "relaxed_extjson": "{\"d\": {\"$numberDouble\": \"-Infinity\"}}" + } + ], + "decodeErrors": [ + { + "description": "double truncated", + "bson": "0B0000000164000000F03F00" + } + ] +} diff --git a/bson/src/test/resources/bson/int32.json b/bson/src/test/resources/bson/int32.json index 78f72b77f17..1353fc3df8b 100644 --- a/bson/src/test/resources/bson/int32.json +++ b/bson/src/test/resources/bson/int32.json @@ -1,35 +1,43 @@ { - "description": "Int32 type", - "documents": [ + "description": "Int32 type", + "bson_type": "0x10", + "test_key": "i", + "valid": [ { - "decoded": { - "i": -2147483648 - }, - "encoded": "0C0000001069000000008000" - }, + "description": "MinValue", + "canonical_bson": "0C0000001069000000008000", + "canonical_extjson": "{\"i\" : {\"$numberInt\": \"-2147483648\"}}", + "relaxed_extjson": "{\"i\" : -2147483648}" + }, { - "decoded": { - "i": 2147483647 - }, - "encoded": "0C000000106900FFFFFF7F00" - }, + "description": "MaxValue", + "canonical_bson": "0C000000106900FFFFFF7F00", + "canonical_extjson": "{\"i\" : {\"$numberInt\": \"2147483647\"}}", + "relaxed_extjson": "{\"i\" : 2147483647}" + }, { - "decoded": { - "i": -1 - }, - "encoded": "0C000000106900FFFFFFFF00" - }, + "description": "-1", + "canonical_bson": "0C000000106900FFFFFFFF00", + "canonical_extjson": "{\"i\" : {\"$numberInt\": \"-1\"}}", + "relaxed_extjson": "{\"i\" : -1}" + }, { - "decoded": { - "i": 0 - }, - "encoded": "0C0000001069000000000000" - }, + "description": "0", + "canonical_bson": "0C0000001069000000000000", + "canonical_extjson": "{\"i\" : {\"$numberInt\": \"0\"}}", + "relaxed_extjson": "{\"i\" : 0}" + }, { - "decoded": { - "i": 1 - }, - "encoded": "0C0000001069000100000000" + "description": "1", + "canonical_bson": "0C0000001069000100000000", + "canonical_extjson": "{\"i\" : {\"$numberInt\": \"1\"}}", + "relaxed_extjson": "{\"i\" : 1}" + } + ], + "decodeErrors": [ + { + "description": "Bad int32 field length", + "bson": "090000001061000500" } ] } diff --git a/bson/src/test/resources/bson/int64.json b/bson/src/test/resources/bson/int64.json new file mode 100644 index 00000000000..91f4abff950 --- /dev/null +++ b/bson/src/test/resources/bson/int64.json @@ -0,0 +1,43 @@ +{ + "description": "Int64 type", + "bson_type": "0x12", + "test_key": "a", + "valid": [ + { + "description": "MinValue", + "canonical_bson": "10000000126100000000000000008000", + "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"-9223372036854775808\"}}", + "relaxed_extjson": "{\"a\" : -9223372036854775808}" + }, + { + "description": "MaxValue", + "canonical_bson": "10000000126100FFFFFFFFFFFFFF7F00", + "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"9223372036854775807\"}}", + "relaxed_extjson": "{\"a\" : 9223372036854775807}" + }, + { + "description": "-1", + "canonical_bson": "10000000126100FFFFFFFFFFFFFFFF00", + "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"-1\"}}", + "relaxed_extjson": "{\"a\" : -1}" + }, + { + "description": "0", + "canonical_bson": "10000000126100000000000000000000", + "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"0\"}}", + "relaxed_extjson": "{\"a\" : 0}" + }, + { + "description": "1", + "canonical_bson": "10000000126100010000000000000000", + "canonical_extjson": "{\"a\" : {\"$numberLong\" : \"1\"}}", + "relaxed_extjson": "{\"a\" : 1}" + } + ], + "decodeErrors": [ + { + "description": "int64 field truncated", + "bson": "0C0000001261001234567800" + } + ] +} diff --git a/bson/src/test/resources/bson/maxkey.json b/bson/src/test/resources/bson/maxkey.json new file mode 100644 index 00000000000..67cad6db57b --- /dev/null +++ b/bson/src/test/resources/bson/maxkey.json @@ -0,0 +1,12 @@ +{ + "description": "Maxkey type", + "bson_type": "0x7F", + "test_key": "a", + "valid": [ + { + "description": "Maxkey", + "canonical_bson": "080000007F610000", + "canonical_extjson": "{\"a\" : {\"$maxKey\" : 1}}" + } + ] +} diff --git a/bson/src/test/resources/bson/minkey.json b/bson/src/test/resources/bson/minkey.json new file mode 100644 index 00000000000..8adee4509a5 --- /dev/null +++ b/bson/src/test/resources/bson/minkey.json @@ -0,0 +1,12 @@ +{ + "description": "Minkey type", + "bson_type": "0xFF", + "test_key": "a", + "valid": [ + { + "description": "Minkey", + "canonical_bson": "08000000FF610000", + "canonical_extjson": "{\"a\" : {\"$minKey\" : 1}}" + } + ] +} diff --git a/bson/src/test/resources/bson/multi-type-deprecated.json b/bson/src/test/resources/bson/multi-type-deprecated.json new file mode 100644 index 00000000000..5aac1bd2e7d --- /dev/null +++ b/bson/src/test/resources/bson/multi-type-deprecated.json @@ -0,0 +1,15 @@ +{ + "description": "Multiple types within the same document", + "bson_type": "0x00", + "deprecated": true, + "valid": [ + { + "description": "All BSON types", + "canonical_bson": "3B020000075F69640057E193D7A9CC81B4027498B50E53796D626F6C000700000073796D626F6C0002537472696E670007000000737472696E670010496E743332002A00000012496E743634002A0000000000000001446F75626C6500000000000000F0BF0542696E617279001000000003A34C38F7C3ABEDC8A37814A992AB8DB60542696E61727955736572446566696E656400050000008001020304050D436F6465000E00000066756E6374696F6E2829207B7D000F436F64655769746853636F7065001B0000000E00000066756E6374696F6E2829207B7D00050000000003537562646F63756D656E74001200000002666F6F0004000000626172000004417272617900280000001030000100000010310002000000103200030000001033000400000010340005000000001154696D657374616D7000010000002A0000000B5265676578007061747465726E0000094461746574696D6545706F6368000000000000000000094461746574696D65506F73697469766500FFFFFF7F00000000094461746574696D654E656761746976650000000080FFFFFFFF085472756500010846616C736500000C4442506F696E746572000E00000064622E636F6C6C656374696F6E0057E193D7A9CC81B4027498B1034442526566003D0000000224726566000B000000636F6C6C656374696F6E00072469640057FD71E96E32AB4225B723FB02246462000900000064617461626173650000FF4D696E6B6579007F4D61786B6579000A4E756C6C0006556E646566696E65640000", + "converted_bson": "4b020000075f69640057e193d7a9cc81b4027498b50253796d626f6c000700000073796d626f6c0002537472696e670007000000737472696e670010496e743332002a00000012496e743634002a0000000000000001446f75626c6500000000000000f0bf0542696e617279001000000003a34c38f7c3abedc8a37814a992ab8db60542696e61727955736572446566696e656400050000008001020304050d436f6465000e00000066756e6374696f6e2829207b7d000f436f64655769746853636f7065001b0000000e00000066756e6374696f6e2829207b7d00050000000003537562646f63756d656e74001200000002666f6f0004000000626172000004417272617900280000001030000100000010310002000000103200030000001033000400000010340005000000001154696d657374616d7000010000002a0000000b5265676578007061747465726e0000094461746574696d6545706f6368000000000000000000094461746574696d65506f73697469766500ffffff7f00000000094461746574696d654e656761746976650000000080ffffffff085472756500010846616c73650000034442506f696e746572002e0000000224726566000e00000064622e636f6c6c656374696f6e00072469640057e193d7a9cc81b4027498b100034442526566003d0000000224726566000b000000636f6c6c656374696f6e00072469640057fd71e96e32ab4225b723fb02246462000900000064617461626173650000ff4d696e6b6579007f4d61786b6579000a4e756c6c000a556e646566696e65640000", + "canonical_extjson": "{\"_id\": {\"$oid\": \"57e193d7a9cc81b4027498b5\"}, \"Symbol\": {\"$symbol\": \"symbol\"}, \"String\": \"string\", \"Int32\": {\"$numberInt\": \"42\"}, \"Int64\": {\"$numberLong\": \"42\"}, \"Double\": {\"$numberDouble\": \"-1.0\"}, \"Binary\": { \"$binary\" : {\"base64\": \"o0w498Or7cijeBSpkquNtg==\", \"subType\": \"03\"}}, \"BinaryUserDefined\": { \"$binary\" : {\"base64\": \"AQIDBAU=\", \"subType\": \"80\"}}, \"Code\": {\"$code\": \"function() {}\"}, \"CodeWithScope\": {\"$code\": \"function() {}\", \"$scope\": {}}, \"Subdocument\": {\"foo\": \"bar\"}, \"Array\": [{\"$numberInt\": \"1\"}, {\"$numberInt\": \"2\"}, {\"$numberInt\": \"3\"}, {\"$numberInt\": \"4\"}, {\"$numberInt\": \"5\"}], \"Timestamp\": {\"$timestamp\": {\"t\": 42, \"i\": 1}}, \"Regex\": {\"$regularExpression\": {\"pattern\": \"pattern\", \"options\": \"\"}}, \"DatetimeEpoch\": {\"$date\": {\"$numberLong\": \"0\"}}, \"DatetimePositive\": {\"$date\": {\"$numberLong\": \"2147483647\"}}, \"DatetimeNegative\": {\"$date\": {\"$numberLong\": \"-2147483648\"}}, \"True\": true, \"False\": false, \"DBPointer\": {\"$dbPointer\": {\"$ref\": \"db.collection\", \"$id\": {\"$oid\": \"57e193d7a9cc81b4027498b1\"}}}, \"DBRef\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57fd71e96e32ab4225b723fb\"}, \"$db\": \"database\"}, \"Minkey\": {\"$minKey\": 1}, \"Maxkey\": {\"$maxKey\": 1}, \"Null\": null, \"Undefined\": {\"$undefined\": true}}", + "converted_extjson": "{\"_id\": {\"$oid\": \"57e193d7a9cc81b4027498b5\"}, \"Symbol\": \"symbol\", \"String\": \"string\", \"Int32\": {\"$numberInt\": \"42\"}, \"Int64\": {\"$numberLong\": \"42\"}, \"Double\": {\"$numberDouble\": \"-1.0\"}, \"Binary\": { \"$binary\" : {\"base64\": \"o0w498Or7cijeBSpkquNtg==\", \"subType\": \"03\"}}, \"BinaryUserDefined\": { \"$binary\" : {\"base64\": \"AQIDBAU=\", \"subType\": \"80\"}}, \"Code\": {\"$code\": \"function() {}\"}, \"CodeWithScope\": {\"$code\": \"function() {}\", \"$scope\": {}}, \"Subdocument\": {\"foo\": \"bar\"}, \"Array\": [{\"$numberInt\": \"1\"}, {\"$numberInt\": \"2\"}, {\"$numberInt\": \"3\"}, {\"$numberInt\": \"4\"}, {\"$numberInt\": \"5\"}], \"Timestamp\": {\"$timestamp\": {\"t\": 42, \"i\": 1}}, \"Regex\": {\"$regularExpression\": {\"pattern\": \"pattern\", \"options\": \"\"}}, \"DatetimeEpoch\": {\"$date\": {\"$numberLong\": \"0\"}}, \"DatetimePositive\": {\"$date\": {\"$numberLong\": \"2147483647\"}}, \"DatetimeNegative\": {\"$date\": {\"$numberLong\": \"-2147483648\"}}, \"True\": true, \"False\": false, \"DBPointer\": {\"$ref\": \"db.collection\", \"$id\": {\"$oid\": \"57e193d7a9cc81b4027498b1\"}}, \"DBRef\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57fd71e96e32ab4225b723fb\"}, \"$db\": \"database\"}, \"Minkey\": {\"$minKey\": 1}, \"Maxkey\": {\"$maxKey\": 1}, \"Null\": null, \"Undefined\": null}" + } + ] +} + diff --git a/bson/src/test/resources/bson/multi-type.json b/bson/src/test/resources/bson/multi-type.json new file mode 100644 index 00000000000..1e1d557c9ba --- /dev/null +++ b/bson/src/test/resources/bson/multi-type.json @@ -0,0 +1,11 @@ +{ + "description": "Multiple types within the same document", + "bson_type": "0x00", + "valid": [ + { + "description": "All BSON types", + "canonical_bson": "F4010000075F69640057E193D7A9CC81B4027498B502537472696E670007000000737472696E670010496E743332002A00000012496E743634002A0000000000000001446F75626C6500000000000000F0BF0542696E617279001000000003A34C38F7C3ABEDC8A37814A992AB8DB60542696E61727955736572446566696E656400050000008001020304050D436F6465000E00000066756E6374696F6E2829207B7D000F436F64655769746853636F7065001B0000000E00000066756E6374696F6E2829207B7D00050000000003537562646F63756D656E74001200000002666F6F0004000000626172000004417272617900280000001030000100000010310002000000103200030000001033000400000010340005000000001154696D657374616D7000010000002A0000000B5265676578007061747465726E0000094461746574696D6545706F6368000000000000000000094461746574696D65506F73697469766500FFFFFF7F00000000094461746574696D654E656761746976650000000080FFFFFFFF085472756500010846616C73650000034442526566003D0000000224726566000B000000636F6C6C656374696F6E00072469640057FD71E96E32AB4225B723FB02246462000900000064617461626173650000FF4D696E6B6579007F4D61786B6579000A4E756C6C0000", + "canonical_extjson": "{\"_id\": {\"$oid\": \"57e193d7a9cc81b4027498b5\"}, \"String\": \"string\", \"Int32\": {\"$numberInt\": \"42\"}, \"Int64\": {\"$numberLong\": \"42\"}, \"Double\": {\"$numberDouble\": \"-1.0\"}, \"Binary\": { \"$binary\" : {\"base64\": \"o0w498Or7cijeBSpkquNtg==\", \"subType\": \"03\"}}, \"BinaryUserDefined\": { \"$binary\" : {\"base64\": \"AQIDBAU=\", \"subType\": \"80\"}}, \"Code\": {\"$code\": \"function() {}\"}, \"CodeWithScope\": {\"$code\": \"function() {}\", \"$scope\": {}}, \"Subdocument\": {\"foo\": \"bar\"}, \"Array\": [{\"$numberInt\": \"1\"}, {\"$numberInt\": \"2\"}, {\"$numberInt\": \"3\"}, {\"$numberInt\": \"4\"}, {\"$numberInt\": \"5\"}], \"Timestamp\": {\"$timestamp\": {\"t\": 42, \"i\": 1}}, \"Regex\": {\"$regularExpression\": {\"pattern\": \"pattern\", \"options\": \"\"}}, \"DatetimeEpoch\": {\"$date\": {\"$numberLong\": \"0\"}}, \"DatetimePositive\": {\"$date\": {\"$numberLong\": \"2147483647\"}}, \"DatetimeNegative\": {\"$date\": {\"$numberLong\": \"-2147483648\"}}, \"True\": true, \"False\": false, \"DBRef\": {\"$ref\": \"collection\", \"$id\": {\"$oid\": \"57fd71e96e32ab4225b723fb\"}, \"$db\": \"database\"}, \"Minkey\": {\"$minKey\": 1}, \"Maxkey\": {\"$maxKey\": 1}, \"Null\": null}" + } + ] +} diff --git a/bson/src/test/resources/bson/null.json b/bson/src/test/resources/bson/null.json new file mode 100644 index 00000000000..f9b269473e6 --- /dev/null +++ b/bson/src/test/resources/bson/null.json @@ -0,0 +1,12 @@ +{ + "description": "Null type", + "bson_type": "0x0A", + "test_key": "a", + "valid": [ + { + "description": "Null", + "canonical_bson": "080000000A610000", + "canonical_extjson": "{\"a\" : null}" + } + ] +} diff --git a/bson/src/test/resources/bson/oid.json b/bson/src/test/resources/bson/oid.json new file mode 100644 index 00000000000..14e9caf4b40 --- /dev/null +++ b/bson/src/test/resources/bson/oid.json @@ -0,0 +1,28 @@ +{ + "description": "ObjectId", + "bson_type": "0x07", + "test_key": "a", + "valid": [ + { + "description": "All zeroes", + "canonical_bson": "1400000007610000000000000000000000000000", + "canonical_extjson": "{\"a\" : {\"$oid\" : \"000000000000000000000000\"}}" + }, + { + "description": "All ones", + "canonical_bson": "14000000076100FFFFFFFFFFFFFFFFFFFFFFFF00", + "canonical_extjson": "{\"a\" : {\"$oid\" : \"ffffffffffffffffffffffff\"}}" + }, + { + "description": "Random", + "canonical_bson": "1400000007610056E1FC72E0C917E9C471416100", + "canonical_extjson": "{\"a\" : {\"$oid\" : \"56e1fc72e0c917e9c4714161\"}}" + } + ], + "decodeErrors": [ + { + "description": "OID truncated", + "bson": "1200000007610056E1FC72E0C917E9C471" + } + ] +} diff --git a/bson/src/test/resources/bson/regex.json b/bson/src/test/resources/bson/regex.json new file mode 100644 index 00000000000..c62b019cdf4 --- /dev/null +++ b/bson/src/test/resources/bson/regex.json @@ -0,0 +1,65 @@ +{ + "description": "Regular Expression type", + "bson_type": "0x0B", + "test_key": "a", + "valid": [ + { + "description": "empty regex with no options", + "canonical_bson": "0A0000000B6100000000", + "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"\", \"options\" : \"\"}}}" + }, + { + "description": "regex without options", + "canonical_bson": "0D0000000B6100616263000000", + "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"\"}}}" + }, + { + "description": "regex with options", + "canonical_bson": "0F0000000B610061626300696D0000", + "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"im\"}}}" + }, + { + "description": "regex with options (keys reversed)", + "canonical_bson": "0F0000000B610061626300696D0000", + "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"im\"}}}", + "degenerate_extjson": "{\"a\" : {\"$regularExpression\" : {\"options\" : \"im\", \"pattern\": \"abc\"}}}" + }, + { + "description": "regex with slash", + "canonical_bson": "110000000B610061622F636400696D0000", + "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"ab/cd\", \"options\" : \"im\"}}}" + }, + { + "description": "flags not alphabetized", + "degenerate_bson": "100000000B6100616263006D69780000", + "canonical_bson": "100000000B610061626300696D780000", + "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"imx\"}}}", + "degenerate_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"abc\", \"options\" : \"mix\"}}}" + }, + { + "description" : "Required escapes", + "canonical_bson" : "100000000B610061625C226162000000", + "canonical_extjson": "{\"a\" : {\"$regularExpression\" : { \"pattern\": \"ab\\\\\\\"ab\", \"options\" : \"\"}}}" + }, + { + "description" : "Regular expression as value of $regex query operator", + "canonical_bson" : "180000000B247265676578007061747465726E0069780000", + "canonical_extjson": "{\"$regex\" : {\"$regularExpression\" : { \"pattern\": \"pattern\", \"options\" : \"ix\"}}}" + }, + { + "description" : "Regular expression as value of $regex query operator with $options", + "canonical_bson" : "270000000B247265676578007061747465726E000002246F7074696F6E73000300000069780000", + "canonical_extjson": "{\"$regex\" : {\"$regularExpression\" : { \"pattern\": \"pattern\", \"options\" : \"\"}}, \"$options\" : \"ix\"}" + } + ], + "decodeErrors": [ + { + "description": "embedded null in pattern", + "bson": "0F0000000B610061006300696D0000" + }, + { + "description": "embedded null in flags", + "bson": "100000000B61006162630069006D0000" + } + ] +} diff --git a/bson/src/test/resources/bson/string.json b/bson/src/test/resources/bson/string.json index 9c4b8d3f543..148334d0919 100644 --- a/bson/src/test/resources/bson/string.json +++ b/bson/src/test/resources/bson/string.json @@ -1,29 +1,72 @@ { - "description": "String type", - "documents": [ - { - "decoded": { - "s": "" - }, - "encoded": "0D000000027300010000000000" - }, - { - "decoded": { - "s": "a" - }, - "encoded": "0E00000002730002000000610000" - }, - { - "decoded": { - "s": "This is a string" - }, - "encoded": "1D0000000273001100000054686973206973206120737472696E670000" - }, - { - "decoded": { - "s": "\u03ba\u1f79\u03c3\u03bc\u03b5" - }, - "encoded": "180000000273000C000000CEBAE1BDB9CF83CEBCCEB50000" + "description": "String", + "bson_type": "0x02", + "test_key": "a", + "valid": [ + { + "description": "Empty string", + "canonical_bson": "0D000000026100010000000000", + "canonical_extjson": "{\"a\" : \"\"}" + }, + { + "description": "Single character", + "canonical_bson": "0E00000002610002000000620000", + "canonical_extjson": "{\"a\" : \"b\"}" + }, + { + "description": "Multi-character", + "canonical_bson": "190000000261000D0000006162616261626162616261620000", + "canonical_extjson": "{\"a\" : \"abababababab\"}" + }, + { + "description": "two-byte UTF-8 (\u00e9)", + "canonical_bson": "190000000261000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", + "canonical_extjson": "{\"a\" : \"\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\\u00e9\"}" + }, + { + "description": "three-byte UTF-8 (\u2606)", + "canonical_bson": "190000000261000D000000E29886E29886E29886E298860000", + "canonical_extjson": "{\"a\" : \"\\u2606\\u2606\\u2606\\u2606\"}" + }, + { + "description": "Embedded nulls", + "canonical_bson": "190000000261000D0000006162006261620062616261620000", + "canonical_extjson": "{\"a\" : \"ab\\u0000bab\\u0000babab\"}" + }, + { + "description": "Required escapes", + "canonical_bson" : "320000000261002600000061625C220102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F61620000", + "canonical_extjson" : "{\"a\":\"ab\\\\\\\"\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001fab\"}" + } + ], + "decodeErrors": [ + { + "description": "bad string length: 0 (but no 0x00 either)", + "bson": "0C0000000261000000000000" + }, + { + "description": "bad string length: -1", + "bson": "0C000000026100FFFFFFFF00" + }, + { + "description": "bad string length: eats terminator", + "bson": "10000000026100050000006200620000" + }, + { + "description": "bad string length: longer than rest of document", + "bson": "120000000200FFFFFF00666F6F6261720000" + }, + { + "description": "string is not null-terminated", + "bson": "1000000002610004000000616263FF00" + }, + { + "description": "empty string, but extra null", + "bson": "0E00000002610001000000000000" + }, + { + "description": "invalid UTF-8", + "bson": "0E00000002610002000000E90000" } ] } diff --git a/bson/src/test/resources/bson/symbol.json b/bson/src/test/resources/bson/symbol.json new file mode 100644 index 00000000000..4e46cb95117 --- /dev/null +++ b/bson/src/test/resources/bson/symbol.json @@ -0,0 +1,80 @@ +{ + "description": "Symbol", + "bson_type": "0x0E", + "deprecated": true, + "test_key": "a", + "valid": [ + { + "description": "Empty string", + "canonical_bson": "0D0000000E6100010000000000", + "canonical_extjson": "{\"a\": {\"$symbol\": \"\"}}", + "converted_bson": "0D000000026100010000000000", + "converted_extjson": "{\"a\": \"\"}" + }, + { + "description": "Single character", + "canonical_bson": "0E0000000E610002000000620000", + "canonical_extjson": "{\"a\": {\"$symbol\": \"b\"}}", + "converted_bson": "0E00000002610002000000620000", + "converted_extjson": "{\"a\": \"b\"}" + }, + { + "description": "Multi-character", + "canonical_bson": "190000000E61000D0000006162616261626162616261620000", + "canonical_extjson": "{\"a\": {\"$symbol\": \"abababababab\"}}", + "converted_bson": "190000000261000D0000006162616261626162616261620000", + "converted_extjson": "{\"a\": \"abababababab\"}" + }, + { + "description": "two-byte UTF-8 (\u00e9)", + "canonical_bson": "190000000E61000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", + "canonical_extjson": "{\"a\": {\"$symbol\": \"éééééé\"}}", + "converted_bson": "190000000261000D000000C3A9C3A9C3A9C3A9C3A9C3A90000", + "converted_extjson": "{\"a\": \"éééééé\"}" + }, + { + "description": "three-byte UTF-8 (\u2606)", + "canonical_bson": "190000000E61000D000000E29886E29886E29886E298860000", + "canonical_extjson": "{\"a\": {\"$symbol\": \"☆☆☆☆\"}}", + "converted_bson": "190000000261000D000000E29886E29886E29886E298860000", + "converted_extjson": "{\"a\": \"☆☆☆☆\"}" + }, + { + "description": "Embedded nulls", + "canonical_bson": "190000000E61000D0000006162006261620062616261620000", + "canonical_extjson": "{\"a\": {\"$symbol\": \"ab\\u0000bab\\u0000babab\"}}", + "converted_bson": "190000000261000D0000006162006261620062616261620000", + "converted_extjson": "{\"a\": \"ab\\u0000bab\\u0000babab\"}" + } + ], + "decodeErrors": [ + { + "description": "bad symbol length: 0 (but no 0x00 either)", + "bson": "0C0000000261000000000000" + }, + { + "description": "bad symbol length: -1", + "bson": "0C000000026100FFFFFFFF00" + }, + { + "description": "bad symbol length: eats terminator", + "bson": "10000000026100050000006200620000" + }, + { + "description": "bad symbol length: longer than rest of document", + "bson": "120000000200FFFFFF00666F6F6261720000" + }, + { + "description": "symbol is not null-terminated", + "bson": "1000000002610004000000616263FF00" + }, + { + "description": "empty symbol, but extra null", + "bson": "0E00000002610001000000000000" + }, + { + "description": "invalid UTF-8", + "bson": "0E00000002610002000000E90000" + } + ] +} diff --git a/bson/src/test/resources/bson/timestamp.json b/bson/src/test/resources/bson/timestamp.json new file mode 100644 index 00000000000..c76bc2998eb --- /dev/null +++ b/bson/src/test/resources/bson/timestamp.json @@ -0,0 +1,29 @@ +{ + "description": "Timestamp type", + "bson_type": "0x11", + "test_key": "a", + "valid": [ + { + "description": "Timestamp: (123456789, 42)", + "canonical_bson": "100000001161002A00000015CD5B0700", + "canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 123456789, \"i\" : 42} } }" + }, + { + "description": "Timestamp: (123456789, 42) (keys reversed)", + "canonical_bson": "100000001161002A00000015CD5B0700", + "canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 123456789, \"i\" : 42} } }", + "degenerate_extjson": "{\"a\" : {\"$timestamp\" : {\"i\" : 42, \"t\" : 123456789} } }" + }, + { + "description": "Timestamp with high-order bit set on both seconds and increment", + "canonical_bson": "10000000116100FFFFFFFFFFFFFFFF00", + "canonical_extjson": "{\"a\" : {\"$timestamp\" : {\"t\" : 4294967295, \"i\" : 4294967295} } }" + } + ], + "decodeErrors": [ + { + "description": "Truncated timestamp field", + "bson": "0f0000001161002A00000015CD5B00" + } + ] +} diff --git a/bson/src/test/resources/bson/top.json b/bson/src/test/resources/bson/top.json new file mode 100644 index 00000000000..68b51195ab1 --- /dev/null +++ b/bson/src/test/resources/bson/top.json @@ -0,0 +1,236 @@ +{ + "description": "Top-level document validity", + "bson_type": "0x00", + "valid": [ + { + "description": "Document with keys that start with $", + "canonical_bson": "0F00000010246B6579002A00000000", + "canonical_extjson": "{\"$key\": {\"$numberInt\": \"42\"}}" + } + ], + "decodeErrors": [ + { + "description": "An object size that's too small to even include the object size, but is a well-formed, empty object", + "bson": "0100000000" + }, + { + "description": "An object size that's only enough for the object size, but is a well-formed, empty object", + "bson": "0400000000" + }, + { + "description": "One object, with length shorter than size (missing EOO)", + "bson": "05000000" + }, + { + "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0x01", + "bson": "0500000001" + }, + { + "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0xff", + "bson": "05000000FF" + }, + { + "description": "One object, sized correctly, with a spot for an EOO, but the EOO is 0x70", + "bson": "0500000070" + }, + { + "description": "Byte count is zero (with non-zero input length)", + "bson": "00000000000000000000" + }, + { + "description": "Stated length exceeds byte count, with truncated document", + "bson": "1200000002666F6F0004000000626172" + }, + { + "description": "Stated length less than byte count, with garbage after envelope", + "bson": "1200000002666F6F00040000006261720000DEADBEEF" + }, + { + "description": "Stated length exceeds byte count, with valid envelope", + "bson": "1300000002666F6F00040000006261720000" + }, + { + "description": "Stated length less than byte count, with valid envelope", + "bson": "1100000002666F6F00040000006261720000" + }, + { + "description": "Invalid BSON type low range", + "bson": "07000000000000" + }, + { + "description": "Invalid BSON type high range", + "bson": "07000000800000" + }, + { + "description": "Document truncated mid-key", + "bson": "1200000002666F" + } + ], + "parseErrors": [ + { + "description" : "Bad $regularExpression (extra field)", + "string" : "{\"a\" : \"$regularExpression\": {\"pattern\": \"abc\", \"options\": \"\", \"unrelated\": true}}}" + }, + { + "description" : "Bad $regularExpression (missing options field)", + "string" : "{\"a\" : \"$regularExpression\": {\"pattern\": \"abc\"}}}" + }, + { + "description": "Bad $regularExpression (pattern is number, not string)", + "string": "{\"x\" : {\"$regularExpression\" : { \"pattern\": 42, \"$options\" : \"\"}}}" + }, + { + "description": "Bad $regularExpression (options are number, not string)", + "string": "{\"x\" : {\"$regularExpression\" : { \"pattern\": \"a\", \"$options\" : 0}}}" + }, + { + "description" : "Bad $regularExpression (missing pattern field)", + "string" : "{\"a\" : \"$regularExpression\": {\"options\":\"ix\"}}}" + }, + { + "description": "Bad $oid (number, not string)", + "string": "{\"a\" : {\"$oid\" : 42}}" + }, + { + "description": "Bad $oid (extra field)", + "string": "{\"a\" : {\"$oid\" : \"56e1fc72e0c917e9c4714161\", \"unrelated\": true}}" + }, + { + "description": "Bad $numberInt (number, not string)", + "string": "{\"a\" : {\"$numberInt\" : 42}}" + }, + { + "description": "Bad $numberInt (extra field)", + "string": "{\"a\" : {\"$numberInt\" : \"42\", \"unrelated\": true}}" + }, + { + "description": "Bad $numberLong (number, not string)", + "string": "{\"a\" : {\"$numberLong\" : 42}}" + }, + { + "description": "Bad $numberLong (extra field)", + "string": "{\"a\" : {\"$numberLong\" : \"42\", \"unrelated\": true}}" + }, + { + "description": "Bad $numberDouble (number, not string)", + "string": "{\"a\" : {\"$numberDouble\" : 42}}" + }, + { + "description": "Bad $numberDouble (extra field)", + "string": "{\"a\" : {\"$numberDouble\" : \".1\", \"unrelated\": true}}" + }, + { + "description": "Bad $numberDecimal (number, not string)", + "string": "{\"a\" : {\"$numberDecimal\" : 42}}" + }, + { + "description": "Bad $numberDecimal (extra field)", + "string": "{\"a\" : {\"$numberDecimal\" : \".1\", \"unrelated\": true}}" + }, + { + "description": "Bad $binary (binary is number, not string)", + "string": "{\"x\" : {\"$binary\" : {\"base64\" : 0, \"subType\" : \"00\"}}}" + }, + { + "description": "Bad $binary (type is number, not string)", + "string": "{\"x\" : {\"$binary\" : {\"base64\" : \"\", \"subType\" : 0}}}" + }, + { + "description": "Bad $binary (missing $type)", + "string": "{\"x\" : {\"$binary\" : {\"base64\" : \"//8=\"}}}" + }, + { + "description": "Bad $binary (missing $binary)", + "string": "{\"x\" : {\"$binary\" : {\"subType\" : \"00\"}}}" + }, + { + "description": "Bad $binary (extra field)", + "string": "{\"x\" : {\"$binary\" : {\"base64\" : \"//8=\", \"subType\" : 0, \"unrelated\": true}}}" + }, + { + "description": "Bad $code (type is number, not string)", + "string": "{\"a\" : {\"$code\" : 42}}" + }, + { + "description": "Bad $code (extra field)", + "string": "{\"a\" : {\"$code\" : \"\", \"unrelated\": true}}" + }, + { + "description": "Bad $code with $scope (scope is number, not doc)", + "string": "{\"x\" : {\"$code\" : \"\", \"$scope\" : 42}}" + }, + { + "description": "Bad $timestamp (type is number, not doc)", + "string": "{\"a\" : {\"$timestamp\" : 42} }" + }, + { + "description": "Bad $timestamp ('t' type is string, not number)", + "string": "{\"a\" : {\"$timestamp\" : {\"t\" : \"123456789\", \"i\" : 42} } }" + }, + { + "description": "Bad $timestamp ('i' type is string, not number)", + "string": "{\"a\" : {\"$timestamp\" : {\"t\" : 123456789, \"i\" : \"42\"} } }" + }, + { + "description": "Bad $timestamp (extra field at same level as $timestamp)", + "string": "{\"a\" : {\"$timestamp\" : {\"t\" : \"123456789\", \"i\" : \"42\"}, \"unrelated\": true } }" + }, + { + "description": "Bad $timestamp (extra field at same level as t and i)", + "string": "{\"a\" : {\"$timestamp\" : {\"t\" : \"123456789\", \"i\" : \"42\", \"unrelated\": true} } }" + }, + { + "description": "Bad $timestamp (missing t)", + "string": "{\"a\" : {\"$timestamp\" : {\"i\" : \"42\"} } }" + }, + { + "description": "Bad $timestamp (missing i)", + "string": "{\"a\" : {\"$timestamp\" : {\"t\" : \"123456789\"} } }" + }, + { + "description": "Bad $date (number, not string or hash)", + "string": "{\"a\" : {\"$date\" : 42}}" + }, + { + "description": "Bad $date (extra field)", + "string": "{\"a\" : {\"$date\" : {\"$numberLong\" : \"1356351330501\"}, \"unrelated\": true}}" + }, + { + "description": "Bad DBRef (ref is number, not string)", + "string": "{\"x\" : {\"$ref\" : 42, \"$id\" : \"abc\"}}" + }, + { + "description": "Bad DBRef (db is number, not string)", + "string": "{\"x\" : {\"$ref\" : \"a\", \"$id\" : \"abc\", \"$db\" : 42}}" + }, + { + "description": "Bad $minKey (boolean, not integer)", + "string": "{\"a\" : {\"$minKey\" : true}}" + }, + { + "description": "Bad $minKey (wrong integer)", + "string": "{\"a\" : {\"$minKey\" : 0}}" + }, + { + "description": "Bad $minKey (extra field)", + "string": "{\"a\" : {\"$minKey\" : 1, \"unrelated\": true}}" + }, + { + "description": "Bad $maxKey (boolean, not integer)", + "string": "{\"a\" : {\"$maxKey\" : true}}" + }, + { + "description": "Bad $maxKey (wrong integer)", + "string": "{\"a\" : {\"$maxKey\" : 0}}" + }, + { + "description": "Bad $maxKey (extra field)", + "string": "{\"a\" : {\"$maxKey\" : 1, \"unrelated\": true}}" + }, + { + "description": "Bad DBpointer (extra field)", + "string": "{\"a\": {\"$dbPointer\": {\"a\": {\"$numberInt\": \"1\"}, \"$id\": {\"$oid\": \"56e1fc72e0c917e9c4714161\"}, \"c\": {\"$numberInt\": \"2\"}, \"$ref\": \"b\"}}}" + } + + ] +} diff --git a/bson/src/test/resources/bson/undefined.json b/bson/src/test/resources/bson/undefined.json new file mode 100644 index 00000000000..285f068258c --- /dev/null +++ b/bson/src/test/resources/bson/undefined.json @@ -0,0 +1,15 @@ +{ + "description": "Undefined type (deprecated)", + "bson_type": "0x06", + "deprecated": true, + "test_key": "a", + "valid": [ + { + "description": "Undefined", + "canonical_bson": "0800000006610000", + "canonical_extjson": "{\"a\" : {\"$undefined\" : true}}", + "converted_bson": "080000000A610000", + "converted_extjson": "{\"a\" : null}" + } + ] +} diff --git a/driver/src/test/unit/org/bson/BSONTest.java b/bson/src/test/unit/org/bson/BSONTest.java similarity index 86% rename from driver/src/test/unit/org/bson/BSONTest.java rename to bson/src/test/unit/org/bson/BSONTest.java index 9440b2529d1..94a15c44c00 100644 --- a/driver/src/test/unit/org/bson/BSONTest.java +++ b/bson/src/test/unit/org/bson/BSONTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.bson; -import com.mongodb.BasicDBObject; import org.bson.io.BasicOutputBuffer; import org.bson.io.OutputBuffer; import org.bson.types.CodeWScope; @@ -31,7 +30,6 @@ import java.util.Date; import java.util.List; -import static com.mongodb.util.Util.hexMD5; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; @@ -41,6 +39,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +@SuppressWarnings("deprecation") public class BSONTest { @Before @@ -55,27 +54,34 @@ public void tearDown() { @Test public void testSimpleDocuments() throws IOException { - checkEncodingAndDecoding(new BasicBSONObject("x", true), 9, "6fe24623e4efc5cf07f027f9c66b5456"); - checkEncodingAndDecoding(new BasicBSONObject("x", null), 8, "12d43430ff6729af501faf0638e68888"); - checkEncodingAndDecoding(new BasicBSONObject("x", 5.2), 16, "aaeeac4a58e9c30eec6b0b0319d0dff2"); - checkEncodingAndDecoding(new BasicBSONObject("x", "eliot"), 18, "331a3b8b7cbbe0706c80acdb45d4ebbe"); + checkEncodingAndDecoding(new BasicBSONObject("x", true), 9, "090000000878000100"); + checkEncodingAndDecoding(new BasicBSONObject("x", null), 8, "080000000a780000"); + checkEncodingAndDecoding(new BasicBSONObject("x", 5.2), 16, "10000000017800cdcccccccccc144000"); + checkEncodingAndDecoding(new BasicBSONObject("x", "eliot"), 18, + "1200000002780006000000656c696f740000"); checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", "truth") .append("z", 1.1), - 40, "7c77b3a6e63e2f988ede92624409da58"); + 40, + "28000000017800cdcccccccccc144002790006000000747275746800017a009a9999999999f13f00"); - checkEncodingAndDecoding(new BasicBSONObject("a", new BasicBSONObject("b", 1.1)), 24, "31887a4b9d55cd9f17752d6a8a45d51f"); + checkEncodingAndDecoding(new BasicBSONObject("a", new BasicBSONObject("b", 1.1)), 24, + "18000000036100100000000162009a9999999999f13f0000"); checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", new BasicBSONObject("a", "eliot").append("b", true)) .append("z", null), - 44, "b3de8a0739ab329e7aea138d87235205"); + 44, + "2c000000017800cdcccccccccc14400379001600000002610006000000656c696f740008620001000a7a0000"); checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", new Object[]{"a", "eliot", "b", true}) .append("z", null), - 62, "cb7bad5697714ba0cbf51d113b6a0ee8"); - checkEncodingAndDecoding(new BasicBSONObject("x", 4), 12, "d1ed8dbf79b78fa215e2ded74548d89d"); + 62, + "3e000000017800cdcccccccccc14400479002800000002300002000000610002310006000000656c696f740002" + + "320002000000620008330001000a7a0000"); + checkEncodingAndDecoding(new BasicBSONObject("x", 4), 12, "0c0000001078000400000000"); } @Test public void testArray() throws IOException { - checkEncodingAndDecoding(new BasicBSONObject("x", new int[]{1, 2, 3, 4}), 41, "e63397fe37de1349c50e1e4377a45e2d"); + checkEncodingAndDecoding(new BasicBSONObject("x", new int[]{1, 2, 3, 4}), 41, + "2900000004780021000000103000010000001031000200000010320003000000103300040000000000"); } @Test @@ -83,29 +89,33 @@ public void testCode() throws IOException { BSONObject scope = new BasicBSONObject("x", 1); CodeWScope c = new CodeWScope("function() { x += 1; }", scope); BSONObject document = new BasicBSONObject("map", c); - checkEncodingAndDecoding(document, 53, "52918d2367533165bfc617df50335cbb"); + checkEncodingAndDecoding(document, 53, + "350000000f6d6170002b0000001700000066756e6374696f6e2829207b2078202b3d20313b207d000c000000107800010000000000"); } @Test public void testBinary() throws IOException { - byte[] data = new byte[10000]; - for (int i = 0; i < 10000; i++) { + byte[] data = new byte[100]; + for (int i = 0; i < 100; i++) { data[i] = 1; } BSONObject document = new BasicBSONObject("bin", data); - checkEncodingAndDecoding(document, 10015, "1d439ba5b959ecfe297a7862bf95bc10"); + checkEncodingAndDecoding(document, 115, + "730000000562696e006400000000010101010101010101010101010101010101010101010101010101010101010101010101010101" + + "01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" + + "01010101010100"); } private void checkEncodingAndDecoding(final BSONObject toEncodeAndDecode, final int expectedEncodedSize, - final String expectedHash) throws IOException { + final String expectedHex) throws IOException { // check encoding BSONEncoder bsonEncoder = new BasicBSONEncoder(); OutputBuffer buf = new BasicOutputBuffer(); bsonEncoder.set(buf); bsonEncoder.putObject(toEncodeAndDecode); assertEquals(expectedEncodedSize, buf.size()); - assertEquals(expectedHash, hexMD5(buf.toByteArray())); + assertEquals(expectedHex, toHex(buf.toByteArray())); bsonEncoder.done(); // check decoding @@ -120,7 +130,7 @@ private void checkEncodingAndDecoding(final BSONObject toEncodeAndDecode, bsonEncoder.set(buf2); bsonEncoder.putObject((BSONObject) callback.get()); assertEquals(expectedEncodedSize, buf2.size()); - assertEquals(expectedHash, hexMD5(buf2.toByteArray())); + assertEquals(expectedHex, toHex(buf2.toByteArray())); } @Test @@ -307,7 +317,7 @@ private void roundTrip(final BSONObject o) { @Test public void testEncodingDecode() { - BasicDBObject inputDoc = new BasicDBObject("_id", 1); + BasicBSONObject inputDoc = new BasicBSONObject("_id", 1); byte[] encoded = BSON.encode(inputDoc); assertEquals(inputDoc, BSON.decode(encoded)); } @@ -321,6 +331,20 @@ public void testToInt() { assertEquals(13, BSON.toInt(13)); } + public static String toHex(final byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (final byte b : bytes) { + String s = Integer.toHexString(0xff & b); + + if (s.length() < 2) { + sb.append("0"); + } + sb.append(s); + } + return sb.toString(); + } + + private static class StubTransformer implements Transformer { private boolean transformCalled = false; @@ -355,7 +379,7 @@ private class TestDate { private final int minute; private final int second; - public TestDate(final int year, final int month, final int date, final int hour, final int minute, final int second) { + TestDate(final int year, final int month, final int date, final int hour, final int minute, final int second) { this.year = year; this.month = month; this.date = date; diff --git a/driver/src/test/unit/org/bson/BasicBSONDecoderSpecification.groovy b/bson/src/test/unit/org/bson/BasicBSONDecoderSpecification.groovy similarity index 75% rename from driver/src/test/unit/org/bson/BasicBSONDecoderSpecification.groovy rename to bson/src/test/unit/org/bson/BasicBSONDecoderSpecification.groovy index e8abeaa4965..17f91ac2e8a 100644 --- a/driver/src/test/unit/org/bson/BasicBSONDecoderSpecification.groovy +++ b/bson/src/test/unit/org/bson/BasicBSONDecoderSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ package org.bson -import com.mongodb.BasicDBObject import org.bson.types.BSONTimestamp import org.bson.types.Binary import org.bson.types.Code import org.bson.types.CodeWScope +import org.bson.types.Decimal128 import org.bson.types.MaxKey import org.bson.types.MinKey import org.bson.types.ObjectId @@ -37,7 +37,7 @@ class BasicBSONDecoderSpecification extends Specification { private final BSONDecoder bsonDecoder = new BasicBSONDecoder(); def setupSpec() { - Map.metaClass.bitwiseNegate = { new BasicDBObject(delegate) } + Map.metaClass.bitwiseNegate = { new BasicBSONObject(delegate) } Pattern.metaClass.equals = { Pattern other -> delegate.pattern() == other.pattern() && delegate.flags() == other.flags() } @@ -90,6 +90,7 @@ class BasicBSONDecoderSpecification extends Specification { ['i4': Long.MAX_VALUE] | [17, 0, 0, 0, 18, 105, 52, 0, -1, -1, -1, -1, -1, -1, -1, 127, 0] ['k1': new MinKey()] | [9, 0, 0, 0, -1, 107, 49, 0, 0] ['k2': new MaxKey()] | [9, 0, 0, 0, 127, 107, 50, 0, 0] + ['f': Decimal128.parse('0E-6176')] | [24, 0, 0, 0, 19, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] type = BsonType.findByValue(bytes[4]) } @@ -120,22 +121,23 @@ class BasicBSONDecoderSpecification extends Specification { 1 * callback.objectDone() where: - method | args || bytes - 'gotDouble' | ['d1', -1.01d] || [17, 0, 0, 0, 1, 100, 49, 0, 41, 92, -113, -62, -11, 40, -16, -65, 0] - 'gotString' | ['s2', 'danke'] || [19, 0, 0, 0, 2, 115, 50, 0, 6, 0, 0, 0, 100, 97, 110, 107, 101, 0, 0] - 'gotBinary' | ['b2', 0, [102, 111, 111] as byte[]] || [17, 0, 0, 0, 5, 98, 50, 0, 3, 0, 0, 0, 0, 102, 111, 111, 0] - 'gotObjectId' | ['_id', new ObjectId('50d3332018c6a1d8d1662b61')] || [22, 0, 0, 0, 7, 95, 105, 100, 0, 80, -45, 51, 32, 24, -58, -95, -40, -47, 102, 43, 97, 0] - 'gotBoolean' | ['b1', true] || [10, 0, 0, 0, 8, 98, 49, 0, 1, 0] - 'gotDate' | ['d', 582163200] || [16, 0, 0, 0, 9, 100, 0, 0, 27, -77, 34, 0, 0, 0, 0, 0] - 'gotNull' | ['n'] || [8, 0, 0, 0, 10, 110, 0, 0] - 'gotRegex' | ['r', '[a]*', 'i'] || [15, 0, 0, 0, 11, 114, 0, 91, 97, 93, 42, 0, 105, 0, 0] - 'gotCode' | ['js1', 'var i = 0'] || [24, 0, 0, 0, 13, 106, 115, 49, 0, 10, 0, 0, 0, 118, 97, 114, 32, 105, 32, 61, 32, 48, 0, 0] - 'gotSymbol' | ['s', 'c'] || [14, 0, 0, 0, 14, 115, 0, 2, 0, 0, 0, 99, 0, 0] - 'gotInt' | ['i1', -12] || [13, 0, 0, 0, 16, 105, 49, 0, -12, -1, -1, -1, 0] - 'gotLong' | ['i4', Long.MAX_VALUE] || [17, 0, 0, 0, 18, 105, 52, 0, -1, -1, -1, -1, -1, -1, -1, 127, 0] - 'gotTimestamp' | ['t', 123999401, 44332] || [16, 0, 0, 0, 17, 116, 0, 44, -83, 0, 0, -87, 20, 100, 7, 0] - 'gotMinKey' | ['k1'] || [9, 0, 0, 0, -1, 107, 49, 0, 0] - 'gotMaxKey' | ['k2'] || [9, 0, 0, 0, 127, 107, 50, 0, 0] + method | args || bytes + 'gotDouble' | ['d1', -1.01d] || [17, 0, 0, 0, 1, 100, 49, 0, 41, 92, -113, -62, -11, 40, -16, -65, 0] + 'gotString' | ['s2', 'danke'] || [19, 0, 0, 0, 2, 115, 50, 0, 6, 0, 0, 0, 100, 97, 110, 107, 101, 0, 0] + 'gotBinary' | ['b2', 0, [102, 111, 111] as byte[]] || [17, 0, 0, 0, 5, 98, 50, 0, 3, 0, 0, 0, 0, 102, 111, 111, 0] + 'gotObjectId' | ['_id', new ObjectId('50d3332018c6a1d8d1662b61')] || [22, 0, 0, 0, 7, 95, 105, 100, 0, 80, -45, 51, 32, 24, -58, -95, -40, -47, 102, 43, 97, 0] + 'gotBoolean' | ['b1', true] || [10, 0, 0, 0, 8, 98, 49, 0, 1, 0] + 'gotDate' | ['d', 582163200] || [16, 0, 0, 0, 9, 100, 0, 0, 27, -77, 34, 0, 0, 0, 0, 0] + 'gotNull' | ['n'] || [8, 0, 0, 0, 10, 110, 0, 0] + 'gotRegex' | ['r', '[a]*', 'i'] || [15, 0, 0, 0, 11, 114, 0, 91, 97, 93, 42, 0, 105, 0, 0] + 'gotCode' | ['js1', 'var i = 0'] || [24, 0, 0, 0, 13, 106, 115, 49, 0, 10, 0, 0, 0, 118, 97, 114, 32, 105, 32, 61, 32, 48, 0, 0] + 'gotSymbol' | ['s', 'c'] || [14, 0, 0, 0, 14, 115, 0, 2, 0, 0, 0, 99, 0, 0] + 'gotInt' | ['i1', -12] || [13, 0, 0, 0, 16, 105, 49, 0, -12, -1, -1, -1, 0] + 'gotLong' | ['i4', Long.MAX_VALUE] || [17, 0, 0, 0, 18, 105, 52, 0, -1, -1, -1, -1, -1, -1, -1, 127, 0] + 'gotTimestamp' | ['t', 123999401, 44332] || [16, 0, 0, 0, 17, 116, 0, 44, -83, 0, 0, -87, 20, 100, 7, 0] + 'gotMinKey' | ['k1'] || [9, 0, 0, 0, -1, 107, 49, 0, 0] + 'gotMaxKey' | ['k2'] || [9, 0, 0, 0, 127, 107, 50, 0, 0] + 'gotDecimal128' | ['f', Decimal128.parse('0E-6176')] || [24, 0, 0, 0, 19, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] //gotDBRef //arrayStart diff --git a/driver/src/test/unit/org/bson/BasicBSONEncoderSpecification.groovy b/bson/src/test/unit/org/bson/BasicBSONEncoderSpecification.groovy similarity index 97% rename from driver/src/test/unit/org/bson/BasicBSONEncoderSpecification.groovy rename to bson/src/test/unit/org/bson/BasicBSONEncoderSpecification.groovy index ae8c50784d6..6a453b90dd9 100644 --- a/driver/src/test/unit/org/bson/BasicBSONEncoderSpecification.groovy +++ b/bson/src/test/unit/org/bson/BasicBSONEncoderSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.bson -import com.mongodb.BasicDBObject import org.bson.io.BasicOutputBuffer import org.bson.io.OutputBuffer import org.bson.types.BSONTimestamp @@ -24,6 +23,7 @@ import org.bson.types.BasicBSONList import org.bson.types.Binary import org.bson.types.Code import org.bson.types.CodeWScope +import org.bson.types.Decimal128 import org.bson.types.MaxKey import org.bson.types.MinKey import org.bson.types.ObjectId @@ -38,7 +38,7 @@ import java.util.regex.Pattern class BasicBSONEncoderSpecification extends Specification { def setupSpec() { - Map.metaClass.bitwiseNegate = { new BasicDBObject(delegate) } + Map.metaClass.bitwiseNegate = { new BasicBSONObject(delegate) } Pattern.metaClass.equals = { Pattern other -> delegate.pattern() == other.pattern() && delegate.flags() == other.flags() } @@ -88,6 +88,7 @@ class BasicBSONEncoderSpecification extends Specification { ['i': Long.MAX_VALUE] | [16, 0, 0, 0, 18, 105, 0, -1, -1, -1, -1, -1, -1, -1, 127, 0] ['k': new MinKey()] | [8, 0, 0, 0, -1, 107, 0, 0] ['k': new MaxKey()] | [8, 0, 0, 0, 127, 107, 0, 0] + ['f': Decimal128.parse('0E-6176')] | [24, 0, 0, 0, 19, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] aClass = document.find { true }.value.getClass() } diff --git a/bson/src/test/unit/org/bson/BsonArraySpecification.groovy b/bson/src/test/unit/org/bson/BsonArraySpecification.groovy new file mode 100644 index 00000000000..a4d4e11c41a --- /dev/null +++ b/bson/src/test/unit/org/bson/BsonArraySpecification.groovy @@ -0,0 +1,61 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import spock.lang.Specification + +class BsonArraySpecification extends Specification { + + def 'should be array type'() { + expect: + new BsonArray().getBsonType() == BsonType.ARRAY + } + + def 'should construct empty array'() { + when: + def array = new BsonArray() + + then: + array.isEmpty() + array.size() == 0 + array.getValues().isEmpty() + } + + def 'should construct from a list'() { + given: + def list = [BsonBoolean.TRUE, BsonBoolean.FALSE] + + when: + def array = new BsonArray(list) + + then: + !array.isEmpty() + array.size() == 2 + array.getValues() == list + + when: + list.remove(BsonBoolean.TRUE) + + then: + array.getValues() != list + } + + def 'should parse json'() { + expect: + BsonArray.parse('[1, true]') == new BsonArray([new BsonInt32(1), BsonBoolean.TRUE]) + } +} diff --git a/bson/src/test/unit/org/bson/BsonBinaryReaderSpecification.groovy b/bson/src/test/unit/org/bson/BsonBinaryReaderSpecification.groovy new file mode 100644 index 00000000000..d2f689ea398 --- /dev/null +++ b/bson/src/test/unit/org/bson/BsonBinaryReaderSpecification.groovy @@ -0,0 +1,52 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import spock.lang.Specification +import spock.lang.Unroll + +import static org.bson.AbstractBsonReader.State.DONE +import static org.bson.AbstractBsonReader.State.TYPE +import static org.bson.BsonHelper.toBson + +class BsonBinaryReaderSpecification extends Specification { + + @Unroll + def 'should skip value #value'() { + given: + def document = new BsonDocument('name', value) + def reader = new BsonBinaryReader(toBson(document)) + reader.readStartDocument() + reader.readBsonType() + + when: + reader.skipName() + reader.skipValue() + + then: + reader.getState() == TYPE + + when: + reader.readEndDocument() + + then: + reader.getState() == DONE + + where: + value << BsonHelper.valuesOfEveryType() + } +} diff --git a/bson/src/test/unit/org/bson/BsonBinaryReaderTest.java b/bson/src/test/unit/org/bson/BsonBinaryReaderTest.java index a85e87daa90..54c3d0034e6 100644 --- a/bson/src/test/unit/org/bson/BsonBinaryReaderTest.java +++ b/bson/src/test/unit/org/bson/BsonBinaryReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/org/bson/BsonBinarySpecification.groovy b/bson/src/test/unit/org/bson/BsonBinarySpecification.groovy new file mode 100644 index 00000000000..e51094e964f --- /dev/null +++ b/bson/src/test/unit/org/bson/BsonBinarySpecification.groovy @@ -0,0 +1,83 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import spock.lang.Specification +import spock.lang.Unroll + +class BsonBinarySpecification extends Specification { + + @Unroll + def 'should initialize with data'() { + given: + def bsonBinary = new BsonBinary((byte) 80, data as byte[]) + + expect: + data == bsonBinary.getData() + + where: + data << [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [2, 5, 4, 67, 3, 4, 5, 2, 4, 2, 5, 6, 7, 4, 5, 12], + [34, 24, 56, 76, 3, 4, 1, 12, 1, 9, 8, 7, 56, 46, 3, 9] + ] + } + + @Unroll + def 'should initialize with data and BsonBinarySubType'() { + given: + byte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + def bsonBinary = new BsonBinary(subType, data) + + expect: + subType.getValue() == bsonBinary.getType() + data == bsonBinary.getData() + + where: + subType << [BsonBinarySubType.BINARY, BsonBinarySubType.FUNCTION, BsonBinarySubType.MD5, + BsonBinarySubType.OLD_BINARY, BsonBinarySubType.USER_DEFINED, BsonBinarySubType.UUID_LEGACY, + BsonBinarySubType.UUID_STANDARD] + } + + @Unroll + def 'should initialize with UUID'() { + given: + def bsonBinary = new BsonBinary(uuid) + + expect: + uuid == bsonBinary.asUuid() + + where: + uuid << [UUID.fromString('ffadee18-b533-11e8-96f8-529269fb1459'), + UUID.fromString('a5dc280e-b534-11e8-96f8-529269fb1459'), + UUID.fromString('4ef2a357-cb16-45a6-a6f6-a11ae1972917')] + } + + @Unroll + def 'should initialize with UUID and UUID representation'() { + given: + def uuid = UUID.fromString('ffadee18-b533-11e8-96f8-529269fb1459') + def bsonBinary = new BsonBinary(uuid, uuidRepresentation) + + expect: + uuid == bsonBinary.asUuid(uuidRepresentation) + + where: + uuidRepresentation << [UuidRepresentation.STANDARD, UuidRepresentation.C_SHARP_LEGACY, + UuidRepresentation.JAVA_LEGACY, UuidRepresentation.PYTHON_LEGACY] + } +} diff --git a/bson/src/test/unit/org/bson/BsonBinarySubTypeSpecification.groovy b/bson/src/test/unit/org/bson/BsonBinarySubTypeSpecification.groovy new file mode 100644 index 00000000000..5376f8203eb --- /dev/null +++ b/bson/src/test/unit/org/bson/BsonBinarySubTypeSpecification.groovy @@ -0,0 +1,35 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import spock.lang.Specification + +class BsonBinarySubTypeSpecification extends Specification { + + def 'should be uuid only for legacy and uuid types'() { + expect: + BsonBinarySubType.isUuid(value as byte) == isUuid + + where: + value | isUuid + 1 | false + 2 | false + 3 | true + 4 | true + 5 | false + } +} diff --git a/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java b/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java index 4866b86eb25..450647eda13 100644 --- a/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java +++ b/bson/src/test/unit/org/bson/BsonBinaryWriterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.List; +import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -49,20 +51,29 @@ public void tearDown() { writer.close(); } - @Test(expected = BsonSerializationException.class) + @Test public void shouldThrowWhenMaxDocumentSizeIsExceeded() { - writer.writeStartDocument(); - writer.writeBinaryData("b", new BsonBinary(new byte[1024])); - writer.writeEndDocument(); + try { + writer.writeStartDocument(); + writer.writeBinaryData("b", new BsonBinary(new byte[1024])); + writer.writeEndDocument(); + fail(); + } catch (BsonMaximumSizeExceededException e) { + assertEquals("Document size of 1037 is larger than maximum of 1024.", e.getMessage()); + } } - @Test(expected = BsonSerializationException.class) + @Test public void shouldThrowIfAPushedMaxDocumentSizeIsExceeded() { - writer.writeStartDocument(); - writer.pushMaxDocumentSize(10); - writer.writeStartDocument("doc"); - writer.writeString("s", "123456789"); - writer.writeEndDocument(); + try { + writer.writeStartDocument(); + writer.pushMaxDocumentSize(10); + writer.writeStartDocument("doc"); + writer.writeString("s", "123456789"); + writer.writeEndDocument(); + } catch (BsonMaximumSizeExceededException e) { + assertEquals("Document size of 22 is larger than maximum of 10.", e.getMessage()); + } } @Test @@ -273,10 +284,6 @@ public void testWriteMinMaxKeys() { writer.writeEndDocument(); - for (final byte b : buffer.toByteArray()) { - System.out.print(b + ", "); - } - byte[] expectedValues = {17, 0, 0, 0, 127, 107, 49, 0, -1, 107, 50, 0, 127, 107, 51, 0, 0}; assertArrayEquals(expectedValues, buffer.toByteArray()); } @@ -628,6 +635,98 @@ public void testPipeDocumentIntoScopeDocument() { reader2.readEndDocument(); } + @Test + public void testPipeWithExtraElements() { + writer.writeStartDocument(); + writer.writeBoolean("a", true); + writer.writeString("$db", "test"); + writer.writeStartDocument("$readPreference"); + writer.writeString("mode", "primary"); + writer.writeEndDocument(); + writer.writeEndDocument(); + + byte[] bytes = buffer.toByteArray(); + + BasicOutputBuffer pipedBuffer = new BasicOutputBuffer(); + BsonBinaryWriter pipedWriter = new BsonBinaryWriter(new BsonWriterSettings(100), + new BsonBinaryWriterSettings(1024), pipedBuffer); + + pipedWriter.writeStartDocument(); + pipedWriter.writeBoolean("a", true); + pipedWriter.writeEndDocument(); + + List extraElements = asList( + new BsonElement("$db", new BsonString("test")), + new BsonElement("$readPreference", new BsonDocument("mode", new BsonString("primary"))) + ); + + BasicOutputBuffer newBuffer = new BasicOutputBuffer(); + BsonBinaryWriter newWriter = new BsonBinaryWriter(newBuffer); + try { + BsonBinaryReader reader = + new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(pipedBuffer.toByteArray())))); + try { + newWriter.pipe(reader, extraElements); + } finally { + reader.close(); + } + } finally { + newWriter.close(); + } + assertArrayEquals(bytes, newBuffer.toByteArray()); + } + + @Test + public void testPipeOfNestedDocumentWithExtraElements() { + writer.writeStartDocument(); + writer.writeStartDocument("nested"); + + writer.writeBoolean("a", true); + writer.writeString("$db", "test"); + writer.writeStartDocument("$readPreference"); + writer.writeString("mode", "primary"); + writer.writeEndDocument(); + writer.writeEndDocument(); + + writer.writeBoolean("b", true); + writer.writeEndDocument(); + + byte[] bytes = buffer.toByteArray(); + + BasicOutputBuffer pipedBuffer = new BasicOutputBuffer(); + BsonBinaryWriter pipedWriter = new BsonBinaryWriter(new BsonWriterSettings(100), + new BsonBinaryWriterSettings(1024), pipedBuffer); + + pipedWriter.writeStartDocument(); + pipedWriter.writeBoolean("a", true); + pipedWriter.writeEndDocument(); + + List extraElements = asList( + new BsonElement("$db", new BsonString("test")), + new BsonElement("$readPreference", new BsonDocument("mode", new BsonString("primary"))) + ); + + BasicOutputBuffer newBuffer = new BasicOutputBuffer(); + BsonBinaryWriter newWriter = new BsonBinaryWriter(newBuffer); + try { + BsonBinaryReader reader = + new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(pipedBuffer.toByteArray())))); + try { + newWriter.writeStartDocument(); + newWriter.writeName("nested"); + newWriter.pipe(reader, extraElements); + newWriter.writeBoolean("b", true); + newWriter.writeEndDocument(); + } finally { + reader.close(); + } + } finally { + newWriter.close(); + } + byte[] actualBytes = newBuffer.toByteArray(); + assertArrayEquals(bytes, actualBytes); + } + @Test public void testPipeOfDocumentWithInvalidSize() { byte[] bytes = {4, 0, 0, 0}; // minimum document size is 5; diff --git a/bson/src/test/unit/org/bson/BsonDocumentReaderSpecification.groovy b/bson/src/test/unit/org/bson/BsonDocumentReaderSpecification.groovy index 6a397e0a9f0..e79fcc4f1ca 100644 --- a/bson/src/test/unit/org/bson/BsonDocumentReaderSpecification.groovy +++ b/bson/src/test/unit/org/bson/BsonDocumentReaderSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.bson import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.DecoderContext +import org.bson.types.Decimal128 import org.bson.types.ObjectId import spock.lang.Shared import spock.lang.Specification @@ -40,6 +41,7 @@ class BsonDocumentReaderSpecification extends Specification { new BsonElement('null', new BsonNull()), new BsonElement('int32', new BsonInt32(42)), new BsonElement('int64', new BsonInt64(52L)), + new BsonElement('decimal128', new BsonDecimal128(Decimal128.parse('1.0'))), new BsonElement('boolean', new BsonBoolean(true)), new BsonElement('date', new BsonDateTime(new Date().getTime())), new BsonElement('double', new BsonDouble(62.0)), @@ -96,4 +98,4 @@ class BsonDocumentReaderSpecification extends Specification { } -} \ No newline at end of file +} diff --git a/bson/src/test/unit/org/bson/BsonDocumentSpecification.groovy b/bson/src/test/unit/org/bson/BsonDocumentSpecification.groovy index 58098bec3bf..70004b654d5 100644 --- a/bson/src/test/unit/org/bson/BsonDocumentSpecification.groovy +++ b/bson/src/test/unit/org/bson/BsonDocumentSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,14 @@ package org.bson +import org.bson.codecs.BsonDocumentCodec +import org.bson.codecs.DecoderContext +import org.bson.types.Decimal128 import org.bson.types.ObjectId import spock.lang.Specification +import static org.bson.BsonHelper.documentWithValuesOfEveryType + class BsonDocumentSpecification extends Specification { def 'conversion methods should behave correctly for the happy path'() { @@ -27,6 +32,7 @@ class BsonDocumentSpecification extends Specification { def bsonNull = new BsonNull() def bsonInt32 = new BsonInt32(42) def bsonInt64 = new BsonInt64(52L) + def bsonDecimal128 = new BsonDecimal128(Decimal128.parse('1.0')) def bsonBoolean = new BsonBoolean(true) def bsonDateTime = new BsonDateTime(new Date().getTime()) def bsonDouble = new BsonDouble(62.0) @@ -51,6 +57,7 @@ class BsonDocumentSpecification extends Specification { new BsonElement('null', bsonNull), new BsonElement('int32', bsonInt32), new BsonElement('int64', bsonInt64), + new BsonElement('decimal128', bsonDecimal128), new BsonElement('boolean', bsonBoolean), new BsonElement('date', bsonDateTime), new BsonElement('double', bsonDouble), @@ -73,6 +80,7 @@ class BsonDocumentSpecification extends Specification { root.isNull('null') root.getInt32('int32').is(bsonInt32) root.getInt64('int64').is(bsonInt64) + root.getDecimal128('decimal128').is(bsonDecimal128) root.getBoolean('boolean').is(bsonBoolean) root.getDateTime('date').is(bsonDateTime) root.getDouble('double').is(bsonDouble) @@ -89,6 +97,7 @@ class BsonDocumentSpecification extends Specification { root.getInt32('int32', new BsonInt32(2)).is(bsonInt32) root.getInt64('int64', new BsonInt64(4)).is(bsonInt64) + root.getDecimal128('decimal128', new BsonDecimal128(Decimal128.parse('4.0'))).is(bsonDecimal128) root.getDouble('double', new BsonDouble(343.0)).is(bsonDouble) root.getBoolean('boolean', new BsonBoolean(false)).is(bsonBoolean) root.getDateTime('date', new BsonDateTime(3453)).is(bsonDateTime) @@ -105,6 +114,7 @@ class BsonDocumentSpecification extends Specification { root.get('int32').asInt32().is(bsonInt32) root.get('int64').asInt64().is(bsonInt64) + root.get('decimal128').asDecimal128().is(bsonDecimal128) root.get('boolean').asBoolean().is(bsonBoolean) root.get('date').asDateTime().is(bsonDateTime) root.get('double').asDouble().is(bsonDouble) @@ -118,6 +128,7 @@ class BsonDocumentSpecification extends Specification { root.isInt32('int32') root.isNumber('int32') root.isInt64('int64') + root.isDecimal128('decimal128') root.isNumber('int64') root.isBoolean('boolean') root.isDateTime('date') @@ -140,6 +151,7 @@ class BsonDocumentSpecification extends Specification { !root.isNumber('number') !root.isInt32('int32') !root.isInt64('int64') + !root.isDecimal128('decimal128') !root.isBoolean('boolean') !root.isDateTime('date') !root.isDouble('double') @@ -156,6 +168,7 @@ class BsonDocumentSpecification extends Specification { def bsonNull = new BsonNull() def bsonInt32 = new BsonInt32(42) def bsonInt64 = new BsonInt64(52L) + def bsonDecimal128 = new BsonDecimal128(Decimal128.parse('1.0')) def bsonBoolean = new BsonBoolean(true) def bsonDateTime = new BsonDateTime(new Date().getTime()) def bsonDouble = new BsonDouble(62.0) @@ -165,6 +178,7 @@ class BsonDocumentSpecification extends Specification { def timestamp = new BsonTimestamp(0x12345678, 5) def binary = new BsonBinary((byte) 80, [5, 4, 3, 2, 1] as byte[]) def bsonArray = new BsonArray([new BsonInt32(1), new BsonInt64(2L), new BsonBoolean(true), + new BsonDecimal128(Decimal128.parse('4.0')), new BsonArray([new BsonInt32(1), new BsonInt32(2), new BsonInt32(3)]), new BsonDocument('a', new BsonInt64(2L))]) def bsonDocument = new BsonDocument('a', new BsonInt32(1)) @@ -179,6 +193,7 @@ class BsonDocumentSpecification extends Specification { root.getDouble('m', bsonDouble).is(bsonDouble) root.getInt32('m', bsonInt32).is(bsonInt32) root.getInt64('m', bsonInt64).is(bsonInt64) + root.getDecimal128('m', bsonDecimal128).is(bsonDecimal128) root.getString('m', bsonString).is(bsonString) root.getObjectId('m', objectId).is(objectId) root.getString('m', bsonString).is(bsonString) @@ -240,6 +255,12 @@ class BsonDocumentSpecification extends Specification { then: thrown(BsonInvalidOperationException) + when: + root.getDecimal128('decimal128') + + then: + thrown(BsonInvalidOperationException) + when: root.getBoolean('boolean') @@ -307,10 +328,44 @@ class BsonDocumentSpecification extends Specification { thrown(BsonInvalidOperationException) } + def 'should get first key'() { + given: + def document = new BsonDocument('i', new BsonInt32(2)) + + expect: + document.getFirstKey() == 'i' + } + + def 'getFirstKey should throw NoSuchElementException if the document is empty'() { + given: + def document = new BsonDocument() + + when: + document.getFirstKey() + + then: + thrown(NoSuchElementException) + } + + def 'should create BsonReader'() { + given: + def document = documentWithValuesOfEveryType() + + when: + def reader = document.asBsonReader() + + then: + new BsonDocumentCodec().decode(reader, DecoderContext.builder().build()) == document + + cleanup: + reader.close() + } + def 'should serialize and deserialize'() { given: def document = new BsonDocument('d', new BsonDocument().append('i2', new BsonInt32(1))) .append('i', new BsonInt32(2)) + .append('d', new BsonDecimal128(Decimal128.parse('1.0'))) .append('a', new BsonArray([new BsonInt32(3), new BsonArray([new BsonInt32(11)]), new BsonDocument('i3', new BsonInt32(6)), diff --git a/bson/src/test/unit/org/bson/BsonDocumentTest.java b/bson/src/test/unit/org/bson/BsonDocumentTest.java index 3589b15c17d..e9029022713 100644 --- a/bson/src/test/unit/org/bson/BsonDocumentTest.java +++ b/bson/src/test/unit/org/bson/BsonDocumentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,7 +93,7 @@ public void toJsonShouldRespectDefaultJsonWriterSettings() { @Test public void toJsonShouldRespectJsonWriterSettings() { StringWriter writer = new StringWriter(); - JsonWriterSettings settings = new JsonWriterSettings(JsonMode.SHELL); + JsonWriterSettings settings = JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build(); new BsonDocumentCodec().encode(new JsonWriter(writer, settings), document, EncoderContext.builder().build()); assertEquals(writer.toString(), document.toJson(settings)); } diff --git a/bson/src/test/unit/org/bson/BsonDocumentWrapperSpecification.groovy b/bson/src/test/unit/org/bson/BsonDocumentWrapperSpecification.groovy index 231d0234ae7..b3e3ba2ea53 100644 --- a/bson/src/test/unit/org/bson/BsonDocumentWrapperSpecification.groovy +++ b/bson/src/test/unit/org/bson/BsonDocumentWrapperSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/org/bson/BsonDocumentWriterSpecification.groovy b/bson/src/test/unit/org/bson/BsonDocumentWriterSpecification.groovy index 0195319208a..125628272ed 100644 --- a/bson/src/test/unit/org/bson/BsonDocumentWriterSpecification.groovy +++ b/bson/src/test/unit/org/bson/BsonDocumentWriterSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,45 +18,50 @@ package org.bson import org.bson.codecs.BsonDocumentCodec import org.bson.codecs.EncoderContext -import org.bson.types.ObjectId import spock.lang.Specification +import static org.bson.BsonHelper.documentWithValuesOfEveryType + class BsonDocumentWriterSpecification extends Specification { + def 'should write all types'() { - given: - def doc = new BsonDocument( - [ - new BsonElement('null', new BsonNull()), - new BsonElement('int32', new BsonInt32(42)), - new BsonElement('int64', new BsonInt64(52L)), - new BsonElement('boolean', new BsonBoolean(true)), - new BsonElement('date', new BsonDateTime(new Date().getTime())), - new BsonElement('double', new BsonDouble(62.0)), - new BsonElement('string', new BsonString('the fox ...')), - new BsonElement('minKey', new BsonMinKey()), - new BsonElement('maxKey', new BsonMaxKey()), - new BsonElement('dbPointer', new BsonDbPointer('test.test', new ObjectId())), - new BsonElement('javaScript', new BsonJavaScript('int i = 0;')), - new BsonElement('codeWithScope', new BsonJavaScriptWithScope('x', new BsonDocument('x', new BsonInt32(1)))), - new BsonElement('objectId', new BsonObjectId(new ObjectId())), - new BsonElement('regex', new BsonRegularExpression('^test.*regex.*xyz$', 'i')), - new BsonElement('symbol', new BsonSymbol('ruby stuff')), - new BsonElement('timestamp', new BsonTimestamp(0x12345678, 5)), - new BsonElement('undefined', new BsonUndefined()), - new BsonElement('binary', new BsonBinary((byte) 80, [5, 4, 3, 2, 1] as byte[])), - new BsonElement('array', new BsonArray([new BsonInt32(1), new BsonInt64(2L), new BsonBoolean(true), - new BsonArray([new BsonInt32(1), new BsonInt32(2), new BsonInt32(3)]), - new BsonDocument('a', new BsonInt64(2L))])), - new BsonElement('document', new BsonDocument('a', new BsonInt32(1))) - ]) + when: + def encodedDoc = new BsonDocument(); + new BsonDocumentCodec().encode(new BsonDocumentWriter(encodedDoc), documentWithValuesOfEveryType(), + EncoderContext.builder().build()) + then: + encodedDoc == documentWithValuesOfEveryType() + } + + def 'should pipe all types'() { + given: + def document = new BsonDocument() + def reader = new BsonDocumentReader(documentWithValuesOfEveryType()) + def writer = new BsonDocumentWriter(document) when: - def encodedDoc = new BsonDocument(); - new BsonDocumentCodec().encode(new BsonDocumentWriter(encodedDoc), doc, EncoderContext.builder().build()) + writer.pipe(reader) then: - encodedDoc == doc + document == documentWithValuesOfEveryType() } -} \ No newline at end of file + def 'should pipe all types with extra elements'() { + given: + def document = new BsonDocument() + def reader = new BsonDocumentReader(new BsonDocument()) + def writer = new BsonDocumentWriter(document) + + def extraElements = [] + for (def entry : documentWithValuesOfEveryType()) { + extraElements.add(new BsonElement(entry.getKey(), entry.getValue())) + } + + when: + writer.pipe(reader, extraElements) + + then: + document == documentWithValuesOfEveryType() + } +} diff --git a/bson/src/test/unit/org/bson/BsonHelper.java b/bson/src/test/unit/org/bson/BsonHelper.java new file mode 100644 index 00000000000..29920f6651e --- /dev/null +++ b/bson/src/test/unit/org/bson/BsonHelper.java @@ -0,0 +1,112 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson; + +import org.bson.codecs.BsonDocumentCodec; +import org.bson.codecs.EncoderContext; +import org.bson.io.BasicOutputBuffer; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Arrays.asList; + +public final class BsonHelper { + + private static final Date DATE = new Date(); + private static final ObjectId OBJECT_ID = new ObjectId(); + + private static List getBsonValues() { + return asList( + new BsonNull(), + new BsonInt32(42), + new BsonInt64(52L), + new BsonDecimal128(Decimal128.parse("4.00")), + new BsonBoolean(true), + new BsonDateTime(DATE.getTime()), + new BsonDouble(62.0), + new BsonString("the fox ..."), + new BsonMinKey(), + new BsonMaxKey(), + new BsonDbPointer("test.test", OBJECT_ID), + new BsonJavaScript("int i = 0;"), + new BsonJavaScriptWithScope("x", new BsonDocument("x", new BsonInt32(1))), + new BsonObjectId(OBJECT_ID), + new BsonRegularExpression("^test.*regex.*xyz$", "i"), + new BsonSymbol("ruby stuff"), + new BsonTimestamp(0x12345678, 5), + new BsonUndefined(), + new BsonBinary((byte) 80, new byte[]{5, 4, 3, 2, 1}), + new BsonArray(asList( + new BsonInt32(1), + new BsonInt64(2L), + new BsonBoolean(true), + new BsonArray(asList( + new BsonInt32(1), + new BsonInt32(2), + new BsonInt32(3), + new BsonDocument("a", new BsonInt64(2L)))))), + new BsonDocument("a", new BsonInt32(1))); + } + + // fail class loading if any BSON types are not represented in BSON_VALUES. + static { + for (BsonType curBsonType : BsonType.values()) { + if (curBsonType == BsonType.END_OF_DOCUMENT) { + continue; + } + + boolean found = false; + for (BsonValue curBsonValue : getBsonValues()) { + if (curBsonValue.getBsonType() == curBsonType) { + found = true; + break; + } + } + if (!found) { + throw new IllegalStateException(format("Missing BsonValue type %s in BSON_VALUES. Please add a BsonValue with that type!", + curBsonType)); + } + } + } + + public static List valuesOfEveryType() { + return getBsonValues(); + } + + public static BsonDocument documentWithValuesOfEveryType() { + BsonDocument document = new BsonDocument(); + List bsonValues = getBsonValues(); + for (int i = 0; i < bsonValues.size(); i++) { + document.append(Integer.toString(i), bsonValues.get(i)); + } + return document; + } + + public static ByteBuffer toBson(final BsonDocument document) { + BasicOutputBuffer bsonOutput = new BasicOutputBuffer(); + new BsonDocumentCodec().encode(new BsonBinaryWriter(bsonOutput), document, EncoderContext.builder().build()); + return ByteBuffer.wrap(bsonOutput.toByteArray()); + } + + private BsonHelper() { + } +} diff --git a/bson/src/test/unit/org/bson/BsonNumberSpecification.groovy b/bson/src/test/unit/org/bson/BsonNumberSpecification.groovy new file mode 100644 index 00000000000..ae8dac25eae --- /dev/null +++ b/bson/src/test/unit/org/bson/BsonNumberSpecification.groovy @@ -0,0 +1,77 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import org.bson.types.Decimal128 +import spock.lang.Specification + +class BsonNumberSpecification extends Specification { + + def 'should convert to int value'() { + expect: + new BsonInt32(1).intValue() == 1 + + new BsonInt64(1L).intValue() == 1 + new BsonInt64(Long.MAX_VALUE).intValue() == -1 + new BsonInt64(Long.MIN_VALUE).intValue() == 0 + + new BsonDouble(3.14).intValue() == 3 + + new BsonDecimal128(new Decimal128(1L)).intValue() == 1 + } + + def 'should convert to long value'() { + expect: + new BsonInt32(1).longValue() == 1L + + new BsonInt64(1L).longValue() == 1L + + new BsonDouble(3.14).longValue() == 3L + + new BsonDecimal128(new Decimal128(1L)).longValue() == 1L + } + + def 'should convert to double value'() { + expect: + new BsonInt32(1).doubleValue() == 1.0d + + new BsonInt64(1L).doubleValue() == 1.0d + new BsonInt64(Long.MAX_VALUE).doubleValue() == 9.223372036854776E18d + new BsonInt64(Long.MIN_VALUE).doubleValue() == -9.223372036854776E18d + + new BsonDouble(3.14d).doubleValue() == 3.14d + + new BsonDecimal128(Decimal128.parse('3.14')).doubleValue() == 3.14d + } + + def 'should convert to decimal128 value'() { + expect: + new BsonInt32(1).decimal128Value() == Decimal128.parse('1') + + new BsonInt64(1L).decimal128Value() == Decimal128.parse('1') + new BsonInt64(Long.MAX_VALUE).decimal128Value() == Decimal128.parse('9223372036854775807') + new BsonInt64(Long.MIN_VALUE).decimal128Value() == Decimal128.parse('-9223372036854775808') + + new BsonDouble(1.0d).decimal128Value() == Decimal128.parse('1') + new BsonDouble(Double.NaN).decimal128Value() == Decimal128.NaN + new BsonDouble(Double.POSITIVE_INFINITY).decimal128Value() == Decimal128.POSITIVE_INFINITY + new BsonDouble(Double.NEGATIVE_INFINITY).decimal128Value() == Decimal128.NEGATIVE_INFINITY + + new BsonDecimal128(Decimal128.parse('3.14')).decimal128Value() == Decimal128.parse('3.14') + } + +} diff --git a/bson/src/test/unit/org/bson/BsonRegularExpressionSpecification.groovy b/bson/src/test/unit/org/bson/BsonRegularExpressionSpecification.groovy new file mode 100644 index 00000000000..a8a58cc9020 --- /dev/null +++ b/bson/src/test/unit/org/bson/BsonRegularExpressionSpecification.groovy @@ -0,0 +1,62 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import spock.lang.Specification + +class BsonRegularExpressionSpecification extends Specification { + def 'should get type'() { + expect: + new BsonRegularExpression('abc', '').bsonType == BsonType.REGULAR_EXPRESSION + } + + def 'should sort options'() { + expect: + new BsonRegularExpression('abc', 'uxsmi').options == 'imsux' + } + + def 'should accept invalid options'() { + expect: + new BsonRegularExpression('abc', 'uxsmiw').options == 'imsuwx' + } + + def 'should allow null options'() { + expect: + new BsonRegularExpression('abc').options == '' + new BsonRegularExpression('abc', null).options == '' + } + + def 'should get regular expression'() { + expect: + new BsonRegularExpression('abc', null).pattern == 'abc' + } + + def 'equivalent values should be equal and have same hashcode'() { + given: + def first = new BsonRegularExpression('abc', 'uxsmi') + def second = new BsonRegularExpression('abc', 'imsxu') + + expect: + first.equals(second) + first.hashCode() == second.hashCode() + } + + def 'should convert to string'() { + expect: + new BsonRegularExpression('abc', 'uxsmi').toString() == 'BsonRegularExpression{pattern=\'abc\', options=\'imsux\'}' + } +} diff --git a/bson/src/test/unit/org/bson/BsonTimestampSpecification.groovy b/bson/src/test/unit/org/bson/BsonTimestampSpecification.groovy new file mode 100644 index 00000000000..e539961d44b --- /dev/null +++ b/bson/src/test/unit/org/bson/BsonTimestampSpecification.groovy @@ -0,0 +1,88 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import spock.lang.Specification +import spock.lang.Unroll + +import static java.lang.Integer.MAX_VALUE +import static java.lang.Integer.MIN_VALUE + +class BsonTimestampSpecification extends Specification { + + def 'bsonType should get expected value'() { + expect: + new BsonTimestamp().bsonType == BsonType.TIMESTAMP + } + + @Unroll + def 'compareTo should sort the timestamps as unsigned values'() { + def timestamps = [new BsonTimestamp(Long.MIN_VALUE), + new BsonTimestamp(Long.MAX_VALUE), + new BsonTimestamp(1), + new BsonTimestamp(2), + new BsonTimestamp(-1), + new BsonTimestamp(-2)] + when: + Collections.sort(timestamps) + + then: + timestamps == [new BsonTimestamp(1), + new BsonTimestamp(2), + new BsonTimestamp(Long.MAX_VALUE), + new BsonTimestamp(Long.MIN_VALUE), + new BsonTimestamp(-2), + new BsonTimestamp(-1)] + } + + @Unroll + def 'constructors should initialize instance'() { + when: + def tsFromValue = new BsonTimestamp(value) + def tsFromSecondsAndIncrement = new BsonTimestamp(seconds, increment) + + then: + tsFromValue.time == seconds + tsFromValue.inc == increment + tsFromValue.value == value + + tsFromSecondsAndIncrement.time == seconds + tsFromSecondsAndIncrement.inc == increment + tsFromSecondsAndIncrement.value == value + + where: + seconds | increment | value + 0 | 0 | 0L + 1 | 2 | 0x100000002L + -1 | -2 | 0xfffffffffffffffeL + 123456789 | 42 | 530242871224172586L + MIN_VALUE | MIN_VALUE | 0x8000000080000000L + MIN_VALUE | MAX_VALUE | 0x800000007fffffffL + MAX_VALUE | MIN_VALUE | 0x7fffffff80000000L + MAX_VALUE | MAX_VALUE | 0x7fffffff7fffffffL + } + + def 'no args constructor should initialize instance'() { + when: + def tsFromValue = new BsonTimestamp() + + then: + tsFromValue.time == 0 + tsFromValue.inc == 0 + tsFromValue.value == 0 + } +} diff --git a/bson/src/test/unit/org/bson/BsonValueSpecification.groovy b/bson/src/test/unit/org/bson/BsonValueSpecification.groovy index 6b2644b4665..1313bca7edf 100644 --- a/bson/src/test/unit/org/bson/BsonValueSpecification.groovy +++ b/bson/src/test/unit/org/bson/BsonValueSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.bson +import org.bson.types.Decimal128 import org.bson.types.ObjectId import spock.lang.Specification @@ -27,6 +28,7 @@ class BsonValueSpecification extends Specification { new BsonInt32(42).isNumber() new BsonInt64(52L).isInt64() new BsonInt64(52L).isNumber() + new BsonDecimal128(Decimal128.parse('1')).isDecimal128() new BsonDouble(62.0).isDouble() new BsonDouble(62.0).isNumber() new BsonBoolean(true).isBoolean() @@ -50,6 +52,7 @@ class BsonValueSpecification extends Specification { !new BsonNull().isInt32() !new BsonNull().isNumber() !new BsonNull().isInt64() + !new BsonNull().isDecimal128() !new BsonNull().isNumber() !new BsonNull().isDouble() !new BsonNull().isNumber() @@ -105,6 +108,12 @@ class BsonValueSpecification extends Specification { then: thrown(BsonInvalidOperationException) + when: + new BsonNull().asDecimal128() + + then: + thrown(BsonInvalidOperationException) + when: new BsonNull().asBoolean() @@ -189,4 +198,4 @@ class BsonValueSpecification extends Specification { then: thrown(BsonInvalidOperationException) } -} \ No newline at end of file +} diff --git a/bson/src/test/unit/org/bson/BsonWriterSpecification.groovy b/bson/src/test/unit/org/bson/BsonWriterSpecification.groovy index 114ec189081..d85cb47bcaf 100644 --- a/bson/src/test/unit/org/bson/BsonWriterSpecification.groovy +++ b/bson/src/test/unit/org/bson/BsonWriterSpecification.groovy @@ -1,14 +1,14 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * - * Licensed under the Apache License, Version 2.0 (the 'License'); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, + * 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. diff --git a/bson/src/test/unit/org/bson/DocumentTest.java b/bson/src/test/unit/org/bson/DocumentTest.java index 4aef414b53b..6deaa90e4de 100644 --- a/bson/src/test/unit/org/bson/DocumentTest.java +++ b/bson/src/test/unit/org/bson/DocumentTest.java @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -29,6 +29,10 @@ import org.bson.json.JsonReader; import org.junit.Test; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import static java.util.Arrays.asList; import static org.bson.codecs.configuration.CodecRegistries.fromCodecs; import static org.bson.codecs.configuration.CodecRegistries.fromProviders; @@ -75,6 +79,45 @@ public void toJsonShouldReturnEquivalent() { document); } + // Test to ensure that toJson does not reorder _id field + @Test + public void toJsonShouldNotReorderIdField() { + // given + Document d = new Document().append("x", 1) + .append("y", Collections.singletonList("one")) + .append("_id", "1"); + assertEquals("{\"x\": 1, \"y\": [\"one\"], \"_id\": \"1\"}", d.toJson()); + } + + // Test in Java to make sure none of the casts result in compiler warnings or class cast exceptions + @Test + public void shouldGetWithDefaultValue() { + // given + Document d = new Document("x", 1) + .append("y", Collections.singletonList("one")) + .append("z", "foo"); + + // when the key is found + int x = d.get("x", 2); + List y = d.get("y", Arrays.asList("three", "four")); + String z = d.get("z", "bar"); + + // then it returns the value + assertEquals(1, x); + assertEquals(Arrays.asList("one"), y); + assertEquals("foo", z); + + // when the key is not found + int x2 = d.get("x2", 2); + List y2 = d.get("y2", Arrays.asList("three", "four")); + String z2 = d.get("z2", "bar"); + + // then it returns the default value + assertEquals(2, x2); + assertEquals(Arrays.asList("three", "four"), y2); + assertEquals("bar", z2); + } + @Test public void toJsonShouldTakeACustomDocumentCodec() { @@ -85,7 +128,7 @@ public void toJsonShouldTakeACustomDocumentCodec() { // noop } - assertEquals("{ \"database\" : { \"name\" : \"MongoDB\" } }", customDocument.toJson(customDocumentCodec)); + assertEquals("{\"database\": {\"name\": \"MongoDB\"}}", customDocument.toJson(customDocumentCodec)); } public class Name { diff --git a/bson/src/test/unit/org/bson/GenericBsonTest.java b/bson/src/test/unit/org/bson/GenericBsonTest.java index 998c5c369e4..0a611267f05 100644 --- a/bson/src/test/unit/org/bson/GenericBsonTest.java +++ b/bson/src/test/unit/org/bson/GenericBsonTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,8 +12,6 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * - * */ package org.bson; @@ -22,20 +20,31 @@ import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; import org.bson.io.BasicOutputBuffer; +import org.bson.json.JsonMode; +import org.bson.json.JsonParseException; +import org.bson.json.JsonWriterSettings; +import org.bson.types.Decimal128; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import util.Hex; import util.JsonPoweredTestHelper; -import javax.xml.bind.DatatypeConverter; import java.io.File; import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; import java.net.URISyntaxException; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import static java.lang.String.format; +import static org.bson.BsonDocument.parse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -43,69 +52,287 @@ @RunWith(Parameterized.class) public class GenericBsonTest { - private final BsonDocument definition; + private static final List IGNORED_PARSE_ERRORS = Arrays.asList( + "Bad $binary (type is number, not string)", // for backwards compat, JsonReader supports number for binary type + "Bad $date (number, not string or hash)", // for backwards compat, JsonReader supports numbers for $date + "Bad DBRef (ref is number, not string)", // JsonReader knows nothing of DBRef so these are not parse errors + "Bad DBRef (db is number, not string)"); + + enum TestCaseType { + VALID, + DECODE_ERROR, + PARSE_ERROR + } + + private final BsonDocument testDefinition; + private final BsonDocument testCase; + private final TestCaseType testCaseType; - public GenericBsonTest(final String description, final BsonDocument definition) { - this.definition = definition; + public GenericBsonTest(final String description, final BsonDocument testDefinition, final BsonDocument testCase, + final TestCaseType testCaseType) { + this.testDefinition = testDefinition; + this.testCase = testCase; + this.testCaseType = testCaseType; } @Test public void shouldPassAllOutcomes() { - for (BsonValue curValue : definition.getArray("documents")) { - BsonDocument curDocument = curValue.asDocument(); - try { - validateDecoding(curDocument); - validateEncoding(curDocument); - } catch (BsonSerializationException e) { - // should not throw unless the test case has an error string - if (!curDocument.containsKey("error")) { - fail(curDocument.getString("error").getValue() + ": " + e.getMessage()); + switch (testCaseType) { + case VALID: + runValid(); + break; + case DECODE_ERROR: + runDecodeError(); + break; + case PARSE_ERROR: + runParseError(); + break; + default: + throw new IllegalArgumentException(format("Unsupported test case type %s", testCaseType)); + } + } + + private void runValid() { + String description = testCase.getString("description").getValue(); + String canonicalBsonHex = testCase.getString("canonical_bson").getValue().toUpperCase(); + String degenerateBsonHex = testCase.getString("degenerate_bson", new BsonString("")).getValue().toUpperCase(); + String canonicalJson = replaceUnicodeEscapes(testCase.getString("canonical_extjson").getValue()); + String relaxedJson = replaceUnicodeEscapes(testCase.getString("relaxed_extjson", new BsonString("")).getValue()); + String degenerateJson = replaceUnicodeEscapes(testCase.getString("degenerate_extjson", new BsonString("")).getValue()); + boolean lossy = testCase.getBoolean("lossy", new BsonBoolean(false)).getValue(); + + BsonDocument decodedDocument = decodeToDocument(canonicalBsonHex, description); + + // native_to_bson( bson_to_native(cB) ) = cB + assertEquals(format("Failed to create expected BSON for document with description '%s'", description), + canonicalBsonHex, encodeToHex(decodedDocument)); + + JsonWriterSettings canonicalJsonWriterSettings = JsonWriterSettings.builder().outputMode(JsonMode.EXTENDED).build(); + JsonWriterSettings relaxedJsonWriterSettings = JsonWriterSettings.builder().outputMode(JsonMode.RELAXED).build(); + + if (!canonicalJson.isEmpty()) { + // native_to_canonical_extended_json( bson_to_native(cB) ) = cEJ + assertEquals(format("Failed to create expected canonical JSON for document with description '%s'", description), + stripWhiteSpace(canonicalJson), stripWhiteSpace(decodedDocument.toJson(canonicalJsonWriterSettings))); + + // native_to_canonical_extended_json( json_to_native(cEJ) ) = cEJ + BsonDocument parsedCanonicalJsonDocument = parse(canonicalJson); + assertEquals("Failed to create expected canonical JSON from parsing canonical JSON", + stripWhiteSpace(canonicalJson), stripWhiteSpace(parsedCanonicalJsonDocument.toJson(canonicalJsonWriterSettings))); + + if (!lossy) { + // native_to_bson( json_to_native(cEJ) ) = cB + assertEquals("Failed to create expected canonical BSON from parsing canonical JSON", + canonicalBsonHex, encodeToHex(parsedCanonicalJsonDocument)); + } + } + + if (!relaxedJson.isEmpty()) { + // native_to_relaxed_extended_json( bson_to_native(cB) ) = rEJ + assertEquals(format("Failed to create expected relaxed JSON for document with description '%s'", description), + stripWhiteSpace(relaxedJson), stripWhiteSpace(decodedDocument.toJson(relaxedJsonWriterSettings))); + + // native_to_relaxed_extended_json( json_to_native(rEJ) ) = rEJ + assertEquals("Failed to create expected relaxed JSON from parsing relaxed JSON", stripWhiteSpace(relaxedJson), + stripWhiteSpace(parse(relaxedJson).toJson(relaxedJsonWriterSettings))); + } + + if (!degenerateJson.isEmpty()) { + // native_to_bson( json_to_native(dEJ) ) = cB + assertEquals("Failed to create expected canonical BSON from parsing canonical JSON", + canonicalBsonHex, encodeToHex(parse(degenerateJson))); + } + + if (!degenerateBsonHex.isEmpty()) { + BsonDocument decodedDegenerateDocument = decodeToDocument(degenerateBsonHex, description); + // native_to_bson( bson_to_native(dB) ) = cB + assertEquals(format("Failed to create expected canonical BSON from degenerate BSON for document with description " + + "'%s'", description), canonicalBsonHex, encodeToHex(decodedDegenerateDocument)); + } + } + + // The corpus escapes all non-ascii characters, but JSONWriter does not. This method converts the Unicode escape sequence into its + // regular UTF encoding in order to match the JSONWriter behavior. + private String replaceUnicodeEscapes(final String json) { + try { + StringReader reader = new StringReader(json); + StringWriter writer = new StringWriter(); + int cur; + while ((cur = reader.read()) != -1) { + char curChar = (char) cur; + if (curChar != '\\') { + writer.write(curChar); + continue; + } + + char nextChar = (char) reader.read(); + if (nextChar != 'u') { + writer.write(curChar); + writer.write(nextChar); + continue; + } + char[] codePointString = new char[4]; + reader.read(codePointString); + char escapedChar = (char) Integer.parseInt(new String(codePointString), 16); + if (shouldEscapeCharacter(escapedChar)) { + writer.write("\\u" + new String(codePointString)); + } else { + writer.write(escapedChar); } - } catch (RuntimeException e) { - fail("Threw RuntimeException instead of BsonSerializationException: " + e.toString()); + } + return writer.toString(); + } catch (IOException e) { + throw new RuntimeException("impossible"); + } + } + + // copied from JsonWriter... + private boolean shouldEscapeCharacter(final char escapedChar) { + switch (Character.getType(escapedChar)) { + case Character.UPPERCASE_LETTER: + case Character.LOWERCASE_LETTER: + case Character.TITLECASE_LETTER: + case Character.OTHER_LETTER: + case Character.DECIMAL_DIGIT_NUMBER: + case Character.LETTER_NUMBER: + case Character.OTHER_NUMBER: + case Character.SPACE_SEPARATOR: + case Character.CONNECTOR_PUNCTUATION: + case Character.DASH_PUNCTUATION: + case Character.START_PUNCTUATION: + case Character.END_PUNCTUATION: + case Character.INITIAL_QUOTE_PUNCTUATION: + case Character.FINAL_QUOTE_PUNCTUATION: + case Character.OTHER_PUNCTUATION: + case Character.MATH_SYMBOL: + case Character.CURRENCY_SYMBOL: + case Character.MODIFIER_SYMBOL: + case Character.OTHER_SYMBOL: + return false; + default: + return true; } } - private String validateDecoding(final BsonDocument curDocument) { - BsonDocument expectedDecodedDocument = curDocument.getDocument("decoded", null); - String encoded = curDocument.getString("encoded").getValue(); - - if (encoded != null) { - ByteBuffer byteBuffer = ByteBuffer.wrap(DatatypeConverter.parseHexBinary(encoded)); - BsonDocument actualDecodedDocument = new BsonDocumentCodec().decode(new BsonBinaryReader(byteBuffer), - DecoderContext.builder().build()); - - if (byteBuffer.hasRemaining()) { - fail("Should have consumed all bytes, but " + byteBuffer.remaining() + " still remain in the buffer"); - } else if (expectedDecodedDocument == null) { - fail("Decoding of '" + encoded + "' should have failed with error '" - + curDocument.getString("error").getValue() - + "' but succeeded with " + actualDecodedDocument.toJson()); - } else { - assertEquals("Failed to decode to expected document", expectedDecodedDocument, actualDecodedDocument); + private BsonDocument decodeToDocument(final String subjectHex, final String description) { + ByteBuffer byteBuffer = ByteBuffer.wrap(Hex.decode(subjectHex)); + BsonDocument actualDecodedDocument = new BsonDocumentCodec().decode(new BsonBinaryReader(byteBuffer), + DecoderContext.builder().build()); + + if (byteBuffer.hasRemaining()) { + throw new BsonSerializationException(format("Should have consumed all bytes, but " + byteBuffer.remaining() + + " still remain in the buffer for document with description ", + description)); + } + return actualDecodedDocument; + } + + private String encodeToHex(final BsonDocument decodedDocument) { + BasicOutputBuffer outputBuffer = new BasicOutputBuffer(); + new BsonDocumentCodec().encode(new BsonBinaryWriter(outputBuffer), decodedDocument, EncoderContext.builder().build()); + return Hex.encode(outputBuffer.toByteArray()); + } + + private void runDecodeError() { + try { + String description = testCase.getString("description").getValue(); + throwIfValueIsStringContainingReplacementCharacter(description); + fail(format("Should have failed parsing for subject with description '%s'", description)); + } catch (BsonSerializationException e) { + // all good + } + } + + private void runParseError() { + String description = testCase.getString("description").getValue(); + + Assume.assumeFalse(IGNORED_PARSE_ERRORS.contains(description)); + + String str = testCase.getString("string").getValue(); + + String testDefinitionDescription = testDefinition.getString("description").getValue(); + if (testDefinitionDescription.startsWith("Decimal128")) { + try { + Decimal128.parse(str); + fail(format("Should fail to parse '" + str + "' with description '%s'", description + "'")); + } catch (NumberFormatException e) { + // all good + } + } else if (testDefinitionDescription.startsWith("Top-level")) { + try { + parse(str); + fail("Should fail to parse JSON '" + str + "' with description '" + description + "'"); + } catch (JsonParseException e) { + // all good + } catch (BsonInvalidOperationException e) { + if (!description.equals("Bad $code with $scope (scope is number, not doc)")) { + fail("Should throw JsonParseException for '" + str + "' with description '" + description + "'"); + } + // all good } + } else { + fail("Unrecognized test definition description: " + testDefinitionDescription); } - return encoded; } - private void validateEncoding(final BsonDocument curDocument) { - BsonDocument expectedDecodedDocument = curDocument.getDocument("decoded", null); - if (expectedDecodedDocument != null && !curDocument.getBoolean("decodeOnly", BsonBoolean.FALSE).getValue()) { - BasicOutputBuffer buffer = new BasicOutputBuffer(); - new BsonDocumentCodec().encode(new BsonBinaryWriter(buffer), expectedDecodedDocument, EncoderContext.builder().build()); - assertEquals("Failed to properly encode", curDocument.getString("encoded").getValue(), - DatatypeConverter.printHexBinary(buffer.toByteArray())); + + + // TODO: Working around the fact that the Java driver doesn't report an error for invalid UTF-8, but rather replaces the invalid + // sequence with the replacement character + private void throwIfValueIsStringContainingReplacementCharacter(final String description) { + BsonDocument decodedDocument = decodeToDocument(testCase.getString("bson").getValue(), description); + String testKey = decodedDocument.keySet().iterator().next(); + + if (decodedDocument.containsKey(testKey)) { + String decodedString = null; + if (decodedDocument.get(testKey).isString()) { + decodedString = decodedDocument.getString(testKey).getValue(); + } + if (decodedDocument.get(testKey).isDBPointer()) { + decodedString = decodedDocument.get(testKey).asDBPointer().getNamespace(); + } + if (decodedString != null && decodedString.contains(Charset.forName("UTF-8").newDecoder().replacement())) { + throw new BsonSerializationException("String contains replacement character"); + } + } } + @Parameterized.Parameters(name = "{0}") public static Collection data() throws URISyntaxException, IOException { List data = new ArrayList(); for (File file : JsonPoweredTestHelper.getTestFiles("/bson")) { BsonDocument testDocument = JsonPoweredTestHelper.getTestDocument(file); - data.add(new Object[]{testDocument.getString("description").getValue(), testDocument}); + for (BsonValue curValue : testDocument.getArray("valid", new BsonArray())) { + BsonDocument testCaseDocument = curValue.asDocument(); + data.add(new Object[]{createTestCaseDescription(testDocument, testCaseDocument, "valid"), testDocument, testCaseDocument, + TestCaseType.VALID}); + } + + for (BsonValue curValue : testDocument.getArray("decodeErrors", new BsonArray())) { + BsonDocument testCaseDocument = curValue.asDocument(); + data.add(new Object[]{createTestCaseDescription(testDocument, testCaseDocument, "decodeError"), testDocument, + testCaseDocument, TestCaseType.DECODE_ERROR}); + } + for (BsonValue curValue : testDocument.getArray("parseErrors", new BsonArray())) { + BsonDocument testCaseDocument = curValue.asDocument(); + data.add(new Object[]{createTestCaseDescription(testDocument, testCaseDocument, "parseError"), testDocument, + testCaseDocument, TestCaseType.PARSE_ERROR}); + } } return data; } + + private static String createTestCaseDescription(final BsonDocument testDocument, final BsonDocument testCaseDocument, + final String testCaseType) { + return testDocument.getString("description").getValue() + + "[" + testCaseType + "]" + + ": " + testCaseDocument.getString("description").getValue(); + } + + private String stripWhiteSpace(final String json) { + return json.replace(" ", ""); + } } diff --git a/driver/src/test/unit/org/bson/LazyBSONDecoderTest.java b/bson/src/test/unit/org/bson/LazyBSONDecoderTest.java similarity index 98% rename from driver/src/test/unit/org/bson/LazyBSONDecoderTest.java rename to bson/src/test/unit/org/bson/LazyBSONDecoderTest.java index 715f146cf6b..8e7679a8fb6 100644 --- a/driver/src/test/unit/org/bson/LazyBSONDecoderTest.java +++ b/bson/src/test/unit/org/bson/LazyBSONDecoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/test/unit/org/bson/LazyBSONListTest.java b/bson/src/test/unit/org/bson/LazyBSONListTest.java similarity index 97% rename from driver/src/test/unit/org/bson/LazyBSONListTest.java rename to bson/src/test/unit/org/bson/LazyBSONListTest.java index 6c3ca731e47..eb775c48f5b 100644 --- a/driver/src/test/unit/org/bson/LazyBSONListTest.java +++ b/bson/src/test/unit/org/bson/LazyBSONListTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -@SuppressWarnings("rawtypes") +@SuppressWarnings({"rawtypes", "deprecation"}) public class LazyBSONListTest { private LazyBSONList encodeAndExtractList(final List list) { BSONObject document = new BasicBSONObject("l", list); diff --git a/bson/src/test/unit/org/bson/LazyBSONObjectSpecification.groovy b/bson/src/test/unit/org/bson/LazyBSONObjectSpecification.groovy new file mode 100644 index 00000000000..0ec23b66c21 --- /dev/null +++ b/bson/src/test/unit/org/bson/LazyBSONObjectSpecification.groovy @@ -0,0 +1,361 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import org.bson.types.BSONTimestamp +import org.bson.types.Binary +import org.bson.types.Code +import org.bson.types.CodeWScope +import org.bson.types.Decimal128 +import org.bson.types.MaxKey +import org.bson.types.MinKey +import org.bson.types.ObjectId +import org.bson.types.Symbol +import spock.lang.Specification +import spock.lang.Unroll + +import java.util.regex.Pattern + +import static org.bson.BsonHelper.toBson +import static org.bson.BsonHelper.valuesOfEveryType +import static org.bson.BsonType.SYMBOL +import static org.bson.BsonType.UNDEFINED + +@SuppressWarnings(['LineLength']) +class LazyBSONObjectSpecification extends Specification { + + def setupSpec() { + Map.metaClass.bitwiseNegate = { new BasicBSONObject(delegate) } + Pattern.metaClass.equals = { Pattern other -> + delegate.pattern() == other.pattern() && delegate.flags() == other.flags() + } + } + + @Unroll + def 'should read #type'() { + def lazyBSONObject = new LazyBSONObject(bytes as byte[], new LazyBSONCallback()) + given: + + expect: + value == lazyBSONObject.get('f') + lazyBSONObject.keySet().contains('f') + + where: + value | bytes + -1.01 | [16, 0, 0, 0, 1, 102, 0, 41, 92, -113, -62, -11, 40, -16, -65, 0] + Float.MIN_VALUE | [16, 0, 0, 0, 1, 102, 0, 0, 0, 0, 0, 0, 0, -96, 54, 0] + Double.MAX_VALUE | [16, 0, 0, 0, 1, 102, 0, -1, -1, -1, -1, -1, -1, -17, 127, 0] + 0.0 | [16, 0, 0, 0, 1, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + '' | [13, 0, 0, 0, 2, 102, 0, 1, 0, 0, 0, 0, 0] + 'danke' | [18, 0, 0, 0, 2, 102, 0, 6, 0, 0, 0, 100, 97, 110, 107, 101, 0, 0] + ',+\\\"<>;[]{}@#$%^&*()+_' | [35, 0, 0, 0, 2, 102, 0, 23, 0, 0, 0, 44, 43, 92, 34, 60, 62, 59, 91, 93, 123, 125, 64, 35, 36, 37, 94, 38, 42, 40, 41, 43, 95, 0, 0] + 'a\u00e9\u3042\u0430\u0432\u0431\u0434' | [27, 0, 0, 0, 2, 102, 0, 15, 0, 0, 0, 97, -61, -87, -29, -127, -126, -48, -80, -48, -78, -48, -79, -48, -76, 0, 0] + new LazyBSONObject([5, 0, 0, 0, 0] as byte[], new LazyBSONCallback()) | [13, 0, 0, 0, 3, 102, 0, 5, 0, 0, 0, 0, 0] + [] | [13, 0, 0, 0, 4, 102, 0, 5, 0, 0, 0, 0, 0] + [1, 2, 3] as int[] | [34, 0, 0, 0, 4, 102, 0, 26, 0, 0, 0, 16, 48, 0, 1, 0, 0, 0, 16, 49, 0, 2, 0, 0, 0, 16, 50, 0, 3, 0, 0, 0, 0, 0] + [[]] | [21, 0, 0, 0, 4, 102, 0, 13, 0, 0, 0, 4, 48, 0, 5, 0, 0, 0, 0, 0, 0] + new Binary((byte) 0x01, (byte[]) [115, 116, 11]) | [16, 0, 0, 0, 5, 102, 0, 3, 0, 0, 0, 1, 115, 116, 11, 0] + new Binary((byte) 0x03, (byte[]) [115, 116, 11]) | [16, 0, 0, 0, 5, 102, 0, 3, 0, 0, 0, 3, 115, 116, 11, 0] + new Binary((byte) 0x04, (byte[]) [115, 116, 11]) | [16, 0, 0, 0, 5, 102, 0, 3, 0, 0, 0, 4, 115, 116, 11, 0] + UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + [13, 12] as byte[] | [15, 0, 0, 0, 5, 102, 0, 2, 0, 0, 0, 0, 13, 12, 0] + [102, 111, 111] as byte[] | [16, 0, 0, 0, 5, 102, 0, 3, 0, 0, 0, 0, 102, 111, 111, 0] + new ObjectId('50d3332018c6a1d8d1662b61') | [20, 0, 0, 0, 7, 102, 0, 80, -45, 51, 32, 24, -58, -95, -40, -47, 102, 43, 97, 0] + true | [9, 0, 0, 0, 8, 102, 0, 1, 0] + false | [9, 0, 0, 0, 8, 102, 0, 0, 0] + new Date(582163200) | [16, 0, 0, 0, 9, 102, 0, 0, 27, -77, 34, 0, 0, 0, 0, 0] + null | [8, 0, 0, 0, 10, 102, 0, 0] + null | [8, 0, 0, 0, 6, 102, 0, 0] + Pattern.compile('[a]*', Pattern.CASE_INSENSITIVE) | [15, 0, 0, 0, 11, 102, 0, 91, 97, 93, 42, 0, 105, 0, 0] + new Code('var i = 0') | [22, 0, 0, 0, 13, 102, 0, 10, 0, 0, 0, 118, 97, 114, 32, 105, 32, 61, 32, 48, 0, 0] + new Symbol('c') | [14, 0, 0, 0, 14, 102, 0, 2, 0, 0, 0, 99, 0, 0] + new CodeWScope('i++', ~['x': 1]) | [32, 0, 0, 0, 15, 102, 0, 24, 0, 0, 0, 4, 0, 0, 0, 105, 43, 43, 0, 12, 0, 0, 0, 16, 120, 0, 1, 0, 0, 0, 0, 0] + -12 | [12, 0, 0, 0, 16, 102, 0, -12, -1, -1, -1, 0] + Integer.MIN_VALUE | [12, 0, 0, 0, 16, 102, 0, 0, 0, 0, -128, 0] + 0 | [12, 0, 0, 0, 16, 102, 0, 0, 0, 0, 0, 0] + new BSONTimestamp(123999401, 44332) | [16, 0, 0, 0, 17, 102, 0, 44, -83, 0, 0, -87, 20, 100, 7, 0] + Long.MAX_VALUE | [16, 0, 0, 0, 18, 102, 0, -1, -1, -1, -1, -1, -1, -1, 127, 0] + new MinKey() | [8, 0, 0, 0, -1, 102, 0, 0] + new MaxKey() | [8, 0, 0, 0, 127, 102, 0, 0] + Decimal128.parse('0E-6176') | [24, 0, 0, 0, 19, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + type = BsonType.findByValue(bytes[4]) + } + + @Unroll + def 'should read value of #value'() { + given: + def bsonDocument = new BsonDocument('name', value) + def callback = new BasicBSONCallback() + new BasicBSONDecoder().decode(toBson(bsonDocument).array(), callback) + def dbObject = callback.get() as BasicBSONObject + def lazyBSONObject = new LazyBSONObject(toBson(bsonDocument).array(), new LazyBSONCallback()) + + expect: + lazyBSONObject.keySet().contains('name') + + when: + def expectedValue + if (value.bsonType == UNDEFINED) { + expectedValue = null + } else if (value.bsonType == SYMBOL) { + expectedValue = new Symbol(((BsonSymbol) value).getSymbol()) + } else { + expectedValue = dbObject.get('name') + } + + then: + expectedValue == lazyBSONObject.get('name') + + where: + value << valuesOfEveryType() + } + + def 'should have nested items as lazy'() { + given: + byte[] bytes = [ + 53, 0, 0, 0, 4, 97, 0, 26, 0, 0, 0, 16, 48, 0, 1, 0, 0, 0, 16, 49, 0, 2, 0, 0, 0, 16, 50, 0, + 3, 0, 0, 0, 0, 3, 111, 0, 16, 0, 0, 0, 1, 122, 0, -102, -103, -103, -103, -103, -103, -71, 63, 0, 0 + ]; + + when: + LazyBSONObject document = new LazyBSONObject(bytes, new LazyBSONCallback()) + + then: + document.get('a') instanceof LazyBSONList + document.get('o') instanceof LazyBSONObject + } + + def 'should not understand DBRefs'() { + given: + byte[] bytes = [ + 44, 0, 0, 0, 3, 102, 0, 36, 0, 0, 0, 2, 36, 114, 101, 102, + 0, 4, 0, 0, 0, 97, 46, 98, 0, 7, 36, 105, 100, 0, 18, 52, + 86, 120, -112, 18, 52, 86, 120, -112, 18, 52, 0, 0, + ] + + when: + LazyBSONObject document = new LazyBSONObject(bytes, new LazyBSONCallback()) + + then: + document.get('f') instanceof LazyBSONObject + document.get('f').keySet() == ['$ref', '$id'] as Set + } + + def 'should retain fields order'() { + given: + byte[] bytes = [ + 47, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 16, 98, 0, 2, 0, 0, 0, 16, 100, 0, 3, 0, 0, + 0, 16, 99, 0, 4, 0, 0, 0, 16, 101, 0, 5, 0, 0, 0, 16, 48, 0, 6, 0, 0, 0, 0 + ] + + when: + Iterator iterator = new LazyBSONObject(bytes, new LazyBSONCallback()).keySet().iterator() + + then: + iterator.next() == 'a' + iterator.next() == 'b' + iterator.next() == 'd' + iterator.next() == 'c' + iterator.next() == 'e' + iterator.next() == '0' + !iterator.hasNext() + } + + def 'should be able to compare itself to others'() { + given: + byte[] bytes = [ + 39, 0, 0, 0, 3, 97, 0, + 14, 0, 0, 0, 2, 120, 0, 2, 0, 0, 0, 121, 0, 0, + 3, 98, 0, + 14, 0, 0, 0, 2, 120, 0, 2, 0, 0, 0, 121, 0, 0, + 0 + ] + + when: + def bsonObject1 = new LazyBSONObject(bytes, new LazyBSONCallback()) + def bsonObject2 = new LazyBSONObject(bytes, new LazyBSONCallback()) + def bsonObject3 = new LazyBSONObject(bytes, 7, new LazyBSONCallback()) + def bsonObject4 = new LazyBSONObject(bytes, 24, new LazyBSONCallback()) + def bsonObject5 = new LazyBSONObject([14, 0, 0, 0, 2, 120, 0, 2, 0, 0, 0, 121, 0, 0] as byte[], new LazyBSONCallback()) + def bsonObject6 = new LazyBSONObject([5, 0, 0, 0, 0] as byte[], new LazyBSONCallback()) + + then: + bsonObject1.equals(bsonObject1) + !bsonObject1.equals(null) + !bsonObject1.equals('not equal') + bsonObject1.equals(bsonObject2) + bsonObject3.equals(bsonObject4) + !bsonObject1.equals(bsonObject3) + bsonObject4.equals(bsonObject5) + !bsonObject1.equals(bsonObject6) + + bsonObject1.hashCode() == bsonObject2.hashCode() + bsonObject3.hashCode() == bsonObject4.hashCode() + bsonObject1.hashCode() != bsonObject3.hashCode() + bsonObject4.hashCode() == bsonObject5.hashCode() + bsonObject1.hashCode() != bsonObject6.hashCode() + } + + def 'should return the size of a document'() { + given: + byte[] bytes = [12, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 0] + + when: + LazyBSONObject document = new LazyBSONObject(bytes, new LazyBSONCallback()) + + then: + document.getBSONSize() == 12 + } + + def 'should understand that object is empty'() { + given: + byte[] bytes = [5, 0, 0, 0, 0] + + when: + LazyBSONObject document = new LazyBSONObject(bytes, new LazyBSONCallback()) + + then: + document.isEmpty() + } + + def 'should implement Map.keySet()'() { + given: + byte[] bytes = [16, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 8, 98, 0, 1, 0] + + when: + LazyBSONObject document = new LazyBSONObject(bytes, new LazyBSONCallback()) + + then: + document.containsField('a') + !document.containsField('z') + document.get('z') == null + document.keySet() == ['a', 'b'] as Set + } + + def 'should implement Map.entrySet()'() { + given: + byte[] bytes = [16, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 8, 98, 0, 1, 0] + LazyBSONObject document = new LazyBSONObject(bytes, new LazyBSONCallback()) + + when: + def entrySet = document.entrySet() + + then: + entrySet.size() == 2 + !entrySet.isEmpty() + entrySet.contains(new AbstractMap.SimpleImmutableEntry('a', 1)) + !entrySet.contains(new AbstractMap.SimpleImmutableEntry('a', 2)) + entrySet.containsAll([new AbstractMap.SimpleImmutableEntry('a', 1), new AbstractMap.SimpleImmutableEntry('b', true)]) + !entrySet.containsAll([new AbstractMap.SimpleImmutableEntry('a', 1), new AbstractMap.SimpleImmutableEntry('b', false)]) + entrySet.toArray() == [new AbstractMap.SimpleImmutableEntry('a', 1), new AbstractMap.SimpleImmutableEntry('b', true)].toArray() + entrySet.toArray(new Map.Entry[2]) == + [new AbstractMap.SimpleImmutableEntry('a', 1), new AbstractMap.SimpleImmutableEntry('b', true)].toArray() + + when: + def iterator = entrySet.iterator() + + then: + iterator.hasNext() + iterator.next() == new AbstractMap.SimpleImmutableEntry('a', 1) + iterator.hasNext() + iterator.next() == new AbstractMap.SimpleImmutableEntry('b', true) + !iterator.hasNext() + + when: + entrySet.add(new AbstractMap.SimpleImmutableEntry('key', null)) + + then: + thrown(UnsupportedOperationException) + + when: + entrySet.addAll([new AbstractMap.SimpleImmutableEntry('key', null)]) + + then: + thrown(UnsupportedOperationException) + + when: + entrySet.clear() + + then: + thrown(UnsupportedOperationException) + + when: + entrySet.remove(new AbstractMap.SimpleImmutableEntry('key', null)) + + then: + thrown(UnsupportedOperationException) + + when: + entrySet.removeAll([new AbstractMap.SimpleImmutableEntry('key', null)]) + + then: + thrown(UnsupportedOperationException) + + when: + entrySet.retainAll([new AbstractMap.SimpleImmutableEntry('key', null)]) + + then: + thrown(UnsupportedOperationException) + } + + def 'should throw on modification'() { + given: + LazyBSONObject document = new LazyBSONObject( + [16, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 8, 98, 0, 1, 0] as byte[], + new LazyBSONCallback() + ) + + when: + document.keySet().add('c') + + then: + thrown(UnsupportedOperationException) + + when: + document.put('c', 2) + + then: + thrown(UnsupportedOperationException) + + when: + document.removeField('a') + + then: + thrown(UnsupportedOperationException) + + when: + document.toMap().put('a', 22) + + then: + thrown(UnsupportedOperationException) + } + + def 'should pipe to stream'() { + given: + byte[] bytes = [16, 0, 0, 0, 16, 97, 0, 1, 0, 0, 0, 8, 98, 0, 1, 0]; + LazyBSONObject document = new LazyBSONObject(bytes, new LazyBSONCallback()) + ByteArrayOutputStream baos = new ByteArrayOutputStream() + + when: + document.pipe(baos) + + then: + bytes == baos.toByteArray() + } +} diff --git a/bson/src/test/unit/org/bson/LimitedLookaheadMarkSpecification.groovy b/bson/src/test/unit/org/bson/LimitedLookaheadMarkSpecification.groovy index f34250ced9a..dd025c13f2f 100644 --- a/bson/src/test/unit/org/bson/LimitedLookaheadMarkSpecification.groovy +++ b/bson/src/test/unit/org/bson/LimitedLookaheadMarkSpecification.groovy @@ -1,19 +1,17 @@ /* + * Copyright 2008-present MongoDB, Inc. * - * * Copyright (c) 2008-2014 MongoDB, Inc. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.bson @@ -25,6 +23,7 @@ import org.bson.json.JsonWriter import spock.lang.Specification @SuppressWarnings('UnnecessaryObjectReferences') +@SuppressWarnings('deprecation') class LimitedLookaheadMarkSpecification extends Specification { def 'should throw if mark without resetting previous mark'(BsonWriter writer) { @@ -242,7 +241,197 @@ class LimitedLookaheadMarkSpecification extends Specification { ] } - def 'should peek binary subtype'(BsonWriter writer) { + def 'Lookahead should work at various states with Mark'(BsonWriter writer, boolean useAlternateReader) { + given: + writer.with { + writeStartDocument() + writeInt64('int64', 52L) + writeStartArray('array') + writeInt32(1) + writeInt64(2L) + writeStartArray() + writeInt32(3) + writeInt32(4) + writeEndArray() + writeStartDocument() + writeInt32('a', 5) + writeEndDocument() + writeNull() + writeEndArray() + writeStartDocument('document') + writeInt32('a', 6) + writeEndDocument() + writeEndDocument() + } + + + when: + BsonReader reader + BsonReaderMark mark + if (writer instanceof BsonDocumentWriter) { + reader = new BsonDocumentReader(writer.document) + } else if (writer instanceof BsonBinaryWriter) { + BasicOutputBuffer buffer = (BasicOutputBuffer) writer.getBsonOutput(); + reader = new BsonBinaryReader(new ByteBufferBsonInput(buffer.getByteBuffers().get(0))) + } else if (writer instanceof JsonWriter) { + if (useAlternateReader) { + reader = new JsonReader(new InputStreamReader(new ByteArrayInputStream(writer.writer.toString().getBytes()))) + } else { + reader = new JsonReader(writer.writer.toString()) + } + } + + reader.readStartDocument() + // mark beginning of document * 1 + mark = reader.getMark() + + then: + reader.readName() == 'int64' + reader.readInt64() == 52L + reader.readStartArray() + + when: + // reset to beginning of document * 2 + mark.reset() + // mark beginning of document * 2 + mark = reader.getMark() + + then: + reader.readName() == 'int64' + reader.readInt64() == 52L + + when: + // make sure it's possible to reset to a mark after getting a new mark + reader.getMark() + // reset to beginning of document * 3 + mark.reset() + // mark beginning of document * 3 + mark = reader.getMark() + + then: + reader.readName() == 'int64' + reader.readInt64() == 52L + reader.readName() == 'array' + reader.readStartArray() + reader.readInt32() == 1 + reader.readInt64() == 2 + reader.readStartArray() + reader.readInt32() == 3 + reader.readInt32() == 4 + reader.readEndArray() + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 5 + reader.readEndDocument() + reader.readNull() + reader.readEndArray() + reader.readName() == 'document' + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 6 + reader.readEndDocument() + reader.readEndDocument() + + when: + // read entire document, reset to beginning + mark.reset() + + then: + reader.readName() == 'int64' + reader.readInt64() == 52L + reader.readName() == 'array' + + when: + // mark in outer-document * 1 + mark = reader.getMark() + + then: + reader.readStartArray() + reader.readInt32() == 1 + reader.readInt64() == 2 + reader.readStartArray() + + when: + // reset in sub-document * 1 + mark.reset() + // mark in outer-document * 2 + mark = reader.getMark() + + then: + reader.readStartArray() + reader.readInt32() == 1 + reader.readInt64() == 2 + reader.readStartArray() + reader.readInt32() == 3 + + when: + // reset in sub-document * 2 + mark.reset() + + then: + reader.readStartArray() + reader.readInt32() == 1 + reader.readInt64() == 2 + reader.readStartArray() + reader.readInt32() == 3 + reader.readInt32() == 4 + + when: + // mark in sub-document * 1 + mark = reader.getMark() + + then: + reader.readEndArray() + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 5 + reader.readEndDocument() + reader.readNull() + reader.readEndArray() + + when: + // reset in outer-document * 1 + mark.reset() + // mark in sub-document * 2 + mark = reader.getMark() + + then: + reader.readEndArray() + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 5 + reader.readEndDocument() + reader.readNull() + reader.readEndArray() + + when: + // reset in out-document * 2 + mark.reset() + + then: + reader.readEndArray() + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 5 + reader.readEndDocument() + reader.readNull() + reader.readEndArray() + reader.readName() == 'document' + reader.readStartDocument() + reader.readName() == 'a' + reader.readInt32() == 6 + reader.readEndDocument() + reader.readEndDocument() + + where: + writer | useAlternateReader + new BsonDocumentWriter(new BsonDocument()) | false + new BsonBinaryWriter(new BasicOutputBuffer()) | false + new JsonWriter(new StringWriter()) | false + new JsonWriter(new StringWriter()) | true + } + + def 'should peek binary subtype and size'(BsonWriter writer) { given: writer.with { writeStartDocument() @@ -265,12 +454,14 @@ class LimitedLookaheadMarkSpecification extends Specification { reader.readStartDocument() reader.readName() def subType = reader.peekBinarySubType() + def size = reader.peekBinarySize() def binary = reader.readBinaryData() def longValue = reader.readInt64('int64') reader.readEndDocument() then: subType == BsonBinarySubType.UUID_LEGACY.value + size == 16 binary == new BsonBinary(BsonBinarySubType.UUID_LEGACY, new byte[16]) longValue == 52L @@ -281,4 +472,4 @@ class LimitedLookaheadMarkSpecification extends Specification { new JsonWriter(new StringWriter()) ] } -} \ No newline at end of file +} diff --git a/bson/src/test/unit/org/bson/RawBsonArraySpecification.groovy b/bson/src/test/unit/org/bson/RawBsonArraySpecification.groovy new file mode 100644 index 00000000000..27a7dcbbaa7 --- /dev/null +++ b/bson/src/test/unit/org/bson/RawBsonArraySpecification.groovy @@ -0,0 +1,402 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson + +import org.bson.codecs.BsonDocumentCodec +import spock.lang.Specification + +import java.nio.ByteOrder + +import static java.util.Arrays.asList +import static util.GroovyHelpers.areEqual + +class RawBsonArraySpecification extends Specification { + + static BsonArray emptyBsonArray = new BsonArray() + static RawBsonArray emptyRawBsonArray = new RawBsonDocument(new BsonDocument('a', emptyBsonArray), new BsonDocumentCodec()).get('a') + static BsonArray bsonArray = new BsonArray(asList(new BsonInt32(1), new BsonInt32(2), new BsonDocument('x', BsonBoolean.TRUE), + new BsonArray(asList(new BsonDocument('y', BsonBoolean.FALSE), new BsonArray(asList(new BsonInt32(1))))))) + + def 'constructors should throw if parameters are invalid'() { + when: + new RawBsonArray(null) + + then: + thrown(IllegalArgumentException) + + when: + new RawBsonArray(null, 0, 5) + + then: + thrown(IllegalArgumentException) + + when: + new RawBsonArray(new byte[5], -1, 5) + + then: + thrown(IllegalArgumentException) + + when: + new RawBsonArray(new byte[5], 5, 5) + + then: + thrown(IllegalArgumentException) + + when: + new RawBsonArray(new byte[5], 0, 0) + + then: + thrown(IllegalArgumentException) + + when: + new RawBsonArray(new byte[10], 6, 5) + + then: + thrown(IllegalArgumentException) + } + + def 'byteBuffer should contain the correct bytes'() { + when: + def byteBuf = rawBsonArray.getByteBuffer() + + then: + rawBsonArray == bsonArray + byteBuf.asNIO().order() == ByteOrder.LITTLE_ENDIAN + byteBuf.remaining() == 66 + + when: + def actualBytes = new byte[66] + byteBuf.get(actualBytes) + + then: + actualBytes == getBytesFromBsonArray() + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'contains should find existing values'() { + expect: + rawBsonArray.contains( bsonArray.get(0) ) + rawBsonArray.contains( bsonArray.get(1) ) + rawBsonArray.contains( bsonArray.get(2) ) + rawBsonArray.contains( bsonArray.get(3) ) + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'containsAll should return true if contains all'() { + expect: + rawBsonArray.containsAll(bsonArray.getValues()) + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'should return RawBsonDocument for sub documents and RawBsonArray for arrays'() { + expect: + rawBsonArray.get(0) instanceof BsonInt32 + rawBsonArray.get(1) instanceof BsonInt32 + rawBsonArray.get(2) instanceof RawBsonDocument + rawBsonArray.get(3) instanceof RawBsonArray + rawBsonArray.get(3).asArray().get(0) instanceof RawBsonDocument + rawBsonArray.get(3).asArray().get(1) instanceof RawBsonArray + + and: + rawBsonArray.get(2).getBoolean('x').value + !rawBsonArray.get(3).asArray().get(0).asDocument().getBoolean('y').value + rawBsonArray.get(3).asArray().get(1).asArray().get(0).asInt32().value == 1 + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + + def 'get should throw if index out of bounds'() { + when: + rawBsonArray.get(-1) + + then: + thrown(IndexOutOfBoundsException) + + when: + rawBsonArray.get(5) + + then: + thrown(IndexOutOfBoundsException) + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'isEmpty should return false when the BsonArray is not empty'() { + expect: + !rawBsonArray.isEmpty() + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'isEmpty should return true when the BsonArray is empty'() { + expect: + emptyRawBsonArray.isEmpty() + } + + def 'should get correct size when the BsonArray is empty'() { + expect: + emptyRawBsonArray.size() == 0 + } + + def 'should get correct values set when the BsonArray is empty'() { + expect: + emptyRawBsonArray.getValues().isEmpty() + } + + def 'should get correct size'() { + expect: + createRawBsonArrayFromBsonArray().size() == 4 + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'should get correct values set'() { + expect: + rawBsonArray.getValues() == bsonArray.getValues() + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'all write methods should throw UnsupportedOperationException'() { + given: + def rawBsonArray = createRawBsonArrayFromBsonArray() + + when: + rawBsonArray.clear() + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.add(BsonNull.VALUE) + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.add(1, BsonNull.VALUE) + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.addAll([BsonNull.VALUE]) + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.addAll(1, [BsonNull.VALUE]) + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.remove(BsonNull.VALUE) + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.remove(1) + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.removeAll([BsonNull.VALUE]) + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.retainAll([BsonNull.VALUE]) + + then: + thrown(UnsupportedOperationException) + + when: + rawBsonArray.set(0, BsonNull.VALUE) + + then: + thrown(UnsupportedOperationException) + } + + def 'should find the indexOf a value'() { + expect: + rawBsonArray.indexOf(bsonArray.get(2)) == 2 + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'should find the lastIndexOf a value'() { + when: + RawBsonArray rawBsonArray = RawBsonDocument.parse('{a: [1, 2, 3, 1]}').get('a') + + then: + rawBsonArray.lastIndexOf(rawBsonArray.get(0)) == 3 + } + + + def 'should return a valid iterator for empty Bson Arrays'() { + when: + def iterator = emptyRawBsonArray.iterator() + + then: + !iterator.hasNext() + !iterator.hasNext() + } + + def 'should return a listIterator'() { + when: + RawBsonArray rawBsonArray = RawBsonDocument.parse('{a: [1, 2, 3, 1]}').get('a') + + then: + rawBsonArray.listIterator().toList() == rawBsonArray.getValues() + } + + def 'should return a listIterator with index'() { + when: + RawBsonArray rawBsonArray = RawBsonDocument.parse('{a: [1, 2, 3, 1]}').get('a') + + then: + rawBsonArray.listIterator(1).toList() == rawBsonArray.getValues().subList(1, 4) + } + + def 'should iterate forwards and backwards through a list iterator'() { + when: + RawBsonArray rawBsonArray = RawBsonDocument.parse('{a: [1, 2, 3, 4]}').get('a') + def iter = rawBsonArray.listIterator() + + then: + + iter.next() == new BsonInt32(1) + iter.previous() == new BsonInt32(1) + iter.next() == new BsonInt32(1) + iter.next() == new BsonInt32(2) + iter.previous() == new BsonInt32(2) + iter.previous() == new BsonInt32(1) + + when: + iter.previous() + + then: + thrown(NoSuchElementException) + } + + def 'should return a sublist'() { + when: + RawBsonArray rawBsonArray = RawBsonDocument.parse('{a: [1, 2, 3, 1]}').get('a') + + then: + rawBsonArray.subList(2, 3).toList() == rawBsonArray.getValues().subList(2, 3) + } + + def 'hashCode should equal hash code of identical BsonArray'() { + expect: + rawBsonArray.hashCode() == bsonArray.hashCode() + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'equals should equal identical BsonArray'() { + expect: + areEqual(rawBsonArray, bsonArray) + areEqual(bsonArray, rawBsonArray) + areEqual(rawBsonArray, rawBsonArray) + !areEqual(rawBsonArray, emptyRawBsonArray) + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'clone should make a deep copy'() { + when: + RawBsonArray cloned = rawBsonArray.clone() + + then: + !cloned.getByteBuffer().array().is(createRawBsonArrayFromBsonArray().getByteBuffer().array()) + cloned.getByteBuffer().remaining() == rawBsonArray.getByteBuffer().remaining() + cloned == createRawBsonArrayFromBsonArray() + + where: + rawBsonArray << createRawBsonArrayVariants() + } + + def 'should serialize and deserialize'() { + given: + def baos = new ByteArrayOutputStream() + def oos = new ObjectOutputStream(baos) + + when: + oos.writeObject(localRawDocument) + def bais = new ByteArrayInputStream(baos.toByteArray()) + def ois = new ObjectInputStream(bais) + def deserializedDocument = ois.readObject() + + then: + bsonArray == deserializedDocument + + where: + localRawDocument << createRawBsonArrayVariants() + } + + private static List createRawBsonArrayVariants() { + [ + createRawBsonArrayFromBsonArray(), + createRawBsonArrayFromByteArray(), + createRawBsonArrayFromByteArrayOffsetLength() + ] + } + + private static RawBsonArray createRawBsonArrayFromBsonArray() { + (RawBsonArray) new RawBsonDocument(new BsonDocument('a', bsonArray), new BsonDocumentCodec()).get('a') + } + + + private static byte[] getBytesFromBsonArray() { + def byteBuffer = createRawBsonArrayFromBsonArray().byteBuffer + byte[] strippedBytes = new byte[byteBuffer.remaining()] + byteBuffer.get(strippedBytes) + strippedBytes + } + + private static RawBsonArray createRawBsonArrayFromByteArray() { + new RawBsonArray(getBytesFromBsonArray()) + } + + private static RawBsonArray createRawBsonArrayFromByteArrayOffsetLength() { + def strippedBytes = getBytesFromBsonArray() + byte[] unstrippedBytes = new byte[strippedBytes.length + 2] + System.arraycopy(strippedBytes, 0, unstrippedBytes, 1, strippedBytes.length) + new RawBsonArray(unstrippedBytes, 1, strippedBytes.length) + } +} diff --git a/bson/src/test/unit/org/bson/RawBsonDocumentSpecification.groovy b/bson/src/test/unit/org/bson/RawBsonDocumentSpecification.groovy index f85c67aebe2..e4688592e6b 100644 --- a/bson/src/test/unit/org/bson/RawBsonDocumentSpecification.groovy +++ b/bson/src/test/unit/org/bson/RawBsonDocumentSpecification.groovy @@ -1,14 +1,14 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * - * Licensed under the Apache License, Version 2.0 (the 'License'); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, + * 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. @@ -36,12 +36,12 @@ import static util.GroovyHelpers.areEqual class RawBsonDocumentSpecification extends Specification { static emptyDocument = new BsonDocument() - static emptyRawDocument = new RawBsonDocument(emptyDocument, new BsonDocumentCodec()); + static emptyRawDocument = new RawBsonDocument(emptyDocument, new BsonDocumentCodec()) static document = new BsonDocument() .append('a', new BsonInt32(1)) .append('b', new BsonInt32(2)) .append('c', new BsonDocument('x', BsonBoolean.TRUE)) - .append('d', new BsonArray(asList(new BsonDocument('y', BsonBoolean.FALSE), new BsonInt32(1)))) + .append('d', new BsonArray(asList(new BsonDocument('y', BsonBoolean.FALSE), new BsonArray(asList(new BsonInt32(1)))))) def 'constructors should throw if parameters are invalid'() { when: @@ -100,10 +100,10 @@ class RawBsonDocumentSpecification extends Specification { then: rawDocument == document byteBuf.asNIO().order() == ByteOrder.LITTLE_ENDIAN - byteBuf.remaining() == 58 + byteBuf.remaining() == 66 when: - def actualBytes = new byte[58] + def actualBytes = new byte[66] byteBuf.get(actualBytes) then: @@ -161,6 +161,24 @@ class RawBsonDocumentSpecification extends Specification { rawDocument << createRawDocumentVariants() } + def 'should return RawBsonDocument for sub documents and RawBsonArray for arrays'() { + expect: + rawDocument.get('a') instanceof BsonInt32 + rawDocument.get('b') instanceof BsonInt32 + rawDocument.get('c') instanceof RawBsonDocument + rawDocument.get('d') instanceof RawBsonArray + rawDocument.get('d').asArray().get(0) instanceof RawBsonDocument + rawDocument.get('d').asArray().get(1) instanceof RawBsonArray + + and: + rawDocument.getDocument('c').getBoolean('x').value + !rawDocument.get('d').asArray().get(0).asDocument().getBoolean('y').value + rawDocument.get('d').asArray().get(1).asArray().get(0).asInt32().value == 1 + + where: + rawDocument << createRawDocumentVariants() + } + def 'containValue should find an existing value'() { expect: rawDocument.containsValue(document.get('a')) @@ -250,6 +268,33 @@ class RawBsonDocumentSpecification extends Specification { rawDocument << createRawDocumentVariants() } + def 'should get first key'() { + expect: + document.getFirstKey() == 'a' + + where: + rawDocument << createRawDocumentVariants() + } + + def 'getFirstKey should throw NoSuchElementException if the document is empty'() { + when: + emptyRawDocument.getFirstKey() + + then: + thrown(NoSuchElementException) + } + + def 'should create BsonReader'() { + when: + def reader = document.asBsonReader() + + then: + new BsonDocumentCodec().decode(reader, DecoderContext.builder().build()) == document + + cleanup: + reader.close() + } + def 'toJson should return equivalent JSON'() { expect: new RawBsonDocumentCodec().decode(new JsonReader(rawDocument.toJson()), DecoderContext.builder().build()) == document @@ -260,10 +305,10 @@ class RawBsonDocumentSpecification extends Specification { def 'toJson should respect default JsonWriterSettings'() { given: - def writer = new StringWriter(); + def writer = new StringWriter() when: - new BsonDocumentCodec().encode(new JsonWriter(writer), document, EncoderContext.builder().build()); + new BsonDocumentCodec().encode(new JsonWriter(writer), document, EncoderContext.builder().build()) then: writer.toString() == rawDocument.toJson() @@ -275,10 +320,10 @@ class RawBsonDocumentSpecification extends Specification { def 'toJson should respect JsonWriterSettings'() { given: def jsonWriterSettings = new JsonWriterSettings(JsonMode.SHELL) - def writer = new StringWriter(); + def writer = new StringWriter() when: - new RawBsonDocumentCodec().encode(new JsonWriter(writer, jsonWriterSettings), rawDocument, EncoderContext.builder().build()); + new RawBsonDocumentCodec().encode(new JsonWriter(writer, jsonWriterSettings), rawDocument, EncoderContext.builder().build()) then: writer.toString() == rawDocument.toJson(jsonWriterSettings) @@ -381,7 +426,6 @@ class RawBsonDocumentSpecification extends Specification { where: localRawDocument << createRawDocumentVariants() - } private static List createRawDocumentVariants() { @@ -424,7 +468,7 @@ class RawBsonDocumentSpecification extends Specification { class TestEntry implements Map.Entry { - private final String key; + private final String key private BsonValue value TestEntry(String key, BsonValue value) { diff --git a/bson/src/test/unit/org/bson/codecs/AtomicCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/AtomicCodecSpecification.groovy index 56723124fba..df3800f8797 100644 --- a/bson/src/test/unit/org/bson/codecs/AtomicCodecSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/AtomicCodecSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -107,4 +107,4 @@ class AtomicCodecSpecification extends Specification { value.get() == atomicLong.get() } -} \ No newline at end of file +} diff --git a/bson/src/test/unit/org/bson/codecs/AtomicIntegerCodecTest.java b/bson/src/test/unit/org/bson/codecs/AtomicIntegerCodecTest.java new file mode 100644 index 00000000000..e8922aaaf26 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/AtomicIntegerCodecTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; + +public final class AtomicIntegerCodecTest extends CodecTestCase { + + @Test + public void shouldRoundTripAtomicIntegerValues() { + Document original = new Document("a", new AtomicInteger(Integer.MAX_VALUE)); + roundTrip(original, new AtomicIntegerComparator(original)); + + original = new Document("a", new AtomicInteger(Integer.MIN_VALUE)); + roundTrip(original, new AtomicIntegerComparator(original)); + } + + @Test + public void shouldHandleAlternativeNumberValues() { + Document expected = new Document("a", new AtomicInteger(10)); + roundTrip(new Document("a", 10), new AtomicIntegerComparator(expected)); + roundTrip(new Document("a", 10L), new AtomicIntegerComparator(expected)); + roundTrip(new Document("a", 10.00), new AtomicIntegerComparator(expected)); + roundTrip(new Document("a", 9.9999999999999992), new AtomicIntegerComparator(expected)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDoubleValues() { + Document original = new Document("a", 9.9999999999999991); + roundTrip(original, new AtomicIntegerComparator(original)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMinRange() { + roundTrip(new Document("a", Long.MIN_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMaxRange() { + roundTrip(new Document("a", Long.MAX_VALUE)); + } + + @Override + DocumentCodecProvider getDocumentCodecProvider() { + return getSpecificNumberDocumentCodecProvider(AtomicInteger.class); + } + + private class AtomicIntegerComparator implements Comparator { + private final Document expected; + + AtomicIntegerComparator(final Document expected) { + this.expected = expected; + } + + @Override + public void apply(final Document result) { + assertEquals("Codec Round Trip", + expected.get("a", AtomicInteger.class).get(), + result.get("a", AtomicInteger.class).get()); + } + } + +} diff --git a/bson/src/test/unit/org/bson/codecs/AtomicLongCodecTest.java b/bson/src/test/unit/org/bson/codecs/AtomicLongCodecTest.java new file mode 100644 index 00000000000..2321becc1ed --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/AtomicLongCodecTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.Assert.assertEquals; + +public final class AtomicLongCodecTest extends CodecTestCase { + + @Test + public void shouldRoundTripAtomicLongValues() { + Document original = new Document("a", new AtomicLong(Long.MAX_VALUE)); + roundTrip(original, new AtomicLongComparator(original)); + + original = new Document("a", new AtomicLong(Long.MIN_VALUE)); + roundTrip(original, new AtomicLongComparator(original)); + } + + @Test + public void shouldHandleAlternativeNumberValues() { + Document expected = new Document("a", new AtomicLong(10L)); + roundTrip(new Document("a", 10), new AtomicLongComparator(expected)); + roundTrip(new Document("a", 10L), new AtomicLongComparator(expected)); + roundTrip(new Document("a", 10.00), new AtomicLongComparator(expected)); + roundTrip(new Document("a", 9.9999999999999992), new AtomicLongComparator(expected)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDoubleValues() { + Document original = new Document("a", 9.9999999999999991); + roundTrip(original, new AtomicLongComparator(original)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMinRange() { + roundTrip(new Document("a", -Double.MAX_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMaxRange() { + roundTrip(new Document("a", Double.MAX_VALUE)); + } + + @Override + DocumentCodecProvider getDocumentCodecProvider() { + return getSpecificNumberDocumentCodecProvider(AtomicLong.class); + } + + private class AtomicLongComparator implements Comparator { + private final Document expected; + + AtomicLongComparator(final Document expected) { + this.expected = expected; + } + + @Override + public void apply(final Document result) { + assertEquals("Codec Round Trip", + expected.get("a", AtomicLong.class).get(), + result.get("a", AtomicLong.class).get()); + } + } + +} diff --git a/bson/src/test/unit/org/bson/codecs/BigDecimalCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/BigDecimalCodecSpecification.groovy new file mode 100644 index 00000000000..d8381e5c0c8 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/BigDecimalCodecSpecification.groovy @@ -0,0 +1,67 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs + +import org.bson.BsonDecimal128 +import org.bson.BsonDocument +import org.bson.BsonDocumentReader +import org.bson.BsonDocumentWriter +import org.bson.BsonReader +import org.bson.types.Decimal128 +import spock.lang.Specification + +class BigDecimalCodecSpecification extends Specification { + + def 'should round trip BigDecimal successfully'() { + given: + def codec = new BigDecimalCodec() + def bsonDecimal128 = new BsonDecimal128(new Decimal128(bigDecimal)) + + when: + def writer = new BsonDocumentWriter(new BsonDocument()) + writer.writeStartDocument() + writer.writeName('bigDecimal') + codec.encode(writer, bigDecimal, EncoderContext.builder().build()) + writer.writeEndDocument() + + then: + bsonDecimal128 == writer.getDocument().get('bigDecimal') + + when: + BsonReader bsonReader = new BsonDocumentReader(writer.getDocument()) + bsonReader.readStartDocument() + bsonReader.readName() + BigDecimal actual = codec.decode(bsonReader, DecoderContext.builder().build()) + + then: + bigDecimal == actual + + where: + bigDecimal << [ + new BigDecimal(123), + new BigDecimal(42L), + new BigDecimal('12345678901234567890'), + new BigDecimal(Long.valueOf(42)), + new BigDecimal('42.0'), + new BigDecimal(Double.valueOf(42)), + new BigDecimal('1.2345678901234567890'), + new BigDecimal(Long.MAX_VALUE), + new BigDecimal(Long.MIN_VALUE), + new BigDecimal(0), + ] + } +} diff --git a/bson/src/test/unit/org/bson/codecs/BsonCodecProviderSpecification.groovy b/bson/src/test/unit/org/bson/codecs/BsonCodecProviderSpecification.groovy new file mode 100644 index 00000000000..13739fe539f --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/BsonCodecProviderSpecification.groovy @@ -0,0 +1,41 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs + + +import org.bson.BsonDocument +import org.bson.BsonDocumentWrapper +import org.bson.RawBsonDocument +import spock.lang.Specification + +import static org.bson.codecs.configuration.CodecRegistries.fromProviders + +class BsonCodecProviderSpecification extends Specification { + + def provider = new BsonCodecProvider() + def codecRegistry = fromProviders(provider) + + def 'should get correct codec'() { + expect: + provider.get(String, codecRegistry) == null + + provider.get(BsonDocument, codecRegistry).class == BsonCodec + provider.get(BsonDocumentWrapper, codecRegistry).class == BsonCodec + provider.get(RawBsonDocument, codecRegistry).class == BsonCodec + provider.get(BsonDocumentSubclass, codecRegistry).class == BsonCodec + } +} diff --git a/bson/src/test/unit/org/bson/codecs/BsonCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/BsonCodecSpecification.groovy new file mode 100644 index 00000000000..22add20813b --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/BsonCodecSpecification.groovy @@ -0,0 +1,87 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs + + +import org.bson.BsonDocument +import org.bson.BsonDocumentWriter +import org.bson.BsonReader +import org.bson.codecs.configuration.CodecConfigurationException +import org.bson.codecs.configuration.CodecRegistry +import org.bson.conversions.Bson +import spock.lang.Specification + +import static org.bson.codecs.configuration.CodecRegistries.fromProviders + +class BsonCodecSpecification extends Specification { + + def provider = new BsonCodecProvider() + def registry = fromProviders(provider) + + def 'should encode Bson'() { + given: + def codec = new BsonCodec() + def customBson = new CustomBson() + + when: + def writer = new BsonDocumentWriter(new BsonDocument()) + writer.writeStartDocument() + writer.writeName('customBson') + codec.encode(writer, customBson, EncoderContext.builder().build()) + writer.writeEndDocument() + + then: + BsonDocument.parse('{a: 1, b:2}') == writer.getDocument().get('customBson') + } + + def 'should throw CodecConfiguration exception if cannot encode Bson'() { + given: + def codec = new BsonCodec() + def customBson = new ExceptionRaisingBson() + + when: + def writer = new BsonDocumentWriter(new BsonDocument()) + writer.writeStartDocument() + writer.writeName('customBson') + codec.encode(writer, customBson, EncoderContext.builder().build()) + + then: + thrown(CodecConfigurationException) + } + + def 'should throw UnsupportedOperation exception if decode is called'() { + when: + new BsonCodec().decode(Stub(BsonReader), DecoderContext.builder().build()) + + then: + thrown(UnsupportedOperationException) + } + + class CustomBson implements Bson { + @Override + BsonDocument toBsonDocument(final Class clazz, final CodecRegistry codecRegistry) { + BsonDocument.parse('{a: 1, b: 2}') + } + } + + class ExceptionRaisingBson implements Bson { + @Override + BsonDocument toBsonDocument(final Class clazz, final CodecRegistry codecRegistry) { + throw new Exception('Cannot encode') + } + } +} diff --git a/bson/src/test/unit/org/bson/codecs/BsonDocumentCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/BsonDocumentCodecSpecification.groovy index bdfb179a42d..889ae17dccf 100644 --- a/bson/src/test/unit/org/bson/codecs/BsonDocumentCodecSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/BsonDocumentCodecSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import org.bson.BsonBinaryReader import org.bson.BsonBinaryWriter import org.bson.BsonBoolean import org.bson.BsonDateTime +import org.bson.BsonDecimal128 import org.bson.BsonDocument import org.bson.BsonDocumentWriter import org.bson.BsonDouble @@ -43,6 +44,7 @@ import org.bson.ByteBufNIO import org.bson.RawBsonDocument import org.bson.io.BasicOutputBuffer import org.bson.io.ByteBufferBsonInput +import org.bson.types.Decimal128 import org.bson.types.ObjectId import spock.lang.Specification @@ -58,6 +60,7 @@ class BsonDocumentCodecSpecification extends Specification { new BsonElement('null', new BsonNull()), new BsonElement('int32', new BsonInt32(42)), new BsonElement('int64', new BsonInt64(52L)), + new BsonElement('decimal128', new BsonDecimal128(Decimal128.parse('1.0'))), new BsonElement('boolean', new BsonBoolean(true)), new BsonElement('date', new BsonDateTime(new Date().getTime())), new BsonElement('double', new BsonDouble(62.0)), @@ -93,6 +96,7 @@ class BsonDocumentCodecSpecification extends Specification { decodedDoc.get('null') == doc.get('null') decodedDoc.get('int32') == doc.get('int32') decodedDoc.get('int64') == doc.get('int64') + decodedDoc.get('decimal128') == doc.get('decimal128') decodedDoc.get('boolean') == doc.get('boolean') decodedDoc.get('date') == doc.get('date') // decodedDoc.get('dbPointer') == doc.get('dbPointer') diff --git a/bson/src/test/unit/org/bson/codecs/BsonDocumentSubclass.java b/bson/src/test/unit/org/bson/codecs/BsonDocumentSubclass.java index 5ec5fb47c53..980f0ccf8a7 100644 --- a/bson/src/test/unit/org/bson/codecs/BsonDocumentSubclass.java +++ b/bson/src/test/unit/org/bson/codecs/BsonDocumentSubclass.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/org/bson/codecs/BsonTypeClassMapSpecification.groovy b/bson/src/test/unit/org/bson/codecs/BsonTypeClassMapSpecification.groovy new file mode 100644 index 00000000000..5f80719690d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/BsonTypeClassMapSpecification.groovy @@ -0,0 +1,70 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs + +import org.bson.BsonDbPointer +import org.bson.BsonRegularExpression +import org.bson.BsonTimestamp +import org.bson.BsonType +import org.bson.BsonUndefined +import org.bson.Document +import org.bson.types.Binary +import org.bson.types.Code +import org.bson.types.CodeWithScope +import org.bson.types.Decimal128 +import org.bson.types.MaxKey +import org.bson.types.MinKey +import org.bson.types.ObjectId +import org.bson.types.Symbol +import spock.lang.Specification + +class BsonTypeClassMapSpecification extends Specification { + def 'should have defaults for all BSON types'() { + when: + def map = new BsonTypeClassMap() + + then: + map.get(BsonType.BINARY) == Binary + map.get(BsonType.BOOLEAN) == Boolean + map.get(BsonType.DATE_TIME) == Date + map.get(BsonType.DB_POINTER) == BsonDbPointer + map.get(BsonType.DOCUMENT) == Document + map.get(BsonType.DOUBLE) == Double + map.get(BsonType.INT32) == Integer + map.get(BsonType.INT64) == Long + map.get(BsonType.DECIMAL128) == Decimal128 + map.get(BsonType.MAX_KEY) == MaxKey + map.get(BsonType.MIN_KEY) == MinKey + map.get(BsonType.JAVASCRIPT) == Code + map.get(BsonType.JAVASCRIPT_WITH_SCOPE) == CodeWithScope + map.get(BsonType.OBJECT_ID) == ObjectId + map.get(BsonType.REGULAR_EXPRESSION) == BsonRegularExpression + map.get(BsonType.STRING) == String + map.get(BsonType.SYMBOL) == Symbol + map.get(BsonType.TIMESTAMP) == BsonTimestamp + map.get(BsonType.UNDEFINED) == BsonUndefined + map.get(BsonType.ARRAY) == List + } + + def 'should obey replacements'() { + when: + def map = new BsonTypeClassMap([(BsonType.DATE_TIME): java.sql.Date]) + + then: + map.get(BsonType.DATE_TIME) == java.sql.Date + } +} diff --git a/bson/src/test/unit/org/bson/codecs/BsonTypeCodecMapSpecification.groovy b/bson/src/test/unit/org/bson/codecs/BsonTypeCodecMapSpecification.groovy new file mode 100644 index 00000000000..a86ff0e5a41 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/BsonTypeCodecMapSpecification.groovy @@ -0,0 +1,69 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs + +import org.bson.BsonType +import org.bson.codecs.configuration.CodecConfigurationException +import spock.lang.Specification + +import static org.bson.codecs.configuration.CodecRegistries.fromProviders +import static org.bson.codecs.configuration.CodecRegistries.fromRegistries + +class BsonTypeCodecMapSpecification extends Specification { + def bsonTypeClassMap = new BsonTypeClassMap() + def registry = fromRegistries(fromProviders(new DocumentCodecProvider(), new ValueCodecProvider(), new BsonValueCodecProvider())) + def bsonTypeCodecMap = new BsonTypeCodecMap(bsonTypeClassMap, registry) + + def 'should map types to codecs'() { + expect: + bsonTypeCodecMap.get(BsonType.BINARY).class == BinaryCodec + bsonTypeCodecMap.get(BsonType.BOOLEAN).class == BooleanCodec + bsonTypeCodecMap.get(BsonType.DATE_TIME).class == DateCodec + bsonTypeCodecMap.get(BsonType.DB_POINTER).class == BsonDBPointerCodec + bsonTypeCodecMap.get(BsonType.DOCUMENT).class == DocumentCodec + bsonTypeCodecMap.get(BsonType.DOUBLE).class == DoubleCodec + bsonTypeCodecMap.get(BsonType.INT32).class == IntegerCodec + bsonTypeCodecMap.get(BsonType.INT64).class == LongCodec + bsonTypeCodecMap.get(BsonType.DECIMAL128).class == Decimal128Codec + bsonTypeCodecMap.get(BsonType.MAX_KEY).class == MaxKeyCodec + bsonTypeCodecMap.get(BsonType.MIN_KEY).class == MinKeyCodec + bsonTypeCodecMap.get(BsonType.JAVASCRIPT).class == CodeCodec + bsonTypeCodecMap.get(BsonType.JAVASCRIPT_WITH_SCOPE).class == CodeWithScopeCodec + bsonTypeCodecMap.get(BsonType.OBJECT_ID).class == ObjectIdCodec + bsonTypeCodecMap.get(BsonType.REGULAR_EXPRESSION).class == BsonRegularExpressionCodec + bsonTypeCodecMap.get(BsonType.STRING).class == StringCodec + bsonTypeCodecMap.get(BsonType.SYMBOL).class == SymbolCodec + bsonTypeCodecMap.get(BsonType.TIMESTAMP).class == BsonTimestampCodec + bsonTypeCodecMap.get(BsonType.UNDEFINED).class == BsonUndefinedCodec + } + + def 'should throw exception for unmapped type'() { + when: + bsonTypeCodecMap.get(BsonType.NULL) + + then: + thrown(CodecConfigurationException) + } + + def 'should throw exception for unregistered codec'() { + when: + bsonTypeCodecMap.get(BsonType.ARRAY) + + then: + thrown(CodecConfigurationException) + } +} diff --git a/bson/src/test/unit/org/bson/codecs/BsonValueCodecProviderSpecification.groovy b/bson/src/test/unit/org/bson/codecs/BsonValueCodecProviderSpecification.groovy index ea844d9b07d..de01d107551 100644 --- a/bson/src/test/unit/org/bson/codecs/BsonValueCodecProviderSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/BsonValueCodecProviderSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import org.bson.BsonArray import org.bson.BsonBoolean import org.bson.BsonDateTime import org.bson.BsonDbPointer +import org.bson.BsonDecimal128 import org.bson.BsonDocument import org.bson.BsonDocumentWrapper import org.bson.BsonDouble @@ -36,6 +37,7 @@ import org.bson.BsonString import org.bson.BsonSymbol import org.bson.BsonTimestamp import org.bson.BsonUndefined +import org.bson.RawBsonArray import org.bson.RawBsonDocument import spock.lang.Specification @@ -55,6 +57,7 @@ class BsonValueCodecProviderSpecification extends Specification { provider.get(BsonDouble, codecRegistry).class == BsonDoubleCodec provider.get(BsonString, codecRegistry).class == BsonStringCodec provider.get(BsonBoolean, codecRegistry).class == BsonBooleanCodec + provider.get(BsonDecimal128, codecRegistry).class == BsonDecimal128Codec provider.get(BsonNull, codecRegistry).class == BsonNullCodec provider.get(BsonDateTime, codecRegistry).class == BsonDateTimeCodec @@ -71,6 +74,7 @@ class BsonValueCodecProviderSpecification extends Specification { provider.get(BsonJavaScriptWithScope, codecRegistry).class == BsonJavaScriptWithScopeCodec provider.get(BsonArray, codecRegistry).class == BsonArrayCodec + provider.get(RawBsonArray, codecRegistry).class == BsonArrayCodec provider.get(BsonDocument, codecRegistry).class == BsonDocumentCodec provider.get(BsonDocumentWrapper, codecRegistry).class == BsonDocumentWrapperCodec diff --git a/bson/src/test/unit/org/bson/codecs/ByteCodecTest.java b/bson/src/test/unit/org/bson/codecs/ByteCodecTest.java new file mode 100644 index 00000000000..667c1308527 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/ByteCodecTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.junit.Test; + +public final class ByteCodecTest extends CodecTestCase { + + @Test + public void shouldRoundTripByteValues() { + roundTrip(new Document("a", Byte.MAX_VALUE)); + roundTrip(new Document("a", Byte.MIN_VALUE)); + } + + @Test + public void shouldHandleAlternativeNumberValues() { + Document expected = new Document("a", (byte) 10); + roundTrip(new Document("a", 10), expected); + roundTrip(new Document("a", 10.00), expected); + roundTrip(new Document("a", 9.9999999999999992), expected); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMinRange() { + roundTrip(new Document("a", Integer.MIN_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMaxRange() { + roundTrip(new Document("a", Integer.MAX_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDoubleValues() { + roundTrip(new Document("a", 9.9999999999999991)); + } + + @Override + DocumentCodecProvider getDocumentCodecProvider() { + return getSpecificNumberDocumentCodecProvider(Byte.class); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/CharacterCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/CharacterCodecSpecification.groovy index b5eed939714..596fce92b54 100644 --- a/bson/src/test/unit/org/bson/codecs/CharacterCodecSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/CharacterCodecSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/org/bson/codecs/CodeWithScopeSpecification.groovy b/bson/src/test/unit/org/bson/codecs/CodeWithScopeSpecification.groovy index b8ef5a6cecd..5315843f040 100644 --- a/bson/src/test/unit/org/bson/codecs/CodeWithScopeSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/CodeWithScopeSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/org/bson/codecs/CodecTestCase.java b/bson/src/test/unit/org/bson/codecs/CodecTestCase.java new file mode 100644 index 00000000000..572a4e26f88 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/CodecTestCase.java @@ -0,0 +1,110 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonBinaryReader; +import org.bson.BsonBinaryWriter; +import org.bson.BsonType; +import org.bson.BsonWriter; +import org.bson.ByteBufNIO; +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.io.BasicOutputBuffer; +import org.bson.io.ByteBufferBsonInput; +import org.bson.io.OutputBuffer; + +import java.nio.ByteBuffer; +import java.util.HashMap; + +import static java.util.Arrays.asList; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; +import static org.junit.Assert.assertEquals; + +abstract class CodecTestCase { + + DocumentCodecProvider getDocumentCodecProvider() { + return new DocumentCodecProvider(); + } + + CodecRegistry getRegistry() { + return fromProviders(asList(new ValueCodecProvider(), getDocumentCodecProvider())); + } + + void roundTrip(final T value) { + roundTrip(value, new DefaultComparator(value)); + } + + void roundTrip(final T value, final Comparator comparator) { + roundTripWithRegistry(value, comparator, getRegistry()); + } + + @SuppressWarnings("unchecked") + void roundTripWithRegistry(final T value, final Comparator comparator, final CodecRegistry codecRegistry) { + Codec codec = (Codec) codecRegistry.get(value.getClass()); + OutputBuffer encoded = encode(codec, value); + T decoded = decode(codec, encoded); + comparator.apply(decoded); + } + + public void roundTrip(final Document input, final Document expected) { + roundTrip(input, new Comparator() { + @Override + public void apply(final Document result) { + assertEquals("Codec Round Trip", expected, result); + } + }); + } + + OutputBuffer encode(final Codec codec, final T value) { + OutputBuffer buffer = new BasicOutputBuffer(); + BsonWriter writer = new BsonBinaryWriter(buffer); + codec.encode(writer, value, EncoderContext.builder().build()); + return buffer; + } + + T decode(final Codec codec, final OutputBuffer buffer) { + BsonBinaryReader reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(buffer.toByteArray())))); + return codec.decode(reader, DecoderContext.builder().build()); + } + + DocumentCodecProvider getSpecificNumberDocumentCodecProvider(final Class clazz) { + HashMap> replacements = new HashMap>(); + replacements.put(BsonType.DOUBLE, clazz); + replacements.put(BsonType.INT32, clazz); + replacements.put(BsonType.INT64, clazz); + replacements.put(BsonType.DECIMAL128, clazz); + return new DocumentCodecProvider(new BsonTypeClassMap(replacements)); + } + + interface Comparator { + void apply(T result); + } + + class DefaultComparator implements Comparator { + private final T original; + + DefaultComparator(final T original) { + this.original = original; + } + + @Override + public void apply(final T result) { + assertEquals("Codec Round Trip", original, result); + } + } + +} diff --git a/bson/src/test/unit/org/bson/codecs/CodecTestUtil.java b/bson/src/test/unit/org/bson/codecs/CodecTestUtil.java index c1be2469e24..54e0efee5b7 100644 --- a/bson/src/test/unit/org/bson/codecs/CodecTestUtil.java +++ b/bson/src/test/unit/org/bson/codecs/CodecTestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/org/bson/codecs/DocumentCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/DocumentCodecSpecification.groovy index 3f717e631fe..1902789325d 100644 --- a/bson/src/test/unit/org/bson/codecs/DocumentCodecSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/DocumentCodecSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import org.bson.types.ObjectId import org.bson.types.Symbol import spock.lang.Shared import spock.lang.Specification +import spock.lang.Unroll import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicBoolean @@ -49,6 +50,12 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong import static java.util.Arrays.asList +import static org.bson.UuidRepresentation.C_SHARP_LEGACY +import static org.bson.UuidRepresentation.JAVA_LEGACY +import static org.bson.UuidRepresentation.PYTHON_LEGACY +import static org.bson.UuidRepresentation.STANDARD +import static org.bson.UuidRepresentation.UNSPECIFIED +import static org.bson.codecs.configuration.CodecRegistries.fromCodecs import static org.bson.codecs.configuration.CodecRegistries.fromProviders class DocumentCodecSpecification extends Specification { @@ -95,7 +102,7 @@ class DocumentCodecSpecification extends Specification { if (writer instanceof BsonDocumentWriter) { reader = new BsonDocumentReader(bsonDoc) } else if (writer instanceof BsonBinaryWriter) { - BasicOutputBuffer buffer = (BasicOutputBuffer)writer.getBsonOutput(); + BasicOutputBuffer buffer = (BasicOutputBuffer)writer.getBsonOutput() reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO( ByteBuffer.wrap(buffer.toByteArray())))) } else { @@ -139,6 +146,67 @@ class DocumentCodecSpecification extends Specification { ] } + def 'should decode binary subtypes for UUID that are not 16 bytes into Binary'() { + given: + def reader = new BsonBinaryReader(ByteBuffer.wrap(bytes as byte[])) + + when: + def document = new DocumentCodec().decode(reader, DecoderContext.builder().build()) + + then: + value == document.get('f') + + where: + value | bytes + new Binary((byte) 0x03, (byte[]) [115, 116, 11]) | [16, 0, 0, 0, 5, 102, 0, 3, 0, 0, 0, 3, 115, 116, 11, 0] + new Binary((byte) 0x04, (byte[]) [115, 116, 11]) | [16, 0, 0, 0, 5, 102, 0, 3, 0, 0, 0, 4, 115, 116, 11, 0] + } + + @SuppressWarnings(['LineLength']) + @Unroll + def 'should decode binary subtype 3 for UUID'() { + given: + def reader = new BsonBinaryReader(ByteBuffer.wrap(bytes as byte[])) + + when: + def document = new DocumentCodec(fromCodecs(new UuidCodec(representation), new BinaryCodec())) + .withUuidRepresentation(representation) + .decode(reader, DecoderContext.builder().build()) + + then: + value == document.get('f') + + where: + representation | value | bytes + JAVA_LEGACY | UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + C_SHARP_LEGACY | UUID.fromString('04030201-0605-0807-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + PYTHON_LEGACY | UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + STANDARD | new Binary((byte) 3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + UNSPECIFIED | new Binary((byte) 3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + } + + @SuppressWarnings(['LineLength']) + @Unroll + def 'should decode binary subtype 4 for UUID'() { + given: + def reader = new BsonBinaryReader(ByteBuffer.wrap(bytes as byte[])) + + when: + def document = new DocumentCodec().withUuidRepresentation(representation) + .decode(reader, DecoderContext.builder().build()) + + then: + value == document.get('f') + + where: + representation | value | bytes + STANDARD | UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + JAVA_LEGACY | UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + C_SHARP_LEGACY | new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + PYTHON_LEGACY | new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + UNSPECIFIED | new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + } + def 'should respect encodeIdFirst property in encoder context'() { given: def originalDocument = new Document('x', 2) diff --git a/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java b/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java index 10066accac3..61a4f71618e 100644 --- a/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java +++ b/bson/src/test/unit/org/bson/codecs/DocumentCodecTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.CodeWithScope; +import org.bson.types.Decimal128; import org.bson.types.MaxKey; import org.bson.types.MinKey; import org.bson.types.ObjectId; @@ -72,6 +73,7 @@ public void testPrimitiveBSONTypeCodecs() throws IOException { doc.put("long", 2L); doc.put("string", "hello"); doc.put("double", 3.2); + doc.put("decimal", Decimal128.parse("0.100")); doc.put("binary", new Binary(BsonBinarySubType.USER_DEFINED, new byte[]{0, 1, 2, 3})); doc.put("date", new Date(1000)); doc.put("boolean", true); diff --git a/bson/src/test/unit/org/bson/codecs/DoubleCodecTest.java b/bson/src/test/unit/org/bson/codecs/DoubleCodecTest.java new file mode 100644 index 00000000000..cfceae50530 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/DoubleCodecTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.bson.types.Decimal128; +import org.junit.Test; + +public final class DoubleCodecTest extends CodecTestCase { + + @Test + public void shouldRoundTripDoubleValues() { + roundTrip(new Document("a", Long.MAX_VALUE), new Document("a", (double) Long.MAX_VALUE)); + roundTrip(new Document("a", Long.MIN_VALUE), new Document("a", (double) Long.MIN_VALUE)); + } + + @Test + public void shouldHandleAlternativeNumberValues() { + Document expected = new Document("a", 10.00); + roundTrip(new Document("a", 10), expected); + roundTrip(new Document("a", 10L), expected); + roundTrip(new Document("a", Decimal128.parse("10")), expected); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyLongValues() { + roundTrip(new Document("a", Long.MAX_VALUE - 1)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyLongValues2() { + roundTrip(new Document("a", Long.MIN_VALUE + 1)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDecimal128Values() { + roundTrip(new Document("a", Decimal128.parse("10.0"))); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingNonExpressibleDecimal128Values() { + roundTrip(new Document("a", Decimal128.parse("NaN"))); + } + + @Override + DocumentCodecProvider getDocumentCodecProvider() { + return getSpecificNumberDocumentCodecProvider(Double.class); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/FloatCodecTest.java b/bson/src/test/unit/org/bson/codecs/FloatCodecTest.java new file mode 100644 index 00000000000..2d59798fefc --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/FloatCodecTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.bson.types.Decimal128; +import org.junit.Test; + +public final class FloatCodecTest extends CodecTestCase { + + @Test + public void shouldRoundTripFloatValues() { + roundTrip(new Document("a", Float.MAX_VALUE)); + roundTrip(new Document("a", -Float.MAX_VALUE)); + } + + @Test + public void shouldRoundTripNegativeFloatValues() { + roundTrip(new Document("a", -1f)); + } + + @Test + public void shouldHandleAlternativeNumberValues() { + Document expected = new Document("a", 10f); + roundTrip(new Document("a", 10), expected); + roundTrip(new Document("a", 10L), expected); + roundTrip(new Document("a", 9.9999999999999992), expected); + roundTrip(new Document("a", Decimal128.parse("10")), expected); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMinRange() { + roundTrip(new Document("a", -Double.MAX_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMaxRange() { + roundTrip(new Document("a", Double.MAX_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDecimal128Values() { + roundTrip(new Document("a", Decimal128.parse("10.0"))); + } + + @Override + DocumentCodecProvider getDocumentCodecProvider() { + return getSpecificNumberDocumentCodecProvider(Float.class); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/IntegerCodecTest.java b/bson/src/test/unit/org/bson/codecs/IntegerCodecTest.java new file mode 100644 index 00000000000..d71e27122e0 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/IntegerCodecTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.bson.types.Decimal128; +import org.junit.Test; + +public final class IntegerCodecTest extends CodecTestCase { + + @Test + public void shouldRoundTripIntegerValues() { + roundTrip(new Document("a", Integer.MAX_VALUE)); + roundTrip(new Document("a", Integer.MIN_VALUE)); + } + + @Test + public void shouldHandleAlternativeNumberValues() { + Document expected = new Document("a", 10); + roundTrip(new Document("a", 10L), expected); + roundTrip(new Document("a", 10.00), expected); + roundTrip(new Document("a", 9.9999999999999992), expected); + roundTrip(new Document("a", Decimal128.parse("10")), expected); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMinRange() { + roundTrip(new Document("a", Long.MIN_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMaxRange() { + roundTrip(new Document("a", Long.MAX_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDoubleValues() { + roundTrip(new Document("a", 9.9999999999999991)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDecimal128Values() { + roundTrip(new Document("a", Decimal128.parse("10.0"))); + } + + @Override + DocumentCodecProvider getDocumentCodecProvider() { + return getSpecificNumberDocumentCodecProvider(Integer.class); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/IterableCodecProviderSpecification.groovy b/bson/src/test/unit/org/bson/codecs/IterableCodecProviderSpecification.groovy index f3c6fcb25eb..b0eae796fc4 100644 --- a/bson/src/test/unit/org/bson/codecs/IterableCodecProviderSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/IterableCodecProviderSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -7,12 +7,11 @@ * * 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. - * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.bson.codecs @@ -69,4 +68,4 @@ class IterableCodecProviderSpecification extends Specification { !first.equals(third) !second.equals(third) } -} \ No newline at end of file +} diff --git a/bson/src/test/unit/org/bson/codecs/IterableCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/IterableCodecSpecification.groovy index 5a5e3e034e3..9391e2bc996 100644 --- a/bson/src/test/unit/org/bson/codecs/IterableCodecSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/IterableCodecSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -7,22 +7,30 @@ * * 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. - * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.bson.codecs + import org.bson.BsonDocument import org.bson.BsonDocumentReader import org.bson.BsonDocumentWriter +import org.bson.types.Binary import spock.lang.Specification +import spock.lang.Unroll import static org.bson.BsonDocument.parse +import static org.bson.UuidRepresentation.C_SHARP_LEGACY +import static org.bson.UuidRepresentation.JAVA_LEGACY +import static org.bson.UuidRepresentation.PYTHON_LEGACY +import static org.bson.UuidRepresentation.STANDARD +import static org.bson.UuidRepresentation.UNSPECIFIED +import static org.bson.codecs.configuration.CodecRegistries.fromCodecs import static org.bson.codecs.configuration.CodecRegistries.fromProviders class IterableCodecSpecification extends Specification { @@ -68,6 +76,21 @@ class IterableCodecSpecification extends Specification { iterable == [1, 2, 3, null] } + def 'should decode a BSON array of arrays to an Iterable of Iterables'() { + given: + def codec = new IterableCodec(REGISTRY, new BsonTypeClassMap()) + def reader = new BsonDocumentReader(parse('{array : [[1, 2], [3, 4, 5]]}')) + + when: + reader.readStartDocument() + reader.readName('array') + def iterable = codec.decode(reader, DecoderContext.builder().build()) + reader.readEndDocument() + + then: + iterable == [[1, 2], [3, 4, 5]] + } + def 'should use provided transformer'() { given: def codec = new IterableCodec(REGISTRY, new BsonTypeClassMap(), { Object from -> @@ -85,10 +108,10 @@ class IterableCodecSpecification extends Specification { iterable == ['1', '2', '3'] } - def 'should decode a BSON Binary with subtype of UIID_LEGACY to a UUID'() { + def 'should decode binary subtypes for UUID'() { given: def codec = new IterableCodec(REGISTRY, new BsonTypeClassMap(), null) - def reader = new BsonDocumentReader(parse('{array : [{ "$binary" : "D0dqZ20GeYvWzXdt0gkSlA==", "$type" : "3" }]}')) + def reader = new BsonDocumentReader(parse(document)) when: reader.readStartDocument() @@ -97,13 +120,23 @@ class IterableCodecSpecification extends Specification { reader.readEndDocument() then: - iterable == [UUID.fromString('8b79066d-676a-470f-9412-09d26d77cdd6')] + iterable == value + + where: + document | value + '{"array": [{ "$binary" : "c3QL", "$type" : "3" }]}' | [new Binary((byte) 0x03, (byte[]) [115, 116, 11])] + '{"array": [{ "$binary" : "c3QL", "$type" : "4" }]}' | [new Binary((byte) 0x04, (byte[]) [115, 116, 11])] + '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "3" }]}' | [UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09')] + '{"array": [{ "$binary" : "CAcGBQQDAgEQDw4NDAsKCQ==", "$type" : "3" }]}' | [UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10')] } - def 'should decode a BSON Binary with subtype of UIID_STANDARD to a UUID'() { + @SuppressWarnings(['LineLength']) + @Unroll + def 'should decode binary subtype 3 for UUID'() { given: - def codec = new IterableCodec(REGISTRY, new BsonTypeClassMap(), null) - def reader = new BsonDocumentReader(parse('{array : [{ "$binary" : "i3kGbWdqRw+UEgnSbXfN1g==", "$type" : "4" }]}')) + def reader = new BsonDocumentReader(parse(document)) + def codec = new IterableCodec(fromCodecs(new UuidCodec(representation), new BinaryCodec()), new BsonTypeClassMap()) + .withUuidRepresentation(representation) when: reader.readStartDocument() @@ -112,7 +145,40 @@ class IterableCodecSpecification extends Specification { reader.readEndDocument() then: - iterable == [UUID.fromString('8b79066d-676a-470f-9412-09d26d77cdd6')] + value == iterable + + where: + representation | value | document + JAVA_LEGACY | [UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09')] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "3" }]}' + C_SHARP_LEGACY | [UUID.fromString('04030201-0605-0807-090a-0b0c0d0e0f10')] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "3" }]}' + PYTHON_LEGACY | [UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10')] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "3" }]}' + STANDARD | [new Binary((byte) 3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[])] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "3" }]}' + UNSPECIFIED | [new Binary((byte) 3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[])] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "3" }]}' } -} \ No newline at end of file + @SuppressWarnings(['LineLength']) + @Unroll + def 'should decode binary subtype 4 for UUID'() { + given: + def reader = new BsonDocumentReader(parse(document)) + def codec = new IterableCodec(fromCodecs(new UuidCodec(representation), new BinaryCodec()), new BsonTypeClassMap()) + .withUuidRepresentation(representation) + + when: + reader.readStartDocument() + reader.readName('array') + def iterable = codec.decode(reader, DecoderContext.builder().build()) + reader.readEndDocument() + + then: + value == iterable + + where: + representation | value | document + STANDARD | [UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10')] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "4" }]}' + JAVA_LEGACY | [UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10')] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "4" }]}' + C_SHARP_LEGACY | [new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[])] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "4" }]}' + PYTHON_LEGACY | [new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[])] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "4" }]}' + UNSPECIFIED | [new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[])] | '{"array": [{ "$binary" : "AQIDBAUGBwgJCgsMDQ4PEA==", "$type" : "4" }]}' + } +} diff --git a/bson/src/test/unit/org/bson/codecs/LongCodecTest.java b/bson/src/test/unit/org/bson/codecs/LongCodecTest.java new file mode 100644 index 00000000000..07ac4cdede9 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/LongCodecTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.bson.types.Decimal128; +import org.junit.Test; + +public final class LongCodecTest extends CodecTestCase { + + @Test + public void shouldRoundTripLongValues() { + roundTrip(new Document("a", Long.MAX_VALUE)); + roundTrip(new Document("a", Long.MIN_VALUE)); + } + + @Test + public void shouldHandleAlternativeNumberValues() { + Document expected = new Document("a", 10L); + roundTrip(new Document("a", 10), expected); + roundTrip(new Document("a", 10.00), expected); + roundTrip(new Document("a", 9.9999999999999992), expected); + roundTrip(new Document("a", Decimal128.parse("10")), expected); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyValues() { + roundTrip(new Document("a", Double.MAX_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDoubleValues() { + roundTrip(new Document("a", 9.9999999999999991)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldThrowWhenHandlingLossyDecimal128Values() { + roundTrip(new Document("a", Decimal128.parse("10.0"))); + } + + @Override + DocumentCodecProvider getDocumentCodecProvider() { + return getSpecificNumberDocumentCodecProvider(Long.class); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/MapCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/MapCodecSpecification.groovy new file mode 100644 index 00000000000..bd74f1b5f55 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/MapCodecSpecification.groovy @@ -0,0 +1,204 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs + +import org.bson.BsonBinaryReader +import org.bson.BsonBinaryWriter +import org.bson.BsonDbPointer +import org.bson.BsonDocument +import org.bson.BsonDocumentReader +import org.bson.BsonDocumentWriter +import org.bson.BsonInt32 +import org.bson.BsonReader +import org.bson.BsonRegularExpression +import org.bson.BsonTimestamp +import org.bson.BsonUndefined +import org.bson.BsonWriter +import org.bson.ByteBufNIO +import org.bson.Document +import org.bson.io.BasicOutputBuffer +import org.bson.io.ByteBufferBsonInput +import org.bson.json.JsonReader +import org.bson.types.Binary +import org.bson.types.Code +import org.bson.types.CodeWithScope +import org.bson.types.MaxKey +import org.bson.types.MinKey +import org.bson.types.ObjectId +import org.bson.types.Symbol +import spock.lang.Shared +import spock.lang.Specification +import spock.lang.Unroll + +import java.nio.ByteBuffer +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong + +import static java.util.Arrays.asList +import static org.bson.UuidRepresentation.C_SHARP_LEGACY +import static org.bson.UuidRepresentation.JAVA_LEGACY +import static org.bson.UuidRepresentation.PYTHON_LEGACY +import static org.bson.UuidRepresentation.STANDARD +import static org.bson.UuidRepresentation.UNSPECIFIED +import static org.bson.codecs.configuration.CodecRegistries.fromCodecs +import static org.bson.codecs.configuration.CodecRegistries.fromProviders + +class MapCodecSpecification extends Specification { + @Shared + BsonDocument bsonDoc = new BsonDocument() + @Shared + StringWriter stringWriter = new StringWriter() + + def 'should encode and decode all default types with all readers and writers'(BsonWriter writer) { + given: + def originalDocument = [:] + originalDocument.with { + put('null', null) + put('int32', 42) + put('int64', 52L) + put('booleanTrue', true) + put('booleanFalse', false) + put('date', new Date()) + put('dbPointer', new BsonDbPointer('foo.bar', new ObjectId())) + put('double', 62.0 as double) + put('minKey', new MinKey()) + put('maxKey', new MaxKey()) + put('code', new Code('int i = 0;')) + put('codeWithScope', new CodeWithScope('int x = y', new Document('y', 1))) + put('objectId', new ObjectId()) + put('regex', new BsonRegularExpression('^test.*regex.*xyz$', 'i')) + put('string', 'the fox ...') + put('symbol', new Symbol('ruby stuff')) + put('timestamp', new BsonTimestamp(0x12345678, 5)) + put('undefined', new BsonUndefined()) + put('binary', new Binary((byte) 0x80, [5, 4, 3, 2, 1] as byte[])) + put('array', asList(1, 1L, true, [1, 2, 3], new Document('a', 1), null)) + put('uuid', new UUID(1L, 2L)) + put('document', new Document('a', 2)) + put('map', [a:1, b:2]) + put('atomicLong', new AtomicLong(1)) + put('atomicInteger', new AtomicInteger(1)) + put('atomicBoolean', new AtomicBoolean(true)) + } + + when: + new MapCodec().encode(writer, originalDocument, EncoderContext.builder().build()) + BsonReader reader + if (writer instanceof BsonDocumentWriter) { + reader = new BsonDocumentReader(bsonDoc) + } else if (writer instanceof BsonBinaryWriter) { + BasicOutputBuffer buffer = (BasicOutputBuffer)writer.getBsonOutput(); + reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO( + ByteBuffer.wrap(buffer.toByteArray())))) + } else { + reader = new JsonReader(stringWriter.toString()) + } + def decodedDoc = new MapCodec().decode(reader, DecoderContext.builder().build()) + + then: + decodedDoc.get('null') == originalDocument.get('null') + decodedDoc.get('int32') == originalDocument.get('int32') + decodedDoc.get('int64') == originalDocument.get('int64') + decodedDoc.get('booleanTrue') == originalDocument.get('booleanTrue') + decodedDoc.get('booleanFalse') == originalDocument.get('booleanFalse') + decodedDoc.get('date') == originalDocument.get('date') + decodedDoc.get('dbPointer') == originalDocument.get('dbPointer') + decodedDoc.get('double') == originalDocument.get('double') + decodedDoc.get('minKey') == originalDocument.get('minKey') + decodedDoc.get('maxKey') == originalDocument.get('maxKey') + decodedDoc.get('code') == originalDocument.get('code') + decodedDoc.get('codeWithScope') == originalDocument.get('codeWithScope') + decodedDoc.get('objectId') == originalDocument.get('objectId') + decodedDoc.get('regex') == originalDocument.get('regex') + decodedDoc.get('string') == originalDocument.get('string') + decodedDoc.get('symbol') == originalDocument.get('symbol') + decodedDoc.get('timestamp') == originalDocument.get('timestamp') + decodedDoc.get('undefined') == originalDocument.get('undefined') + decodedDoc.get('binary') == originalDocument.get('binary') + decodedDoc.get('uuid') == originalDocument.get('uuid') + decodedDoc.get('array') == originalDocument.get('array') + decodedDoc.get('document') == originalDocument.get('document') + decodedDoc.get('map') == originalDocument.get('map') + decodedDoc.get('atomicLong') == ((AtomicLong) originalDocument.get('atomicLong')).get() + decodedDoc.get('atomicInteger') == ((AtomicInteger) originalDocument.get('atomicInteger')).get() + decodedDoc.get('atomicBoolean') == ((AtomicBoolean) originalDocument.get('atomicBoolean')).get() + + where: + writer << [ + new BsonDocumentWriter(bsonDoc), + new BsonBinaryWriter(new BasicOutputBuffer()) + ] + } + + @SuppressWarnings(['LineLength']) + @Unroll + def 'should decode binary subtype 3 for UUID'() { + given: + def reader = new BsonBinaryReader(ByteBuffer.wrap(bytes as byte[])) + + when: + def map = new MapCodec(fromCodecs(new UuidCodec(representation), new BinaryCodec())) + .withUuidRepresentation(representation) + .decode(reader, DecoderContext.builder().build()) + + then: + value == map.get('f') + + where: + representation | value | bytes + JAVA_LEGACY | UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + C_SHARP_LEGACY | UUID.fromString('04030201-0605-0807-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + PYTHON_LEGACY | UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + STANDARD | new Binary((byte) 3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + UNSPECIFIED | new Binary((byte) 3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + } + + @SuppressWarnings(['LineLength']) + @Unroll + def 'should decode binary subtype 4 for UUID'() { + given: + def reader = new BsonBinaryReader(ByteBuffer.wrap(bytes as byte[])) + + when: + def map = new MapCodec().withUuidRepresentation(representation) + .decode(reader, DecoderContext.builder().build()) + + then: + value == map.get('f') + + where: + representation | value | bytes + STANDARD | UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + JAVA_LEGACY | UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + C_SHARP_LEGACY | new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + PYTHON_LEGACY | new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + UNSPECIFIED | new Binary((byte) 4, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] as byte[]) | [29, 0, 0, 0, 5, 102, 0, 16, 0, 0, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0] + } + + def 'should apply transformer to decoded values'() { + given: + def codec = new MapCodec(fromProviders([new ValueCodecProvider(), new DocumentCodecProvider(), new BsonValueCodecProvider()]), + new BsonTypeClassMap(), + { Object value -> 5 }) + when: + def doc = codec.decode(new BsonDocumentReader(new BsonDocument('_id', new BsonInt32(1))), DecoderContext.builder().build()) + + then: + doc['_id'] == 5 + } +} diff --git a/bson/src/test/unit/org/bson/codecs/OverridableUuidRepresentationUuidCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/OverridableUuidRepresentationUuidCodecSpecification.groovy new file mode 100644 index 00000000000..1504b4e032a --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/OverridableUuidRepresentationUuidCodecSpecification.groovy @@ -0,0 +1,39 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs + +import org.bson.UuidRepresentation +import spock.lang.Specification + +class OverridableUuidRepresentationUuidCodecSpecification extends Specification{ + + def 'should change uuid representation'() { + when: + def codec = new OverridableUuidRepresentationUuidCodec() + + then: + codec.getUuidRepresentation() == UuidRepresentation.JAVA_LEGACY + + when: + def newCodec = codec.withUuidRepresentation(UuidRepresentation.STANDARD) + + then: + newCodec instanceof OverridableUuidRepresentationCodec + (newCodec as OverridableUuidRepresentationCodec).getUuidRepresentation() == UuidRepresentation.STANDARD + } + +} diff --git a/bson/src/test/unit/org/bson/codecs/RawBsonDocumentCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/RawBsonDocumentCodecSpecification.groovy index 0cf70b386fc..eb3f1e7a9c3 100644 --- a/bson/src/test/unit/org/bson/codecs/RawBsonDocumentCodecSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/RawBsonDocumentCodecSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,4 +63,4 @@ class RawBsonDocumentCodecSpecification extends Specification { then: bytes == documentBytes } -} \ No newline at end of file +} diff --git a/bson/src/test/unit/org/bson/codecs/ShortCodecTest.java b/bson/src/test/unit/org/bson/codecs/ShortCodecTest.java new file mode 100644 index 00000000000..3712f35176b --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/ShortCodecTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs; + +import org.bson.BsonInvalidOperationException; +import org.bson.Document; +import org.junit.Test; + +public final class ShortCodecTest extends CodecTestCase { + + @Test + public void shouldRoundTripFloatValues() { + roundTrip(new Document("a", Short.MAX_VALUE)); + roundTrip(new Document("a", Short.MIN_VALUE)); + } + + @Test + public void shouldHandleAlternativeNumberValues() { + Document expected = new Document("a", (short) 10); + roundTrip(new Document("a", 10), expected); + roundTrip(new Document("a", 10L), expected); + roundTrip(new Document("a", 10.00), expected); + roundTrip(new Document("a", 9.9999999999999992), expected); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMinRange() { + roundTrip(new Document("a", Integer.MIN_VALUE)); + } + + @Test(expected = BsonInvalidOperationException.class) + public void shouldErrorDecodingOutsideMaxRange() { + roundTrip(new Document("a", Integer.MAX_VALUE)); + } + + @Override + DocumentCodecProvider getDocumentCodecProvider() { + return getSpecificNumberDocumentCodecProvider(Short.class); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/UndefinedCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/UndefinedCodecSpecification.groovy index 33ade1f62a0..a6074f88731 100644 --- a/bson/src/test/unit/org/bson/codecs/UndefinedCodecSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/UndefinedCodecSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/org/bson/codecs/UuidCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/UuidCodecSpecification.groovy index 3039706e3bb..6f05e776af3 100644 --- a/bson/src/test/unit/org/bson/codecs/UuidCodecSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/UuidCodecSpecification.groovy @@ -1,27 +1,28 @@ /* + * Copyright 2008-present MongoDB, Inc. * - * * Copyright (c) 2008-2014 MongoDB, Inc. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.bson.codecs import org.bson.BsonBinaryReader import org.bson.BsonBinaryWriter +import org.bson.BsonDocument +import org.bson.BsonDocumentWriter import org.bson.ByteBufNIO import org.bson.UuidRepresentation +import org.bson.codecs.configuration.CodecConfigurationException import org.bson.io.BasicOutputBuffer import org.bson.io.ByteBufferBsonInput import spock.lang.Shared @@ -29,6 +30,8 @@ import spock.lang.Specification import java.nio.ByteBuffer +import static org.bson.UuidRepresentation.JAVA_LEGACY + /** * */ @@ -42,8 +45,12 @@ class UuidCodecSpecification extends Specification { outputBuffer = new BasicOutputBuffer(); } - def 'should decode different types of UUID'(UuidCodec codec, byte[] list) throws IOException { + def 'should default to Java legacy representation'() { + expect: + new UuidCodec().getUuidRepresentation() == JAVA_LEGACY + } + def 'should decode different types of UUID'(UuidCodec codec, byte[] list) throws IOException { given: ByteBufferBsonInput inputBuffer = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(list))) @@ -104,7 +111,6 @@ class UuidCodecSpecification extends Specification { 5, 6, 7, 8, 3, 4, 1, 2, 16, 15, 14, 13, 12, 11, 10, 9], //8 bytes for long, 2 longs for UUID, Big Endian ] - } def 'should encode different types of UUIDs'(Byte bsonSubType, @@ -153,4 +159,15 @@ class UuidCodecSpecification extends Specification { UUID.fromString('04030201-0605-0807-090a-0b0c0d0e0f10') // simulated C# UUID ] } -} \ No newline at end of file + + def 'should throw if representation is unspecified'() { + given: + def codec = new UuidCodec(UuidRepresentation.UNSPECIFIED) + + when: + codec.encode(new BsonDocumentWriter(new BsonDocument()), UUID.randomUUID(), EncoderContext.builder().build()) + + then: + thrown(CodecConfigurationException) + } +} diff --git a/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy b/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy index 97102c33824..c20299715e0 100644 --- a/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/ValueCodecProviderSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import org.bson.Document import org.bson.codecs.configuration.CodecRegistries import org.bson.types.Binary import org.bson.types.Code +import org.bson.types.Decimal128 import org.bson.types.MaxKey import org.bson.types.MinKey import org.bson.types.ObjectId @@ -44,6 +45,8 @@ class ValueCodecProviderSpecification extends Specification { provider.get(Boolean, registry) instanceof BooleanCodec provider.get(Integer, registry) instanceof IntegerCodec provider.get(Long, registry) instanceof LongCodec + provider.get(Decimal128, registry) instanceof Decimal128Codec + provider.get(BigDecimal, registry) instanceof BigDecimalCodec provider.get(Double, registry) instanceof DoubleCodec provider.get(Character, registry) instanceof CharacterCodec provider.get(String, registry) instanceof StringCodec @@ -60,7 +63,7 @@ class ValueCodecProviderSpecification extends Specification { provider.get(Code, registry) instanceof CodeCodec provider.get(ObjectId, registry) instanceof ObjectIdCodec provider.get(Symbol, registry) instanceof SymbolCodec - provider.get(UUID, registry) instanceof UuidCodec + provider.get(UUID, registry) instanceof OverridableUuidRepresentationCodec provider.get(Document, registry) == null } diff --git a/bson/src/test/unit/org/bson/codecs/configuration/CodeRegistriesSpecification.groovy b/bson/src/test/unit/org/bson/codecs/configuration/CodeRegistriesSpecification.groovy index db0aa0cb229..9754b24ed6c 100644 --- a/bson/src/test/unit/org/bson/codecs/configuration/CodeRegistriesSpecification.groovy +++ b/bson/src/test/unit/org/bson/codecs/configuration/CodeRegistriesSpecification.groovy @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -23,6 +23,7 @@ import org.bson.codecs.IntegerCodec import org.bson.codecs.LongCodec import org.bson.codecs.UuidCodec import org.bson.codecs.ValueCodecProvider +import org.bson.internal.ProvidersCodecRegistry import spock.lang.Specification import static CodecRegistries.fromCodecs @@ -69,4 +70,4 @@ class CodeRegistriesSpecification extends Specification { registry.get(UUID).is(uuidCodec) registry.get(Integer) instanceof IntegerCodec } -} \ No newline at end of file +} diff --git a/bson/src/test/unit/org/bson/codecs/jsr310/InstantCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/jsr310/InstantCodecSpecification.groovy new file mode 100644 index 00000000000..3ece437ce0a --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/jsr310/InstantCodecSpecification.groovy @@ -0,0 +1,83 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310 + +import org.bson.BsonDocument +import org.bson.codecs.Codec +import org.bson.codecs.configuration.CodecConfigurationException +import spock.lang.IgnoreIf + +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset + +@IgnoreIf({ javaVersion < 1.8 }) +class InstantCodecSpecification extends JsrSpecification { + + def 'should round trip Instant successfully'() { + when: + def writer = encode(instant) + + then: + writer.getDocument().get('key').asDateTime().value == millis + + when: + Instant actual = decode(writer) + + then: + instant == actual + + where: + instant | millis + Instant.EPOCH | 0 + LocalDateTime.of(2007, 10, 20, 0, 35).toInstant(ZoneOffset.UTC) | 1_192_840_500_000 + } + + def 'should wrap long overflow error in a CodecConfigurationException'() { + when: + encode(instant) + + then: + def e = thrown(CodecConfigurationException) + e.getCause().getClass() == ArithmeticException + + where: + instant << [ + Instant.MIN, + Instant.MAX + ] + } + + def 'should throw a CodecConfiguration exception if BsonType is invalid'() { + when: + decode(invalidDuration) + + then: + thrown(CodecConfigurationException) + + where: + invalidDuration << [ + BsonDocument.parse('{key: "10 Minutes"}'), + BsonDocument.parse('{key: 10}') + ] + } + + @Override + Codec getCodec() { + new InstantCodec() + } +} diff --git a/bson/src/test/unit/org/bson/codecs/jsr310/Jsr310CodecProviderSpecification.groovy b/bson/src/test/unit/org/bson/codecs/jsr310/Jsr310CodecProviderSpecification.groovy new file mode 100644 index 00000000000..bf8999cf535 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/jsr310/Jsr310CodecProviderSpecification.groovy @@ -0,0 +1,52 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310 + +import org.bson.codecs.configuration.CodecRegistry +import spock.lang.IgnoreIf +import spock.lang.Specification + +class Jsr310CodecProviderSpecification extends Specification { + + @IgnoreIf({ javaVersion < 1.8 }) + def 'should provide a codec for all JSR-310 classes'() { + given: + def codecRegistry = Stub(CodecRegistry) + def provider = new Jsr310CodecProvider() + + expect: + provider.get(clazz, codecRegistry) != null + + where: + clazz << [ + java.time.Instant, + java.time.LocalDate, + java.time.LocalDateTime, + java.time.LocalTime, + ] + } + + @IgnoreIf({ javaVersion > 1.7 }) + def 'should not error when used on pre java 8'() { + given: + def codecRegistry = Stub(CodecRegistry) + def provider = new Jsr310CodecProvider() + + expect: + provider.get(Integer, codecRegistry) == null + } +} diff --git a/bson/src/test/unit/org/bson/codecs/jsr310/JsrSpecification.groovy b/bson/src/test/unit/org/bson/codecs/jsr310/JsrSpecification.groovy new file mode 100644 index 00000000000..119869b3495 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/jsr310/JsrSpecification.groovy @@ -0,0 +1,51 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310 + +import org.bson.BsonDocument +import org.bson.BsonDocumentReader +import org.bson.BsonDocumentWriter +import org.bson.BsonReader +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import spock.lang.Specification + +abstract class JsrSpecification extends Specification { + + abstract Codec getCodec() + + def encode(jsrDateTime) { + def writer = new BsonDocumentWriter(new BsonDocument()) + writer.writeStartDocument() + writer.writeName('key') + getCodec().encode(writer, jsrDateTime, EncoderContext.builder().build()) + writer.writeEndDocument() + writer + } + + def decode(BsonDocumentWriter writer) { + decode(writer.getDocument()) + } + + def decode(BsonDocument document) { + BsonReader bsonReader = new BsonDocumentReader(document) + bsonReader.readStartDocument() + bsonReader.readName() + getCodec().decode(bsonReader, DecoderContext.builder().build()) + } +} diff --git a/bson/src/test/unit/org/bson/codecs/jsr310/LocalDateCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/jsr310/LocalDateCodecSpecification.groovy new file mode 100644 index 00000000000..e418056cb8f --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/jsr310/LocalDateCodecSpecification.groovy @@ -0,0 +1,108 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310 + +import org.bson.BsonDocument +import org.bson.codecs.Codec +import org.bson.codecs.configuration.CodecConfigurationException +import spock.lang.IgnoreIf + +import java.time.LocalDate + +@IgnoreIf({ javaVersion < 1.8 }) +class LocalDateCodecSpecification extends JsrSpecification { + + def 'should round trip LocalDate successfully'() { + when: + def writer = encode(localDate) + + then: + writer.getDocument().get('key').asDateTime().value == millis + + when: + LocalDate actual = decode(writer) + + then: + localDate == actual + + where: + localDate | millis + LocalDate.of(2007, 10, 20) | 1_192_838_400_000 + LocalDate.ofEpochDay(0) | 0 + LocalDate.ofEpochDay(-99_999_999_999) | -99_999_999_999 * 86_400_000 + LocalDate.ofEpochDay(99_999_999_999) | 99_999_999_999 * 86_400_000 + } + + def 'should round trip different timezones the same'() { + given: + def defaultTimeZone = TimeZone.getDefault() + TimeZone.setDefault(TimeZone.getTimeZone(timeZone)) + def localDate = LocalDate.ofEpochDay(0) + + when: + def writer = encode(localDate) + + then: + writer.getDocument().get('key').asDateTime().value == 0 + + when: + def actual = decode(writer) + + then: + localDate == actual + + cleanup: + TimeZone.setDefault(defaultTimeZone) + + where: + timeZone << ['Pacific/Auckland', 'UTC', 'US/Hawaii'] + } + + def 'should wrap long overflow error in a CodecConfigurationException'() { + when: + encode(localDate) + + then: + def e = thrown(CodecConfigurationException) + e.getCause().getClass() == ArithmeticException + + where: + localDate << [ + LocalDate.MIN, + LocalDate.MAX + ] + } + + def 'should throw a CodecConfiguration exception if BsonType is invalid'() { + when: + decode(invalidDuration) + + then: + thrown(CodecConfigurationException) + + where: + invalidDuration << [ + BsonDocument.parse('{key: "10 Minutes"}'), + BsonDocument.parse('{key: 10}') + ] + } + + @Override + Codec getCodec() { + new LocalDateCodec() + } +} diff --git a/bson/src/test/unit/org/bson/codecs/jsr310/LocalDateTimeCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/jsr310/LocalDateTimeCodecSpecification.groovy new file mode 100644 index 00000000000..1ba101af7ae --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/jsr310/LocalDateTimeCodecSpecification.groovy @@ -0,0 +1,111 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310 + +import org.bson.BsonDocument +import org.bson.codecs.Codec +import org.bson.codecs.configuration.CodecConfigurationException +import spock.lang.IgnoreIf + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.ZoneOffset + +@IgnoreIf({ javaVersion < 1.8 }) +class LocalDateTimeCodecSpecification extends JsrSpecification { + + def 'should round trip LocalDateTime successfully'() { + when: + def writer = encode(localDateTime) + + then: + writer.getDocument().get('key').asDateTime().value == millis + + when: + LocalDateTime actual = decode(writer) + + then: + localDateTime == actual + + where: + localDateTime | millis + LocalDateTime.of(2007, 10, 20, 0, 35) | 1_192_840_500_000 + LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC) | 0 + LocalDateTime.ofEpochSecond(-99_999_999_999, 0, ZoneOffset.UTC) | -99_999_999_999 * 1000 + LocalDateTime.ofEpochSecond(99_999_999_999, 0, ZoneOffset.UTC) | 99_999_999_999 * 1000 + } + + def 'should round trip different timezones the same'() { + given: + def defaultTimeZone = TimeZone.getDefault() + TimeZone.setDefault(TimeZone.getTimeZone(timeZone)) + def localDate = LocalDateTime.of(LocalDate.ofEpochDay(0), LocalTime.MIDNIGHT) + + when: + def writer = encode(localDate) + + then: + writer.getDocument().get('key').asDateTime().value == 0 + + when: + def actual = decode(writer) + + then: + localDate == actual + + cleanup: + TimeZone.setDefault(defaultTimeZone) + + where: + timeZone << ['Pacific/Auckland', 'UTC', 'US/Hawaii'] + } + + def 'should wrap long overflow error in a CodecConfigurationException'() { + when: + encode(localDateTime) + + then: + def e = thrown(CodecConfigurationException) + e.getCause().getClass() == ArithmeticException + + where: + localDateTime << [ + LocalDateTime.MIN, + LocalDateTime.MAX + ] + } + + def 'should throw a CodecConfiguration exception if BsonType is invalid'() { + when: + decode(invalidDuration) + + then: + thrown(CodecConfigurationException) + + where: + invalidDuration << [ + BsonDocument.parse('{key: "10 Minutes"}'), + BsonDocument.parse('{key: 10}') + ] + } + + @Override + Codec getCodec() { + new LocalDateTimeCodec() + } +} diff --git a/bson/src/test/unit/org/bson/codecs/jsr310/LocalTimeCodecSpecification.groovy b/bson/src/test/unit/org/bson/codecs/jsr310/LocalTimeCodecSpecification.groovy new file mode 100644 index 00000000000..1ad1534ae00 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/jsr310/LocalTimeCodecSpecification.groovy @@ -0,0 +1,90 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.jsr310 + +import org.bson.BsonDocument +import org.bson.codecs.Codec +import org.bson.codecs.configuration.CodecConfigurationException +import spock.lang.IgnoreIf + +import java.time.LocalTime + +@IgnoreIf({ javaVersion < 1.8 }) +class LocalTimeCodecSpecification extends JsrSpecification { + + def 'should round trip LocalTime successfully'() { + when: + def writer = encode(localTime) + + then: + writer.getDocument().get('key').asDateTime().value == millis + + when: + LocalTime actual = decode(writer) + + then: + localTime == actual + + where: + localTime | millis + LocalTime.MIN | 0 + LocalTime.of(23, 59, 59, 999_000_000) | 86_399_999 + } + + def 'should round trip different timezones the same'() { + given: + def defaultTimeZone = TimeZone.getDefault() + TimeZone.setDefault(TimeZone.getTimeZone(timeZone)) + def localDate = LocalTime.MIDNIGHT + + when: + def writer = encode(localDate) + + then: + writer.getDocument().get('key').asDateTime().value == 0 + + when: + def actual = decode(writer) + + then: + localDate == actual + + cleanup: + TimeZone.setDefault(defaultTimeZone) + + where: + timeZone << ['Pacific/Auckland', 'UTC', 'US/Hawaii'] + } + + def 'should throw a CodecConfiguration exception if BsonType is invalid'() { + when: + decode(invalidDuration) + + then: + thrown(CodecConfigurationException) + + where: + invalidDuration << [ + BsonDocument.parse('{key: "10:00"}') + ] + } + + @Override + Codec getCodec() { + new LocalTimeCodec() + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/ClassModelBuilderTest.java b/bson/src/test/unit/org/bson/codecs/pojo/ClassModelBuilderTest.java new file mode 100644 index 00000000000..f83e4720f34 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/ClassModelBuilderTest.java @@ -0,0 +1,210 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.pojo.annotations.BsonProperty; +import org.bson.codecs.pojo.entities.ConcreteCollectionsModel; +import org.bson.codecs.pojo.entities.GenericHolderModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderModel; +import org.bson.codecs.pojo.entities.SimpleGenericsModel; +import org.bson.codecs.pojo.entities.SimpleIdModel; +import org.bson.codecs.pojo.entities.UpperBoundsModel; +import org.bson.codecs.pojo.entities.UpperBoundsConcreteModel; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +@SuppressWarnings("rawtypes") +public final class ClassModelBuilderTest { + + @Test + public void testDefaults() { + Class clazz = SimpleGenericsModel.class; + ClassModelBuilder builder = ClassModel.builder(clazz); + assertEquals(4, builder.getPropertyModelBuilders().size()); + for (Field field : clazz.getDeclaredFields()) { + assertEquals(field.getName(), builder.getProperty(field.getName()).getWriteName()); + } + + Map fieldNameToTypeParameterMap = new HashMap(); + fieldNameToTypeParameterMap.put("myIntegerField", TypeParameterMap.builder().build()); + fieldNameToTypeParameterMap.put("myGenericField", TypeParameterMap.builder().addIndex(0).build()); + fieldNameToTypeParameterMap.put("myListField", TypeParameterMap.builder().addIndex(0, 1).build()); + fieldNameToTypeParameterMap.put("myMapField", TypeParameterMap.builder().addIndex(1, 2).build()); + + assertEquals(fieldNameToTypeParameterMap, builder.getPropertyNameToTypeParameterMap()); + assertEquals(3, builder.getConventions().size()); + assertTrue(builder.getAnnotations().isEmpty()); + assertEquals(clazz, builder.getType()); + assertNull(builder.getIdPropertyName()); + assertFalse(builder.useDiscriminator()); + assertNull(builder.getDiscriminator()); + } + + @Test + public void testCanReflectObjectClass() { + Class clazz = Object.class; + ClassModelBuilder builder = ClassModel.builder(clazz); + + assertEquals(0, builder.getPropertyModelBuilders().size()); + assertTrue(builder.getPropertyNameToTypeParameterMap().isEmpty()); + assertEquals(3, builder.getConventions().size()); + assertTrue(builder.getAnnotations().isEmpty()); + assertEquals(clazz, builder.getType()); + assertNull(builder.getIdPropertyName()); + assertFalse(builder.useDiscriminator()); + assertNull(builder.getDiscriminator()); + } + + @Test + public void testMappedBoundedClasses() { + ClassModelBuilder builder = ClassModel.builder(UpperBoundsModel.class); + assertEquals(Number.class, builder.getProperty("myGenericField").getTypeData().getType()); + + builder = ClassModel.builder(UpperBoundsConcreteModel.class); + assertEquals(Long.class, builder.getProperty("myGenericField").getTypeData().getType()); + } + + @Test + public void testNestedGenericHolderModel() { + ClassModelBuilder builder = + ClassModel.builder(NestedGenericHolderModel.class); + assertEquals(GenericHolderModel.class, builder.getProperty("nested").getTypeData().getType()); + assertEquals(TypeData.builder(GenericHolderModel.class).addTypeParameter(TypeData.builder(String.class).build()).build(), + builder.getProperty("nested").getTypeData()); + } + + @Test + public void testFieldsMappedClassTypes() { + ClassModelBuilder builder = + ClassModel.builder(ConcreteCollectionsModel.class); + + assertEquals(Collection.class, builder.getProperty("collection").getTypeData().getType()); + assertEquals(List.class, builder.getProperty("list").getTypeData().getType()); + assertEquals(LinkedList.class, builder.getProperty("linked").getTypeData().getType()); + assertEquals(Map.class, builder.getProperty("map").getTypeData().getType()); + assertEquals(ConcurrentHashMap.class, builder.getProperty("concurrent").getTypeData().getType()); + } + + @Test + public void testOverrides() throws NoSuchFieldException { + ClassModelBuilder builder = ClassModel.builder(SimpleGenericsModel.class) + .annotations(TEST_ANNOTATIONS) + .conventions(TEST_CONVENTIONS) + .discriminatorKey("_cls") + .discriminator("myColl") + .enableDiscriminator(true) + .idPropertyName("myIntegerField") + .instanceCreatorFactory(TEST_INSTANCE_CREATOR_FACTORY); + + assertEquals(TEST_ANNOTATIONS, builder.getAnnotations()); + assertEquals(TEST_CONVENTIONS, builder.getConventions()); + assertEquals("myIntegerField", builder.getIdPropertyName()); + assertEquals(SimpleGenericsModel.class, builder.getType()); + assertTrue(builder.useDiscriminator()); + assertEquals("_cls", builder.getDiscriminatorKey()); + assertEquals("myColl", builder.getDiscriminator()); + assertEquals(TEST_INSTANCE_CREATOR_FACTORY, builder.getInstanceCreatorFactory()); + } + + @Test + public void testCanRemoveField() { + ClassModelBuilder builder = ClassModel.builder(SimpleGenericsModel.class) + .idPropertyName("ID"); + assertEquals(4, builder.getPropertyModelBuilders().size()); + builder.removeProperty("myIntegerField"); + assertEquals(3, builder.getPropertyModelBuilders().size()); + + builder.removeProperty("myIntegerField"); + assertEquals(3, builder.getPropertyModelBuilders().size()); + } + + @Test(expected = CodecConfigurationException.class) + public void testValidationIdProperty() { + ClassModel.builder(SimpleGenericsModel.class).idPropertyName("ID").build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testValidationDuplicateDocumentFieldName() { + ClassModelBuilder builder = ClassModel.builder(SimpleGenericsModel.class); + builder.getProperty("myIntegerField").writeName("myGenericField"); + builder.build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testDifferentTypeIdGenerator() { + ClassModel.builder(SimpleIdModel.class) + .idGenerator(new IdGenerator() { + @Override + public String generate() { + return "id"; + } + + @Override + public Class getType() { + return String.class; + } + }).build(); + } + + private static final List TEST_ANNOTATIONS = Collections.singletonList( + new BsonProperty() { + @Override + public Class annotationType() { + return BsonProperty.class; + } + + @Override + public String value() { + return ""; + } + + @Override + public boolean useDiscriminator() { + return true; + } + }); + + private static final List TEST_CONVENTIONS = Collections.singletonList( + new Convention() { + @Override + public void apply(final ClassModelBuilder builder) { + } + }); + + private static final InstanceCreatorFactory TEST_INSTANCE_CREATOR_FACTORY = + new InstanceCreatorFactory() { + @Override + public InstanceCreator create() { + return null; + } + }; +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/ClassModelTest.java b/bson/src/test/unit/org/bson/codecs/pojo/ClassModelTest.java new file mode 100644 index 00000000000..7cc687bf449 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/ClassModelTest.java @@ -0,0 +1,188 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.pojo.entities.CollectionNestedPojoModel; +import org.bson.codecs.pojo.entities.ConcreteAndNestedAbstractInterfaceModel; +import org.bson.codecs.pojo.entities.GenericHolderModel; +import org.bson.codecs.pojo.entities.InterfaceBasedModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderMapModel; +import org.bson.codecs.pojo.entities.PropertySelectionModel; +import org.bson.codecs.pojo.entities.ShapeHolderCircleModel; +import org.bson.codecs.pojo.entities.ShapeHolderModel; +import org.bson.codecs.pojo.entities.ShapeModelAbstract; +import org.bson.codecs.pojo.entities.ShapeModelCircle; +import org.bson.codecs.pojo.entities.SimpleGenericsModel; +import org.bson.codecs.pojo.entities.SimpleModel; +import org.bson.codecs.pojo.entities.conventions.AnnotationInheritedModel; +import org.bson.codecs.pojo.entities.conventions.AnnotationModel; +import org.junit.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public final class ClassModelTest { + + @Test + @SuppressWarnings("rawtypes") + public void testSimpleGenericsModel() { + ClassModel classModel = ClassModel.builder(SimpleGenericsModel.class).build(); + + assertEquals("SimpleGenericsModel", classModel.getName()); + assertEquals(SimpleGenericsModel.class, classModel.getType()); + assertFalse(classModel.useDiscriminator()); + assertEquals("_t", classModel.getDiscriminatorKey()); + assertEquals("org.bson.codecs.pojo.entities.SimpleGenericsModel", classModel.getDiscriminator()); + assertNull(classModel.getIdPropertyModel()); + assertEquals(4, classModel.getPropertyModels().size()); + assertTrue(classModel.getInstanceCreatorFactory() instanceof InstanceCreatorFactoryImpl); + } + + @Test + @SuppressWarnings("rawtypes") + public void testCollectionNestedPojoModelPropertyTypes() { + TypeData string = TypeData.builder(String.class).build(); + TypeData simple = TypeData.builder(SimpleModel.class).build(); + TypeData list = TypeData.builder(List.class).addTypeParameter(simple).build(); + TypeData listList = TypeData.builder(List.class).addTypeParameter(list).build(); + TypeData set = TypeData.builder(Set.class).addTypeParameter(simple).build(); + TypeData setSet = TypeData.builder(Set.class).addTypeParameter(set).build(); + TypeData map = TypeData.builder(Map.class).addTypeParameter(string).addTypeParameter(simple).build(); + TypeData listMap = TypeData.builder(List.class).addTypeParameter(map).build(); + TypeData mapMap = TypeData.builder(Map.class).addTypeParameter(string).addTypeParameter(map).build(); + TypeData mapList = TypeData.builder(Map.class).addTypeParameter(string).addTypeParameter(list).build(); + TypeData mapListMap = TypeData.builder(Map.class).addTypeParameter(string).addTypeParameter(listMap).build(); + TypeData mapSet = TypeData.builder(Map.class).addTypeParameter(string).addTypeParameter(set).build(); + TypeData listMapList = TypeData.builder(List.class).addTypeParameter(mapList).build(); + TypeData listMapSet = TypeData.builder(List.class).addTypeParameter(mapSet).build(); + + ClassModel classModel = ClassModel.builder(CollectionNestedPojoModel.class).build(); + assertEquals(list, classModel.getPropertyModel("listSimple").getTypeData()); + assertEquals(listList, classModel.getPropertyModel("listListSimple").getTypeData()); + + assertEquals(set, classModel.getPropertyModel("setSimple").getTypeData()); + assertEquals(setSet, classModel.getPropertyModel("setSetSimple").getTypeData()); + + assertEquals(map, classModel.getPropertyModel("mapSimple").getTypeData()); + assertEquals(mapMap, classModel.getPropertyModel("mapMapSimple").getTypeData()); + + assertEquals(mapList, classModel.getPropertyModel("mapListSimple").getTypeData()); + assertEquals(mapListMap, classModel.getPropertyModel("mapListMapSimple").getTypeData()); + assertEquals(mapSet, classModel.getPropertyModel("mapSetSimple").getTypeData()); + + assertEquals(listMap, classModel.getPropertyModel("listMapSimple").getTypeData()); + assertEquals(listMapList, classModel.getPropertyModel("listMapListSimple").getTypeData()); + assertEquals(listMapSet, classModel.getPropertyModel("listMapSetSimple").getTypeData()); + } + + @Test + @SuppressWarnings("rawtypes") + public void testWildcardModel() { + TypeData model = TypeData.builder(InterfaceBasedModel.class).build(); + TypeData wildcard = TypeData.builder(List.class).addTypeParameter(model).build(); + ClassModel classModel = ClassModel.builder(ConcreteAndNestedAbstractInterfaceModel.class).build(); + assertEquals(wildcard, classModel.getPropertyModel("wildcardList").getTypeData()); + } + + @Test + public void testPropertySelection() { + ClassModel classModel = ClassModel.builder(PropertySelectionModel.class).build(); + + assertEquals(2, classModel.getPropertyModels().size()); + assertNotNull(classModel.getPropertyModel("stringField")); + assertNotNull(classModel.getPropertyModel("finalStringField")); + } + + @Test + @SuppressWarnings("rawtypes") + public void testMappingConcreteGenericTypes() { + TypeData string = TypeData.builder(String.class).build(); + TypeData simple = TypeData.builder(SimpleModel.class).build(); + TypeData map = TypeData.builder(Map.class).addTypeParameter(string).addTypeParameter(simple).build(); + TypeData genericHolder = TypeData.builder(GenericHolderModel.class).addTypeParameter(map).build(); + + ClassModel classModel = ClassModel.builder(NestedGenericHolderMapModel.class).build(); + assertEquals(genericHolder, classModel.getPropertyModels().get(0).getTypeData()); + } + + @Test + @SuppressWarnings("rawtypes") + public void testMappingSimpleGenericsModelTypes() { + TypeData object = TypeData.builder(Object.class).build(); + TypeData integer = TypeData.builder(Integer.class).build(); + TypeData string = TypeData.builder(String.class).build(); + TypeData list = TypeData.builder(List.class).addTypeParameter(object).build(); + TypeData map = TypeData.builder(Map.class).addTypeParameter(string).addTypeParameter(object).build(); + + ClassModel classModel = ClassModel.builder(SimpleGenericsModel.class).build(); + assertEquals(integer, classModel.getPropertyModel("myIntegerField").getTypeData()); + assertEquals(object, classModel.getPropertyModel("myGenericField").getTypeData()); + assertEquals(list, classModel.getPropertyModel("myListField").getTypeData()); + assertEquals(map, classModel.getPropertyModel("myMapField").getTypeData()); + } + + @Test + public void testAnnotationModel() { + ClassModel classModel = ClassModel.builder(AnnotationModel.class).build(); + PropertyModel propertyModel = classModel.getIdPropertyModel(); + + assertEquals("AnnotationModel", classModel.getName()); + assertEquals(AnnotationModel.class, classModel.getType()); + assertTrue(classModel.useDiscriminator()); + assertEquals("_cls", classModel.getDiscriminatorKey()); + assertEquals("MyAnnotationModel", classModel.getDiscriminator()); + assertEquals(propertyModel, classModel.getIdPropertyModel()); + assertEquals(3, classModel.getPropertyModels().size()); + assertEquals(propertyModel, classModel.getPropertyModel("customId")); + assertTrue(classModel.getInstanceCreatorFactory() instanceof InstanceCreatorFactoryImpl); + } + + @Test + public void testInheritedClassAnnotations() { + ClassModel classModel = ClassModel.builder(AnnotationInheritedModel.class).build(); + assertTrue(classModel.useDiscriminator()); + assertEquals("_cls", classModel.getDiscriminatorKey()); + assertEquals("org.bson.codecs.pojo.entities.conventions.AnnotationInheritedModel", classModel.getDiscriminator()); + + assertEquals(2, classModel.getPropertyModels().size()); + + PropertyModel propertyModel = classModel.getPropertyModel("customId"); + assertEquals(propertyModel, classModel.getIdPropertyModel()); + + propertyModel = classModel.getPropertyModel("child"); + assertTrue(propertyModel.useDiscriminator()); + } + + @Test + public void testOverridePropertyWithSubclass() { + ClassModel classModel = ClassModel.builder(ShapeHolderModel.class).build(); + assertEquals(1, classModel.getPropertyModels().size()); + assertEquals(ShapeModelAbstract.class, classModel.getPropertyModels().get(0).getTypeData().getType()); + + ClassModel overriddenImplementationClassModel = ClassModel.builder(ShapeHolderCircleModel.class).build(); + assertEquals(1, classModel.getPropertyModels().size()); + assertEquals(ShapeModelCircle.class, overriddenImplementationClassModel.getPropertyModels().get(0).getTypeData().getType()); + } + +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/ConventionsTest.java b/bson/src/test/unit/org/bson/codecs/pojo/ConventionsTest.java new file mode 100644 index 00000000000..e39dc862712 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/ConventionsTest.java @@ -0,0 +1,226 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.pojo.entities.SimpleModel; +import org.bson.codecs.pojo.entities.conventions.AnnotationBsonPropertyIdModel; +import org.bson.codecs.pojo.entities.conventions.AnnotationCollision; +import org.bson.codecs.pojo.entities.conventions.AnnotationDefaultsModel; +import org.bson.codecs.pojo.entities.conventions.AnnotationNameCollision; +import org.bson.codecs.pojo.entities.conventions.AnnotationWithObjectIdModel; +import org.bson.codecs.pojo.entities.conventions.AnnotationWriteCollision; +import org.bson.codecs.pojo.entities.conventions.BsonIgnoreDuplicatePropertyMultipleTypes; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidConstructorModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidMethodModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidMethodReturnTypeModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidMultipleConstructorsModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidMultipleCreatorsModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidMultipleStaticCreatorsModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidTypeConstructorModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidTypeMethodModel; +import org.junit.Test; + +import static java.util.Collections.singletonList; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static org.bson.codecs.pojo.Conventions.ANNOTATION_CONVENTION; +import static org.bson.codecs.pojo.Conventions.CLASS_AND_PROPERTY_CONVENTION; +import static org.bson.codecs.pojo.Conventions.DEFAULT_CONVENTIONS; +import static org.bson.codecs.pojo.Conventions.NO_CONVENTIONS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public final class ConventionsTest { + + @Test + public void testDefaultConventions() { + ClassModel classModel = ClassModel.builder(AnnotationWithObjectIdModel.class) + .conventions(DEFAULT_CONVENTIONS).build(); + + assertTrue(classModel.useDiscriminator()); + assertEquals("_cls", classModel.getDiscriminatorKey()); + assertEquals("MyAnnotationModel", classModel.getDiscriminator()); + + assertEquals(3, classModel.getPropertyModels().size()); + PropertyModel idPropertyModel = classModel.getIdPropertyModel(); + assertNotNull(idPropertyModel); + assertEquals("customId", idPropertyModel.getName()); + assertEquals("_id", idPropertyModel.getWriteName()); + assertEquals(classModel.getIdPropertyModelHolder().getIdGenerator(), IdGenerators.OBJECT_ID_GENERATOR); + + PropertyModel childPropertyModel = classModel.getPropertyModel("child"); + assertNotNull(childPropertyModel); + assertFalse(childPropertyModel.useDiscriminator()); + + PropertyModel renamedPropertyModel = classModel.getPropertyModel("alternative"); + assertEquals("renamed", renamedPropertyModel.getReadName()); + assertEquals("renamed", renamedPropertyModel.getWriteName()); + } + + @Test + public void testAnnotationDefaults() { + ClassModel classModel = ClassModel.builder(AnnotationDefaultsModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + + assertTrue(classModel.useDiscriminator()); + assertEquals("_t", classModel.getDiscriminatorKey()); + assertEquals("AnnotationDefaultsModel", classModel.getDiscriminator()); + + assertEquals(2, classModel.getPropertyModels().size()); + PropertyModel idPropertyModel = classModel.getIdPropertyModel(); + assertNotNull(idPropertyModel); + assertEquals("customId", idPropertyModel.getName()); + assertEquals("_id", idPropertyModel.getWriteName()); + + PropertyModel childPropertyModel = classModel.getPropertyModel("child"); + assertNotNull(childPropertyModel); + assertFalse(childPropertyModel.useDiscriminator()); + } + + @Test + public void testBsonPropertyIdModelModel() { + ClassModel classModel = ClassModel.builder(AnnotationBsonPropertyIdModel.class) + .conventions(DEFAULT_CONVENTIONS).build(); + + assertFalse(classModel.useDiscriminator()); + assertEquals(1, classModel.getPropertyModels().size()); + assertNull(classModel.getIdPropertyModel()); + } + + @Test + @SuppressWarnings("unchecked") + public void testClassAndFieldConventionDoesNotOverwrite() { + ClassModelBuilder builder = ClassModel.builder(SimpleModel.class) + .enableDiscriminator(true) + .discriminatorKey("_cls") + .discriminator("Simples") + .conventions(singletonList(CLASS_AND_PROPERTY_CONVENTION)) + .instanceCreatorFactory(new InstanceCreatorFactory() { + @Override + public InstanceCreator create() { + return null; + } + }); + + PropertyModelBuilder propertyModelBuilder = (PropertyModelBuilder) builder.getProperty("integerField"); + propertyModelBuilder.writeName("id") + .propertySerialization(new PropertyModelSerializationImpl()) + .propertyAccessor(new PropertyAccessorTest()); + + PropertyModelBuilder propertyModelBuilder2 = (PropertyModelBuilder) builder.getProperty("stringField"); + propertyModelBuilder2.writeName("_id") + .propertySerialization(new PropertyModelSerializationImpl()) + .propertyAccessor(new PropertyAccessorTest()); + + ClassModel classModel = builder.idPropertyName("stringField").build(); + + assertTrue(classModel.useDiscriminator()); + assertEquals("_cls", classModel.getDiscriminatorKey()); + assertEquals("Simples", classModel.getDiscriminator()); + + assertEquals(2, classModel.getPropertyModels().size()); + PropertyModel idPropertyModel = classModel.getIdPropertyModel(); + assertEquals("stringField", idPropertyModel.getName()); + assertEquals("_id", idPropertyModel.getWriteName()); + assertNull(idPropertyModel.useDiscriminator()); + } + + @Test(expected = CodecConfigurationException.class) + public void testAnnotationCollision() { + ClassModel.builder(AnnotationCollision.class).conventions(DEFAULT_CONVENTIONS).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testAnnotationWriteCollision() { + ClassModel.builder(AnnotationWriteCollision.class).conventions(DEFAULT_CONVENTIONS).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testAnnotationNameCollision() { + ClassModel.builder(AnnotationNameCollision.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorInvalidConstructorModel() { + ClassModel.builder(CreatorInvalidConstructorModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorInvalidMethodModel() { + ClassModel.builder(CreatorInvalidMethodModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorInvalidMultipleConstructorsModel() { + ClassModel.builder(CreatorInvalidMultipleConstructorsModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorInvalidMultipleCreatorsModel() { + ClassModel.builder(CreatorInvalidMultipleCreatorsModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorInvalidMultipleStaticCreatorsModel() { + ClassModel.builder(CreatorInvalidMultipleStaticCreatorsModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorInvalidMethodReturnTypeModel() { + ClassModel.builder(CreatorInvalidMethodReturnTypeModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorInvalidTypeConstructorModel() { + ClassModel.builder(CreatorInvalidTypeConstructorModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorInvalidTypeMethodModel() { + ClassModel.builder(CreatorInvalidTypeMethodModel.class) + .conventions(singletonList(ANNOTATION_CONVENTION)).build(); + } + + @Test(expected = CodecConfigurationException.class) + public void testBsonIgnoreDuplicatePropertyMultipleTypesModel() { + ClassModel.builder(BsonIgnoreDuplicatePropertyMultipleTypes.class) + .conventions(NO_CONVENTIONS).build(); + } + + private class PropertyAccessorTest implements PropertyAccessor { + + @Override + public T get(final S instance) { + return null; + } + + @Override + public void set(final S instance, final T value) { + + } + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/IdGeneratorsTest.java b/bson/src/test/unit/org/bson/codecs/pojo/IdGeneratorsTest.java new file mode 100644 index 00000000000..da5d33a02b7 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/IdGeneratorsTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonObjectId; +import org.bson.types.ObjectId; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IdGeneratorsTest { + + @Test + public void testObjectIdGenerator() { + IdGenerator idGenerator = IdGenerators.OBJECT_ID_GENERATOR; + + assertEquals(ObjectId.class, idGenerator.getType()); + assertEquals(ObjectId.class, idGenerator.generate().getClass()); + } + + @Test + public void testBsonObjectIdGenerator() { + IdGenerator idGenerator = IdGenerators.BSON_OBJECT_ID_GENERATOR; + + assertEquals(BsonObjectId.class, idGenerator.getType()); + assertEquals(BsonObjectId.class, idGenerator.generate().getClass()); + } + +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/PojoCodecProviderTest.java b/bson/src/test/unit/org/bson/codecs/pojo/PojoCodecProviderTest.java new file mode 100644 index 00000000000..22ce1ef19c4 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/PojoCodecProviderTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.Codec; +import org.bson.codecs.ValueCodecProvider; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.entities.SimpleModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInvalidMethodModel; +import org.junit.Test; + +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public final class PojoCodecProviderTest extends PojoTestCase { + + @Test + public void testClassNotFound() { + PojoCodecProvider provider = PojoCodecProvider.builder().build(); + CodecRegistry registry = fromProviders(provider, new ValueCodecProvider()); + Codec codec = provider.get(SimpleModel.class, registry); + assertNull(codec); + } + + @Test + public void testPackageLessClasses() { + PojoCodecProvider provider = PojoCodecProvider.builder().build(); + CodecRegistry registry = fromProviders(provider, new ValueCodecProvider()); + Codec codec = provider.get(byte.class, registry); + assertNull(codec); + } + + @Test + public void testAutomatic() { + PojoCodecProvider provider = PojoCodecProvider.builder().automatic(true).build(); + CodecRegistry registry = fromProviders(provider, new ValueCodecProvider()); + Codec codec = provider.get(SimpleModel.class, registry); + assertNotNull(codec); + } + + @Test + public void testAutomaticNoProperty() { + PojoCodecProvider provider = PojoCodecProvider.builder().automatic(true).build(); + CodecRegistry registry = fromProviders(provider); + Codec codec = provider.get(Integer.class, registry); + assertNull(codec); + } + + @Test + public void testAutomaticInvalidModel() { + PojoCodecProvider provider = PojoCodecProvider.builder().automatic(true).build(); + CodecRegistry registry = fromProviders(provider, new ValueCodecProvider()); + Codec codec = provider.get(CreatorInvalidMethodModel.class, registry); + assertNull(codec); + } + +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/PojoCustomTest.java b/bson/src/test/unit/org/bson/codecs/pojo/PojoCustomTest.java new file mode 100644 index 00000000000..1d9213a0c81 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/PojoCustomTest.java @@ -0,0 +1,633 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.LongCodec; +import org.bson.codecs.MapCodec; +import org.bson.codecs.configuration.CodecConfigurationException; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.entities.AbstractInterfaceModel; +import org.bson.codecs.pojo.entities.AsymmetricalCreatorModel; +import org.bson.codecs.pojo.entities.AsymmetricalIgnoreModel; +import org.bson.codecs.pojo.entities.AsymmetricalModel; +import org.bson.codecs.pojo.entities.ConcreteAndNestedAbstractInterfaceModel; +import org.bson.codecs.pojo.entities.ConcreteCollectionsModel; +import org.bson.codecs.pojo.entities.ConcreteStandAloneAbstractInterfaceModel; +import org.bson.codecs.pojo.entities.ConstructorNotPublicModel; +import org.bson.codecs.pojo.entities.ConventionModel; +import org.bson.codecs.pojo.entities.ConverterModel; +import org.bson.codecs.pojo.entities.CustomPropertyCodecOptionalModel; +import org.bson.codecs.pojo.entities.GenericTreeModel; +import org.bson.codecs.pojo.entities.InterfaceBasedModel; +import org.bson.codecs.pojo.entities.InvalidCollectionModel; +import org.bson.codecs.pojo.entities.InvalidGetterAndSetterModel; +import org.bson.codecs.pojo.entities.InvalidMapModel; +import org.bson.codecs.pojo.entities.InvalidMapPropertyCodecProvider; +import org.bson.codecs.pojo.entities.InvalidSetterArgsModel; +import org.bson.codecs.pojo.entities.MapStringObjectModel; +import org.bson.codecs.pojo.entities.NestedSimpleIdModel; +import org.bson.codecs.pojo.entities.Optional; +import org.bson.codecs.pojo.entities.OptionalPropertyCodecProvider; +import org.bson.codecs.pojo.entities.PrimitivesModel; +import org.bson.codecs.pojo.entities.PrivateSetterFieldModel; +import org.bson.codecs.pojo.entities.SimpleEnum; +import org.bson.codecs.pojo.entities.SimpleEnumModel; +import org.bson.codecs.pojo.entities.SimpleIdImmutableModel; +import org.bson.codecs.pojo.entities.SimpleIdModel; +import org.bson.codecs.pojo.entities.SimpleModel; +import org.bson.codecs.pojo.entities.SimpleNestedPojoModel; +import org.bson.codecs.pojo.entities.UpperBoundsModel; +import org.bson.codecs.pojo.entities.conventions.AnnotationModel; +import org.bson.codecs.pojo.entities.conventions.CollectionsGetterImmutableModel; +import org.bson.codecs.pojo.entities.conventions.CollectionsGetterMutableModel; +import org.bson.codecs.pojo.entities.conventions.CollectionsGetterNonEmptyModel; +import org.bson.codecs.pojo.entities.conventions.CollectionsGetterNullModel; +import org.bson.codecs.pojo.entities.conventions.CreatorConstructorPrimitivesModel; +import org.bson.codecs.pojo.entities.conventions.CreatorConstructorThrowsExceptionModel; +import org.bson.codecs.pojo.entities.conventions.CreatorMethodThrowsExceptionModel; +import org.bson.codecs.pojo.entities.conventions.MapGetterImmutableModel; +import org.bson.codecs.pojo.entities.conventions.MapGetterMutableModel; +import org.bson.codecs.pojo.entities.conventions.MapGetterNonEmptyModel; +import org.bson.codecs.pojo.entities.conventions.MapGetterNullModel; +import org.bson.types.ObjectId; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.bson.codecs.configuration.CodecRegistries.fromCodecs; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; +import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; +import static org.bson.codecs.pojo.Conventions.DEFAULT_CONVENTIONS; +import static org.bson.codecs.pojo.Conventions.NO_CONVENTIONS; +import static org.bson.codecs.pojo.Conventions.SET_PRIVATE_FIELDS_CONVENTION; +import static org.bson.codecs.pojo.Conventions.USE_GETTERS_FOR_SETTERS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public final class PojoCustomTest extends PojoTestCase { + + @Test + public void testRegisterClassModelPreferredOverClass() { + ClassModel classModel = ClassModel.builder(SimpleModel.class).enableDiscriminator(true).build(); + PojoCodecProvider.Builder builder = PojoCodecProvider.builder().automatic(true).register(SimpleModel.class).register(classModel); + + roundTrip(builder, getSimpleModel(), "{_t: 'org.bson.codecs.pojo.entities.SimpleModel', 'integerField': 42," + + "'stringField': 'myString'}"); + } + + @Test + public void testPackageDiscriminator() { + AnnotationModel model = new AnnotationModel("myId", new AnnotationModel("child", null, null), + new AnnotationModel("alternative", null, null)); + + roundTrip(PojoCodecProvider.builder().register("org.bson.codecs.pojo.entities", "org.bson.codecs.pojo.entities.conventions"), model, + "{_id: 'myId', _cls: 'MyAnnotationModel', renamed: {_id: 'alternative'}, child: {_id: 'child'}}"); + } + + @Test + public void testAsymmetricalModel() { + AsymmetricalModel model = new AsymmetricalModel(42); + + encodesTo(getPojoCodecProviderBuilder(AsymmetricalModel.class), model, "{foo: 42}"); + decodesTo(getPojoCodecProviderBuilder(AsymmetricalModel.class), "{bar: 42}", model); + } + + @Test + public void testAsymmetricalCreatorModel() { + AsymmetricalCreatorModel model = new AsymmetricalCreatorModel("Foo", "Bar"); + + encodesTo(getPojoCodecProviderBuilder(AsymmetricalCreatorModel.class), model, "{baz: 'FooBar'}"); + decodesTo(getPojoCodecProviderBuilder(AsymmetricalCreatorModel.class), "{a: 'Foo', b: 'Bar'}", model); + } + + @Test + public void testAsymmetricalIgnoreModel() { + AsymmetricalIgnoreModel encode = new AsymmetricalIgnoreModel("property", "getter", "setter", "getterAndSetter"); + AsymmetricalIgnoreModel decoded = new AsymmetricalIgnoreModel(); + decoded.setGetterIgnored("getter"); + + encodesTo(getPojoCodecProviderBuilder(AsymmetricalIgnoreModel.class), encode, "{'setterIgnored': 'setter'}"); + decodesTo(getPojoCodecProviderBuilder(AsymmetricalIgnoreModel.class), + "{'propertyIgnored': 'property', 'getterIgnored': 'getter', 'setterIgnored': 'setter', " + + "'getterAndSetterIgnored': 'getterAndSetter'}", decoded); + } + + @Test + public void testConventionsEmpty() { + ClassModelBuilder classModel = ClassModel.builder(ConventionModel.class).conventions(NO_CONVENTIONS); + ClassModelBuilder nestedClassModel = ClassModel.builder(SimpleModel.class).conventions(NO_CONVENTIONS); + + roundTrip(getPojoCodecProviderBuilder(classModel, nestedClassModel), getConventionModel(), + "{'myFinalField': 10, 'myIntField': 10, 'customId': 'id'," + + "'child': {'myFinalField': 10, 'myIntField': 10, 'customId': 'child'," + + " 'simpleModel': {'integerField': 42, 'stringField': 'myString' } } }"); + } + + @Test + public void testConventionsCustom() { + List conventions = Collections.singletonList( + new Convention() { + @Override + public void apply(final ClassModelBuilder classModelBuilder) { + for (PropertyModelBuilder fieldModelBuilder : classModelBuilder.getPropertyModelBuilders()) { + fieldModelBuilder.discriminatorEnabled(false); + fieldModelBuilder.readName( + fieldModelBuilder.getName() + .replaceAll("([^_A-Z])([A-Z])", "$1_$2").toLowerCase()); + fieldModelBuilder.writeName( + fieldModelBuilder.getName() + .replaceAll("([^_A-Z])([A-Z])", "$1_$2").toLowerCase()); + } + if (classModelBuilder.getProperty("customId") != null) { + classModelBuilder.idPropertyName("customId"); + } + classModelBuilder.enableDiscriminator(true); + classModelBuilder.discriminatorKey("_cls"); + classModelBuilder.discriminator(classModelBuilder.getType().getSimpleName() + .replaceAll("([^_A-Z])([A-Z])", "$1_$2").toLowerCase()); + } + }); + + ClassModelBuilder classModel = ClassModel.builder(ConventionModel.class).conventions(conventions); + ClassModelBuilder nestedClassModel = ClassModel.builder(SimpleModel.class).conventions(conventions); + + roundTrip(getPojoCodecProviderBuilder(classModel, nestedClassModel), getConventionModel(), + "{ '_id': 'id', '_cls': 'convention_model', 'my_final_field': 10, 'my_int_field': 10," + + "'child': { '_id': 'child', 'my_final_field': 10, 'my_int_field': 10, " + + " 'simple_model': {'integer_field': 42, 'string_field': 'myString' } } }"); + } + + @Test + public void testIdGeneratorMutable() { + SimpleIdModel simpleIdModel = new SimpleIdModel(42, "myString"); + assertNull(simpleIdModel.getId()); + ClassModelBuilder builder = ClassModel.builder(SimpleIdModel.class).idGenerator(new ObjectIdGenerator()); + + roundTrip(getPojoCodecProviderBuilder(builder), simpleIdModel, "{'integerField': 42, 'stringField': 'myString'}"); + assertNull(simpleIdModel.getId()); + + encodesTo(getPojoCodecProviderBuilder(builder), simpleIdModel, + "{'_id': {'$oid': '123412341234123412341234'}, 'integerField': 42, 'stringField': 'myString'}", true); + assertEquals(new ObjectId("123412341234123412341234"), simpleIdModel.getId()); + } + + @Test + public void testIdGeneratorImmutable() { + SimpleIdImmutableModel simpleIdModelNoId = new SimpleIdImmutableModel(42, "myString"); + SimpleIdImmutableModel simpleIdModelWithId = new SimpleIdImmutableModel(new ObjectId("123412341234123412341234"), 42, "myString"); + ClassModelBuilder builder = ClassModel.builder(SimpleIdImmutableModel.class) + .idGenerator(new ObjectIdGenerator()); + String json = "{'_id': {'$oid': '123412341234123412341234'}, 'integerField': 42, 'stringField': 'myString'}"; + + encodesTo(getPojoCodecProviderBuilder(builder), simpleIdModelNoId, json, true); + decodesTo(getPojoCodecProviderBuilder(builder), json, simpleIdModelWithId); + } + + @Test + public void testIdGeneratorNonObjectId() { + NestedSimpleIdModel nestedSimpleIdModel = new NestedSimpleIdModel(new SimpleIdModel(42, "myString")); + assertNull(nestedSimpleIdModel.getId()); + ClassModelBuilder builder = ClassModel.builder(NestedSimpleIdModel.class) + .idGenerator(new IdGenerator() { + @Override + public String generate() { + return "a"; + } + + @Override + public Class getType() { + return String.class; + } + }); + + roundTrip(getPojoCodecProviderBuilder(builder, ClassModel.builder(SimpleIdModel.class)), nestedSimpleIdModel, + "{'nestedSimpleIdModel': {'integerField': 42, 'stringField': 'myString'}}"); + assertNull(nestedSimpleIdModel.getId()); + + encodesTo(getPojoCodecProviderBuilder(builder, ClassModel.builder(SimpleIdModel.class)), nestedSimpleIdModel, + "{'_id': 'a', 'nestedSimpleIdModel': {'integerField': 42, 'stringField': 'myString'}}", true); + assertEquals("a", nestedSimpleIdModel.getId()); + } + + @Test + public void testSetPrivateFieldConvention() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(PrivateSetterFieldModel.class); + ArrayList conventions = new ArrayList(DEFAULT_CONVENTIONS); + conventions.add(SET_PRIVATE_FIELDS_CONVENTION); + builder.conventions(conventions); + + roundTrip(builder, new PrivateSetterFieldModel(1, "2", asList("a", "b")), + "{'someMethod': 'some method', 'integerField': 1, 'stringField': '2', listField: ['a', 'b']}"); + } + + @Test + public void testUseGettersForSettersConvention() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(CollectionsGetterMutableModel.class, MapGetterMutableModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + roundTrip(builder, new CollectionsGetterMutableModel(asList(1, 2)), "{listField: [1, 2]}"); + roundTrip(builder, new MapGetterMutableModel(Collections.singletonMap("a", 3)), "{mapField: {a: 3}}"); + } + + @Test + public void testWithWildcardListField() { + ClassModel interfaceBasedModelClassModel = + ClassModel.builder(InterfaceBasedModel.class).enableDiscriminator(true).build(); + PojoCodecProvider.Builder builder = PojoCodecProvider.builder().automatic(true) + .register(interfaceBasedModelClassModel) + .register(AbstractInterfaceModel.class, ConcreteStandAloneAbstractInterfaceModel.class, + ConcreteAndNestedAbstractInterfaceModel.class); + + roundTrip(builder, + new ConcreteAndNestedAbstractInterfaceModel("A", + singletonList(new ConcreteStandAloneAbstractInterfaceModel("B"))), + "{'_t': 'org.bson.codecs.pojo.entities.ConcreteAndNestedAbstractInterfaceModel', 'name': 'A', " + + " 'wildcardList': [{'_t': 'org.bson.codecs.pojo.entities.ConcreteStandAloneAbstractInterfaceModel', " + + "'name': 'B'}]}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testUseGettersForSettersConventionInvalidTypeForCollection() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(CollectionsGetterMutableModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + decodingShouldFail(getCodec(builder, CollectionsGetterMutableModel.class), "{listField: ['1', '2']}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testUseGettersForSettersConventionInvalidTypeForMap() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(MapGetterMutableModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + decodingShouldFail(getCodec(builder, MapGetterMutableModel.class), "{mapField: {a: '1'}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testUseGettersForSettersConventionImmutableCollection() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(CollectionsGetterImmutableModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + roundTrip(builder, new CollectionsGetterImmutableModel(asList(1, 2)), "{listField: [1, 2]}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testUseGettersForSettersConventionImmutableMap() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(MapGetterImmutableModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + roundTrip(builder, new MapGetterImmutableModel(Collections.singletonMap("a", 3)), "{mapField: {a: 3}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testUseGettersForSettersConventionNullCollection() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(CollectionsGetterNullModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + roundTrip(builder, new CollectionsGetterNullModel(asList(1, 2)), "{listField: [1, 2]}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testUseGettersForSettersConventionNullMap() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(MapGetterNullModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + roundTrip(builder, new MapGetterNullModel(Collections.singletonMap("a", 3)), "{mapField: {a: 3}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testUseGettersForSettersConventionNotEmptyCollection() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(CollectionsGetterNonEmptyModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + roundTrip(builder, new CollectionsGetterNonEmptyModel(asList(1, 2)), "{listField: [1, 2]}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testUseGettersForSettersConventionNotEmptyMap() { + PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(MapGetterNonEmptyModel.class) + .conventions(getDefaultAndUseGettersConvention()); + + roundTrip(builder, new MapGetterNonEmptyModel(Collections.singletonMap("a", 3)), "{mapField: {a: 3}}"); + } + + @Test + public void testEnumSupportWithCustomCodec() { + CodecRegistry registry = fromRegistries(getCodecRegistry(getPojoCodecProviderBuilder(SimpleEnumModel.class)), + fromCodecs(new SimpleEnumCodec())); + roundTrip(registry, new SimpleEnumModel(SimpleEnum.BRAVO), "{ 'myEnum': 1 }"); + } + + @Test + @SuppressWarnings("unchecked") + public void testCustomCodec() { + ObjectId id = new ObjectId(); + ConverterModel model = new ConverterModel(id.toHexString(), "myName"); + + ClassModelBuilder classModel = ClassModel.builder(ConverterModel.class); + PropertyModelBuilder idPropertyModelBuilder = (PropertyModelBuilder) classModel.getProperty("id"); + idPropertyModelBuilder.codec(new StringToObjectIdCodec()); + + roundTrip(getPojoCodecProviderBuilder(classModel), model, + format("{'_id': {'$oid': '%s'}, 'name': 'myName'}", id.toHexString())); + } + + @Test + @SuppressWarnings("unchecked") + public void testCustomPropertySerializer() { + SimpleModel model = getSimpleModel(); + model.setIntegerField(null); + ClassModelBuilder classModel = ClassModel.builder(SimpleModel.class); + ((PropertyModelBuilder) classModel.getProperty("integerField")) + .propertySerialization(new PropertySerialization() { + @Override + public boolean shouldSerialize(final Integer value) { + return true; + } + }); + + roundTrip(getPojoCodecProviderBuilder(classModel), model, "{'integerField': null, 'stringField': 'myString'}"); + } + + @Test + @SuppressWarnings("unchecked") + public void testCanHandleNullValuesForNestedModels() { + SimpleNestedPojoModel model = getSimpleNestedPojoModel(); + model.setSimple(null); + ClassModelBuilder classModel = ClassModel.builder(SimpleNestedPojoModel.class); + ((PropertyModelBuilder) classModel.getProperty("simple")) + .propertySerialization(new PropertySerialization() { + @Override + public boolean shouldSerialize(final SimpleModel value) { + return true; + } + }); + ClassModelBuilder classModelSimple = ClassModel.builder(SimpleModel.class); + + roundTrip(getPojoCodecProviderBuilder(classModel, classModelSimple), model, "{'simple': null}"); + } + + @Test + @SuppressWarnings("unchecked") + public void testCanHandleNullValuesForCollectionsAndMaps() { + ConcreteCollectionsModel model = getConcreteCollectionsModel(); + model.setCollection(null); + model.setMap(null); + + ClassModelBuilder classModel = + ClassModel.builder(ConcreteCollectionsModel.class); + ((PropertyModelBuilder>) classModel.getProperty("collection")) + .propertySerialization(new PropertySerialization>() { + @Override + public boolean shouldSerialize(final Collection value) { + return true; + } + }); + ((PropertyModelBuilder>) classModel.getProperty("map")) + .propertySerialization(new PropertySerialization>() { + @Override + public boolean shouldSerialize(final Map value) { + return true; + } + }); + + roundTrip(getPojoCodecProviderBuilder(classModel), model, + "{'collection': null, 'list': [4, 5, 6], 'linked': [7, 8, 9], 'map': null," + + "'concurrent': {'D': 4.4, 'E': 5.5, 'F': 6.6}}"); + } + + @Test + public void testCanHandleExtraData() { + decodesTo(getCodec(SimpleModel.class), "{'integerField': 42, 'stringField': 'myString', 'extraFieldA': 1, 'extraFieldB': 2}", + getSimpleModel()); + } + + @Test + public void testDataCanHandleMissingData() { + SimpleModel model = getSimpleModel(); + model.setIntegerField(null); + + decodesTo(getCodec(SimpleModel.class), "{'_t': 'SimpleModel', 'stringField': 'myString'}", model); + } + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + public void testCanHandleTopLevelGenericIfHasCodec() { + UpperBoundsModel model = new UpperBoundsModel(5L); + + ClassModelBuilder classModelBuilder = ClassModel.builder(UpperBoundsModel.class); + ((PropertyModelBuilder) classModelBuilder.getProperty("myGenericField")).codec(new LongCodec()); + + roundTrip(getPojoCodecProviderBuilder(classModelBuilder), model, + "{'myGenericField': {'$numberLong': '5'}}"); + } + + @Test + public void testCustomRegisteredPropertyCodecWithValue() { + CustomPropertyCodecOptionalModel model = new CustomPropertyCodecOptionalModel(Optional.of("foo")); + roundTrip(getPojoCodecProviderBuilder(CustomPropertyCodecOptionalModel.class).register(new OptionalPropertyCodecProvider()), + model, "{'optionalField': 'foo'}"); + } + + @Test + public void testCustomRegisteredPropertyCodecOmittedValue() { + CustomPropertyCodecOptionalModel model = new CustomPropertyCodecOptionalModel(Optional.empty()); + roundTrip(getPojoCodecProviderBuilder(CustomPropertyCodecOptionalModel.class).register(new OptionalPropertyCodecProvider()), + model, "{'optionalField': null}"); + } + + @Test + public void testMapStringObjectModel() { + MapStringObjectModel model = new MapStringObjectModel(new HashMap(Document.parse("{a : 1, b: 'b', c: [1, 2, 3]}"))); + CodecRegistry registry = fromRegistries(fromCodecs(new MapCodec()), + fromProviders(getPojoCodecProviderBuilder(MapStringObjectModel.class).build())); + roundTrip(registry, model, "{ map: {a : 1, b: 'b', c: [1, 2, 3]}}"); + } + + @Test(expected = UnsupportedOperationException.class) + public void testMapStringObjectModelWithObjectCodec() { + MapStringObjectModel model = new MapStringObjectModel(new HashMap(Document.parse("{a : 1, b: 'b', c: [1, 2, 3]}"))); + CodecRegistry registry = fromRegistries(fromCodecs(new MapCodec()), fromCodecs(new ObjectCodec()), + fromProviders(getPojoCodecProviderBuilder(MapStringObjectModel.class).build())); + roundTrip(registry, model, "{ map: {a : 1, b: 'b', c: [1, 2, 3]}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testEncodingInvalidMapModel() { + encodesTo(getPojoCodecProviderBuilder(InvalidMapModel.class), getInvalidMapModel(), "{'invalidMap': {'1': 1, '2': 2}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testDecodingInvalidMapModel() { + try { + decodingShouldFail(getCodec(InvalidMapModel.class), "{'invalidMap': {'1': 1, '2': 2}}"); + } catch (CodecConfigurationException e) { + assertTrue(e.getMessage().startsWith("Failed to decode 'InvalidMapModel'. Decoding 'invalidMap' errored with:")); + throw e; + } + } + + @Test(expected = CodecConfigurationException.class) + public void testEncodingInvalidCollectionModel() { + try { + encodesTo(getPojoCodecProviderBuilder(InvalidCollectionModel.class), new InvalidCollectionModel(asList(1, 2, 3)), + "{collectionField: [1, 2, 3]}"); + } catch (CodecConfigurationException e) { + assertTrue(e.getMessage().startsWith("Failed to encode 'InvalidCollectionModel'. Encoding 'collectionField' errored with:")); + throw e; + } + } + + @Test + public void testInvalidMapModelWithCustomPropertyCodecProvider() { + encodesTo(getPojoCodecProviderBuilder(InvalidMapModel.class).register(new InvalidMapPropertyCodecProvider()), getInvalidMapModel(), + "{'invalidMap': {'1': 1, '2': 2}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testConstructorNotPublicModel() { + decodingShouldFail(getCodec(ConstructorNotPublicModel.class), "{'integerField': 99}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testDataUnknownClass() { + ClassModel classModel = ClassModel.builder(SimpleModel.class).enableDiscriminator(true).build(); + try { + decodingShouldFail(getCodec(PojoCodecProvider.builder().register(classModel), SimpleModel.class), "{'_t': 'FakeModel'}"); + } catch (CodecConfigurationException e) { + assertTrue(e.getMessage().startsWith("Failed to decode 'SimpleModel'. Decoding errored with:")); + throw e; + } + } + + @Test(expected = CodecConfigurationException.class) + public void testInvalidTypeForField() { + decodingShouldFail(getCodec(SimpleModel.class), "{'_t': 'SimpleModel', 'stringField': 123}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testInvalidTypeForPrimitiveField() { + decodingShouldFail(getCodec(PrimitivesModel.class), "{ '_t': 'PrimitivesModel', 'myBoolean': null}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testInvalidTypeForModelField() { + decodingShouldFail(getCodec(SimpleNestedPojoModel.class), "{ '_t': 'SimpleNestedPojoModel', 'simple': 123}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testInvalidDiscriminatorInNestedModel() { + decodingShouldFail(getCodec(SimpleNestedPojoModel.class), "{ '_t': 'SimpleNestedPojoModel'," + + "'simple': {'_t': 'FakeModel', 'integerField': 42, 'stringField': 'myString'}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testCannotEncodeUnspecializedClasses() { + CodecRegistry registry = fromProviders(getPojoCodecProviderBuilder(GenericTreeModel.class).build()); + encode(registry.get(GenericTreeModel.class), getGenericTreeModel(), false); + } + + @Test(expected = CodecConfigurationException.class) + public void testCannotDecodeUnspecializedClasses() { + decodingShouldFail(getCodec(GenericTreeModel.class), + "{'field1': 'top', 'field2': 1, " + + "'left': {'field1': 'left', 'field2': 2, 'left': {'field1': 'left', 'field2': 3}}, " + + "'right': {'field1': 'right', 'field2': 4, 'left': {'field1': 'left', 'field2': 5}}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testBsonCreatorPrimitivesAndNullValues() { + decodingShouldFail(getCodec(CreatorConstructorPrimitivesModel.class), "{intField: 100, stringField: 'test'}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorMethodThrowsExceptionModel() { + decodingShouldFail(getCodec(CreatorMethodThrowsExceptionModel.class), + "{'integerField': 10, 'stringField': 'eleven', 'longField': {$numberLong: '12'}}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testCreatorConstructorThrowsExceptionModel() { + decodingShouldFail(getCodec(CreatorConstructorThrowsExceptionModel.class), "{}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testInvalidSetterModel() { + decodingShouldFail(getCodec(InvalidSetterArgsModel.class), "{'integerField': 42, 'stringField': 'myString'}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testInvalidGetterAndSetterModelEncoding() { + InvalidGetterAndSetterModel model = new InvalidGetterAndSetterModel(42, "myString"); + roundTrip(getPojoCodecProviderBuilder(InvalidGetterAndSetterModel.class), model, "{'integerField': 42, 'stringField': 'myString'}"); + } + + @Test(expected = CodecConfigurationException.class) + public void testInvalidGetterAndSetterModelDecoding() { + decodingShouldFail(getCodec(InvalidGetterAndSetterModel.class), "{'integerField': 42, 'stringField': 'myString'}"); + } + + private List getDefaultAndUseGettersConvention() { + List conventions = new ArrayList(DEFAULT_CONVENTIONS); + conventions.add(USE_GETTERS_FOR_SETTERS); + return conventions; + } + + class ObjectCodec implements Codec { + + @Override + public Object decode(final BsonReader reader, final DecoderContext decoderContext) { + throw new UnsupportedOperationException(); + } + + @Override + public void encode(final BsonWriter writer, final Object value, final EncoderContext encoderContext) { + throw new UnsupportedOperationException(); + } + + @Override + public Class getEncoderClass() { + return Object.class; + } + } + + class ObjectIdGenerator implements IdGenerator { + @Override + public ObjectId generate() { + return new ObjectId("123412341234123412341234"); + } + + @Override + public Class getType() { + return ObjectId.class; + } + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/PojoRoundTripTest.java b/bson/src/test/unit/org/bson/codecs/pojo/PojoRoundTripTest.java new file mode 100644 index 00000000000..9512422b4e0 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/PojoRoundTripTest.java @@ -0,0 +1,486 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonDocument; +import org.bson.codecs.pojo.entities.AbstractInterfaceModel; +import org.bson.codecs.pojo.entities.CollectionNestedPojoModel; +import org.bson.codecs.pojo.entities.CollectionSpecificReturnTypeCreatorModel; +import org.bson.codecs.pojo.entities.CollectionSpecificReturnTypeModel; +import org.bson.codecs.pojo.entities.ConcreteAndNestedAbstractInterfaceModel; +import org.bson.codecs.pojo.entities.ConcreteCollectionsModel; +import org.bson.codecs.pojo.entities.ConcreteInterfaceGenericModel; +import org.bson.codecs.pojo.entities.ConcreteStandAloneAbstractInterfaceModel; +import org.bson.codecs.pojo.entities.ContainsAlternativeMapAndCollectionModel; +import org.bson.codecs.pojo.entities.ConventionModel; +import org.bson.codecs.pojo.entities.FieldAndPropertyTypeMismatchModel; +import org.bson.codecs.pojo.entities.GenericHolderModel; +import org.bson.codecs.pojo.entities.GenericTreeModel; +import org.bson.codecs.pojo.entities.InterfaceBasedModel; +import org.bson.codecs.pojo.entities.InterfaceModelImpl; +import org.bson.codecs.pojo.entities.InterfaceUpperBoundsModelAbstractImpl; +import org.bson.codecs.pojo.entities.MultipleBoundsModel; +import org.bson.codecs.pojo.entities.MultipleLevelGenericModel; +import org.bson.codecs.pojo.entities.NestedFieldReusingClassTypeParameter; +import org.bson.codecs.pojo.entities.NestedGenericHolderFieldWithMultipleTypeParamsModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderMapModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderSimpleGenericsModel; +import org.bson.codecs.pojo.entities.NestedGenericTreeModel; +import org.bson.codecs.pojo.entities.NestedMultipleLevelGenericModel; +import org.bson.codecs.pojo.entities.NestedReusedGenericsModel; +import org.bson.codecs.pojo.entities.NestedSelfReferentialGenericHolderModel; +import org.bson.codecs.pojo.entities.NestedSelfReferentialGenericModel; +import org.bson.codecs.pojo.entities.NestedSimpleIdModel; +import org.bson.codecs.pojo.entities.PrimitivesModel; +import org.bson.codecs.pojo.entities.PropertyReusingClassTypeParameter; +import org.bson.codecs.pojo.entities.PropertySelectionModel; +import org.bson.codecs.pojo.entities.PropertyWithMultipleTypeParamsModel; +import org.bson.codecs.pojo.entities.ReusedGenericsModel; +import org.bson.codecs.pojo.entities.SelfReferentialGenericModel; +import org.bson.codecs.pojo.entities.ShapeHolderCircleModel; +import org.bson.codecs.pojo.entities.ShapeHolderModel; +import org.bson.codecs.pojo.entities.ShapeModelAbstract; +import org.bson.codecs.pojo.entities.ShapeModelCircle; +import org.bson.codecs.pojo.entities.ShapeModelRectangle; +import org.bson.codecs.pojo.entities.SimpleEnum; +import org.bson.codecs.pojo.entities.SimpleEnumModel; +import org.bson.codecs.pojo.entities.SimpleGenericsModel; +import org.bson.codecs.pojo.entities.SimpleIdImmutableModel; +import org.bson.codecs.pojo.entities.SimpleIdModel; +import org.bson.codecs.pojo.entities.SimpleModel; +import org.bson.codecs.pojo.entities.SimpleNestedPojoModel; +import org.bson.codecs.pojo.entities.TreeWithIdModel; +import org.bson.codecs.pojo.entities.UpperBoundsConcreteModel; +import org.bson.codecs.pojo.entities.conventions.AnnotationBsonPropertyIdModel; +import org.bson.codecs.pojo.entities.conventions.BsonIgnoreDuplicatePropertyMultipleTypes; +import org.bson.codecs.pojo.entities.conventions.BsonIgnoreInvalidMapModel; +import org.bson.codecs.pojo.entities.conventions.BsonIgnoreSyntheticProperty; +import org.bson.codecs.pojo.entities.conventions.CollectionDiscriminatorAbstractClassesModel; +import org.bson.codecs.pojo.entities.conventions.CollectionDiscriminatorInterfacesModel; +import org.bson.codecs.pojo.entities.conventions.CreatorAllFinalFieldsModel; +import org.bson.codecs.pojo.entities.conventions.CreatorConstructorIdModel; +import org.bson.codecs.pojo.entities.conventions.CreatorConstructorLegacyBsonPropertyModel; +import org.bson.codecs.pojo.entities.conventions.CreatorConstructorModel; +import org.bson.codecs.pojo.entities.conventions.CreatorConstructorRenameModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInSuperClassModel; +import org.bson.codecs.pojo.entities.conventions.CreatorInSuperClassModelImpl; +import org.bson.codecs.pojo.entities.conventions.CreatorMethodModel; +import org.bson.codecs.pojo.entities.conventions.CreatorNoArgsConstructorModel; +import org.bson.codecs.pojo.entities.conventions.CreatorNoArgsMethodModel; +import org.bson.codecs.pojo.entities.DuplicateAnnotationAllowedModel; +import org.bson.codecs.pojo.entities.conventions.InterfaceModel; +import org.bson.codecs.pojo.entities.conventions.InterfaceModelImplA; +import org.bson.codecs.pojo.entities.conventions.InterfaceModelImplB; +import org.bson.codecs.pojo.entities.conventions.Subclass1Model; +import org.bson.codecs.pojo.entities.conventions.Subclass2Model; +import org.bson.codecs.pojo.entities.conventions.SuperClassModel; +import org.bson.types.ObjectId; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Arrays.asList; + +@RunWith(Parameterized.class) +public final class PojoRoundTripTest extends PojoTestCase { + + private final String name; + private final Object model; + private final PojoCodecProvider.Builder builder; + private final String json; + + public PojoRoundTripTest(final String name, final Object model, final String json, final PojoCodecProvider.Builder builder) { + this.name = name; + this.model = model; + this.json = json; + this.builder = builder; + } + + @Test + public void test() { + roundTrip(builder, model, json); + } + + private static List testCases() { + List data = new ArrayList(); + data.add(new TestData("Simple model", getSimpleModel(), PojoCodecProvider.builder().register(SimpleModel.class), + SIMPLE_MODEL_JSON)); + + data.add(new TestData("Property selection model", new PropertySelectionModel(), + getPojoCodecProviderBuilder(PropertySelectionModel.class), + "{'finalStringField': 'finalStringField', 'stringField': 'stringField'}")); + + data.add(new TestData("Conventions default", getConventionModel(), + getPojoCodecProviderBuilder(ConventionModel.class, SimpleModel.class), + "{'_id': 'id', '_cls': 'AnnotatedConventionModel', 'myFinalField': 10, 'myIntField': 10," + + "'child': {'_id': 'child', 'myFinalField': 10, 'myIntField': 10," + + "'model': {'integerField': 42, 'stringField': 'myString'}}}")); + + data.add(new TestData("BsonIgnore invalid map", new BsonIgnoreInvalidMapModel("myString"), + getPojoCodecProviderBuilder(BsonIgnoreInvalidMapModel.class), + "{stringField: 'myString'}")); + + data.add(new TestData("Interfaced based model", new InterfaceModelImpl("a", "b"), + getPojoCodecProviderBuilder(InterfaceModelImpl.class), + "{'propertyA': 'a', 'propertyB': 'b'}")); + + data.add(new TestData("Interfaced based model with bound", new InterfaceUpperBoundsModelAbstractImpl("someName", + new InterfaceModelImpl("a", "b")), + getPojoCodecProviderBuilder(InterfaceUpperBoundsModelAbstractImpl.class, InterfaceModelImpl.class), + "{'name': 'someName', 'nestedModel': {'propertyA': 'a', 'propertyB': 'b'}}")); + + data.add(new TestData("Interface concrete and abstract model", + new ConcreteAndNestedAbstractInterfaceModel("A", new ConcreteAndNestedAbstractInterfaceModel("B", + new ConcreteStandAloneAbstractInterfaceModel("C"))), + getPojoCodecProviderBuilder(InterfaceBasedModel.class, AbstractInterfaceModel.class, + ConcreteAndNestedAbstractInterfaceModel.class, ConcreteStandAloneAbstractInterfaceModel.class), + "{'_t': 'org.bson.codecs.pojo.entities.ConcreteAndNestedAbstractInterfaceModel', 'name': 'A', " + + "'child': {'_t': 'org.bson.codecs.pojo.entities.ConcreteAndNestedAbstractInterfaceModel', 'name': 'B', " + + " 'child': {'_t': 'org.bson.codecs.pojo.entities.ConcreteStandAloneAbstractInterfaceModel', 'name': 'C'}}}}")); + + data.add(new TestData("Concrete generic interface model", new ConcreteInterfaceGenericModel("someValue"), + getPojoCodecProviderBuilder(ConcreteInterfaceGenericModel.class), "{propertyA: 'someValue'}")); + + data.add(new TestData("Primitives model", getPrimitivesModel(), + getPojoCodecProviderBuilder(PrimitivesModel.class), + "{ 'myBoolean': true, 'myByte': 1, 'myCharacter': '1', 'myDouble': 1.0, 'myFloat': 2.0, 'myInteger': 3, " + + "'myLong': { '$numberLong': '5' }, 'myShort': 6}")); + + data.add(new TestData("Concrete collections model", getConcreteCollectionsModel(), + getPojoCodecProviderBuilder(ConcreteCollectionsModel.class), + "{'collection': [1, 2, 3], 'list': [4, 5, 6], 'linked': [7, 8, 9], 'map': {'A': 1.1, 'B': 2.2, 'C': 3.3}," + + "'concurrent': {'D': 4.4, 'E': 5.5, 'F': 6.6}}")); + + data.add(new TestData("Handling of nulls inside collections", getConcreteCollectionsModelWithNulls(), + getPojoCodecProviderBuilder(ConcreteCollectionsModel.class), + "{'collection': [1, null, 3], 'list': [4, null, 6], 'linked': [null, 8, 9], 'map': {'A': 1.1, 'B': null, 'C': 3.3}}")); + + data.add(new TestData("Concrete specific return collection type model through BsonCreator", + new CollectionSpecificReturnTypeCreatorModel(Arrays.asList("foo", "bar")), + getPojoCodecProviderBuilder(CollectionSpecificReturnTypeCreatorModel.class), + "{'properties': ['foo', 'bar']}")); + + data.add(new TestData("Concrete specific return collection type model through getter and setter", + new CollectionSpecificReturnTypeModel(Arrays.asList("foo", "bar")), + getPojoCodecProviderBuilder(CollectionSpecificReturnTypeModel.class), + "{'properties': ['foo', 'bar']}")); + + data.add(new TestData("Concrete specific return collection type model", getConcreteCollectionsModel(), + getPojoCodecProviderBuilder(ConcreteCollectionsModel.class), + "{'collection': [1, 2, 3], 'list': [4, 5, 6], 'linked': [7, 8, 9], 'map': {'A': 1.1, 'B': 2.2, 'C': 3.3}," + + "'concurrent': {'D': 4.4, 'E': 5.5, 'F': 6.6}}")); + + data.add(new TestData("Nested simple", getSimpleNestedPojoModel(), + getPojoCodecProviderBuilder(SimpleNestedPojoModel.class, SimpleModel.class), + "{'simple': " + SIMPLE_MODEL_JSON + "}")); + + data.add(new TestData("Nested collection", getCollectionNestedPojoModel(), + getPojoCodecProviderBuilder(CollectionNestedPojoModel.class, SimpleModel.class), + "{ 'listSimple': [" + SIMPLE_MODEL_JSON + "]," + + "'listListSimple': [[" + SIMPLE_MODEL_JSON + "]]," + + "'setSimple': [" + SIMPLE_MODEL_JSON + "]," + + "'setSetSimple': [[" + SIMPLE_MODEL_JSON + "]]," + + "'mapSimple': {'s': " + SIMPLE_MODEL_JSON + "}," + + "'mapMapSimple': {'ms': {'s': " + SIMPLE_MODEL_JSON + "}}," + + "'mapListSimple': {'ls': [" + SIMPLE_MODEL_JSON + "]}," + + "'mapListMapSimple': {'lm': [{'s': " + SIMPLE_MODEL_JSON + "}]}," + + "'mapSetSimple': {'s': [" + SIMPLE_MODEL_JSON + "]}," + + "'listMapSimple': [{'s': " + SIMPLE_MODEL_JSON + "}]," + + "'listMapListSimple': [{'ls': [" + SIMPLE_MODEL_JSON + "]}]," + + "'listMapSetSimple': [{'s': [" + SIMPLE_MODEL_JSON + "]}]," + + "}")); + + data.add(new TestData("Nested collection", getCollectionNestedPojoModelWithNulls(), + getPojoCodecProviderBuilder(CollectionNestedPojoModel.class, SimpleModel.class), + "{ 'listListSimple': [ null ]," + + "'setSetSimple': [ null ]," + + "'mapMapSimple': {'ms': null}," + + "'mapListSimple': {'ls': null}," + + "'mapListMapSimple': {'lm': [null]}," + + "'mapSetSimple': {'s': null}," + + "'listMapSimple': [null]," + + "'listMapListSimple': [{'ls': null}]," + + "'listMapSetSimple': [{'s': null}]," + + "}")); + + data.add(new TestData("Nested generic holder", getNestedGenericHolderModel(), + getPojoCodecProviderBuilder(NestedGenericHolderModel.class, GenericHolderModel.class), + "{'nested': {'myGenericField': 'generic', 'myLongField': {'$numberLong': '1'}}}")); + + data.add(new TestData("Nested generic holder map", getNestedGenericHolderMapModel(), + getPojoCodecProviderBuilder(NestedGenericHolderMapModel.class, + GenericHolderModel.class, SimpleGenericsModel.class, SimpleModel.class), + "{ 'nested': { 'myGenericField': {'s': " + SIMPLE_MODEL_JSON + "}, 'myLongField': {'$numberLong': '1'}}}")); + + data.add(new TestData("Nested reused generic", getNestedReusedGenericsModel(), + getPojoCodecProviderBuilder(NestedReusedGenericsModel.class, ReusedGenericsModel.class, SimpleModel.class), + "{ 'nested':{ 'field1':{ '$numberLong':'1' }, 'field2':[" + SIMPLE_MODEL_JSON + "], " + + "'field3':'field3', 'field4':42, 'field5':'field5', 'field6':[" + SIMPLE_MODEL_JSON + ", " + + SIMPLE_MODEL_JSON + "], 'field7':{ '$numberLong':'2' }, 'field8':'field8' } }")); + + + data.add(new TestData("Nested generic holder with multiple types", getNestedGenericHolderFieldWithMultipleTypeParamsModel(), + getPojoCodecProviderBuilder(NestedGenericHolderFieldWithMultipleTypeParamsModel.class, + PropertyWithMultipleTypeParamsModel.class, SimpleGenericsModel.class, GenericHolderModel.class), + "{'nested': {'myGenericField': {_t: 'PropertyWithMultipleTypeParamsModel', " + + "'simpleGenericsModel': {_t: 'org.bson.codecs.pojo.entities.SimpleGenericsModel', 'myIntegerField': 42, " + + "'myGenericField': {'$numberLong': '101'}, 'myListField': ['B', 'C'], 'myMapField': {'D': 2, 'E': 3, 'F': 4 }}}," + + "'myLongField': {'$numberLong': '42'}}}")); + + + data.add(new TestData("Nested generic tree", new NestedGenericTreeModel(42, getGenericTreeModel()), + getPojoCodecProviderBuilder(NestedGenericTreeModel.class, GenericTreeModel.class), + "{'intField': 42, 'nested': {'field1': 'top', 'field2': 1, " + + "'left': {'field1': 'left', 'field2': 2, 'left': {'field1': 'left', 'field2': 3}}, " + + "'right': {'field1': 'right', 'field2': 4, 'left': {'field1': 'left', 'field2': 5}}}}")); + + data.add(new TestData("Nested multiple level", + new NestedMultipleLevelGenericModel(42, new MultipleLevelGenericModel("string", getGenericTreeModel())), + getPojoCodecProviderBuilder(NestedMultipleLevelGenericModel.class, MultipleLevelGenericModel.class, GenericTreeModel.class), + "{'intField': 42, 'nested': {'stringField': 'string', 'nested': {'field1': 'top', 'field2': 1, " + + "'left': {'field1': 'left', 'field2': 2, 'left': {'field1': 'left', 'field2': 3}}, " + + "'right': {'field1': 'right', 'field2': 4, 'left': {'field1': 'left', 'field2': 5}}}}}")); + + data.add(new TestData("Nested Generics holder", getNestedGenericHolderSimpleGenericsModel(), + getPojoCodecProviderBuilder(NestedGenericHolderSimpleGenericsModel.class, GenericHolderModel.class, + SimpleGenericsModel.class, SimpleModel.class), + "{'nested': {'myGenericField': {'myIntegerField': 42, 'myGenericField': 42," + + " 'myListField': [[" + SIMPLE_MODEL_JSON + "]], " + + " 'myMapField': {'A': {'A': " + SIMPLE_MODEL_JSON + "}}}," + + " 'myLongField': {'$numberLong': '42' }}}")); + + data.add(new TestData("Nested property reusing type parameter", + new NestedFieldReusingClassTypeParameter(new PropertyReusingClassTypeParameter(getGenericTreeModelStrings())), + getPojoCodecProviderBuilder(NestedFieldReusingClassTypeParameter.class, PropertyReusingClassTypeParameter.class, + GenericTreeModel.class), + "{'nested': {'tree': {'field1': 'top', 'field2': '1', " + + "'left': {'field1': 'left', 'field2': '2', 'left': {'field1': 'left', 'field2': '3'}}, " + + "'right': {'field1': 'right', 'field2': '4', 'left': {'field1': 'left', 'field2': '5'}}}}}")); + + data.add(new TestData("Abstract shape model - circle", + new ShapeHolderModel(getShapeModelCircle()), getPojoCodecProviderBuilder(ShapeModelAbstract.class, + ShapeModelCircle.class, ShapeModelRectangle.class, ShapeHolderModel.class), + "{'shape': {'_t': 'org.bson.codecs.pojo.entities.ShapeModelCircle', 'color': 'orange', 'radius': 4.2}}")); + + data.add(new TestData("Abstract shape model - rectangle", + new ShapeHolderModel(getShapeModelRectangle()), getPojoCodecProviderBuilder(ShapeModelAbstract.class, + ShapeModelCircle.class, ShapeModelRectangle.class, ShapeHolderModel.class), + "{'shape': {'_t': 'org.bson.codecs.pojo.entities.ShapeModelRectangle', 'color': 'green', 'width': 22.1, 'height': " + + "105.0}}}")); + + data.add(new TestData("Upper bounds", + new UpperBoundsConcreteModel(1L), getPojoCodecProviderBuilder(UpperBoundsConcreteModel.class), + "{'myGenericField': {'$numberLong': '1'}}")); + + data.add(new TestData("Multiple bounds", getMultipleBoundsModel(), getPojoCodecProviderBuilder(MultipleBoundsModel.class), + "{'level1' : 2.2, 'level2': [1, 2, 3], 'level3': {key: 'value'}}")); + + data.add(new TestData("Self referential", getNestedSelfReferentialGenericHolderModel(), + getPojoCodecProviderBuilder(NestedSelfReferentialGenericHolderModel.class, NestedSelfReferentialGenericModel.class, + SelfReferentialGenericModel.class), + "{'nested': { 't': true, 'v': {'$numberLong': '42'}, 'z': 44.0, " + + "'selfRef1': {'t': true, 'v': {'$numberLong': '33'}, 'child': {'t': {'$numberLong': '44'}, 'v': false}}, " + + "'selfRef2': {'t': true, 'v': 3.14, 'child': {'t': 3.42, 'v': true}}}}")); + + data.add(new TestData("Creator constructor", new CreatorConstructorModel(asList(10, 11), "twelve", 13), + getPojoCodecProviderBuilder(CreatorConstructorModel.class), + "{'integersField': [10, 11], 'stringField': 'twelve', 'longField': {$numberLong: '13'}}")); + + data.add(new TestData("Creator constructor with legacy BsonProperty using name", + new CreatorConstructorLegacyBsonPropertyModel(asList(10, 11), "twelve", 13), + getPojoCodecProviderBuilder(CreatorConstructorLegacyBsonPropertyModel.class), + "{'integersField': [10, 11], 'stringField': 'twelve', 'longField': {$numberLong: '13'}}")); + + data.add(new TestData("Creator constructor with rename", new CreatorConstructorRenameModel(asList(10, 11), "twelve", 13), + getPojoCodecProviderBuilder(CreatorConstructorRenameModel.class), + "{'integerList': [10, 11], 'stringField': 'twelve', 'longField': {$numberLong: '13'}}")); + + data.add(new TestData("Creator constructor with ID", new CreatorConstructorIdModel("1234-34567-890", asList(10, 11), "twelve", 13), + getPojoCodecProviderBuilder(CreatorConstructorIdModel.class), + "{'_id': '1234-34567-890', 'integersField': [10, 11], 'stringField': 'twelve', 'longField': {$numberLong: '13'}}")); + + data.add(new TestData("Creator no-args constructor", new CreatorNoArgsConstructorModel(40, "one", 42), + getPojoCodecProviderBuilder(CreatorNoArgsConstructorModel.class), + "{'integerField': 40, 'stringField': 'one', 'longField': {$numberLong: '42'}}")); + + data.add(new TestData("Creator method", new CreatorMethodModel(30, "two", 32), + getPojoCodecProviderBuilder(CreatorMethodModel.class), + "{'integerField': 30, 'stringField': 'two', 'longField': {$numberLong: '32'}}")); + + data.add(new TestData("Creator method", CreatorMethodModel.create(30), + getPojoCodecProviderBuilder(CreatorMethodModel.class), + "{'integerField': 30, 'longField': {$numberLong: '0'}}")); + + data.add(new TestData("Creator no-args method", new CreatorNoArgsMethodModel(10, "one", 11), + getPojoCodecProviderBuilder(CreatorNoArgsMethodModel.class), + "{'integerField': 10, 'stringField': 'one', 'longField': {$numberLong: '11'}}")); + + data.add(new TestData("Creator all final", new CreatorAllFinalFieldsModel("pId", "Ada", "Lovelace"), + getPojoCodecProviderBuilder(CreatorAllFinalFieldsModel.class), + "{'_id': 'pId', '_t': 'org.bson.codecs.pojo.entities.conventions.CreatorAllFinalFieldsModel', " + + "'firstName': 'Ada', 'lastName': 'Lovelace'}")); + + data.add(new TestData("Creator all final with nulls", new CreatorAllFinalFieldsModel("pId", "Ada", null), + getPojoCodecProviderBuilder(CreatorAllFinalFieldsModel.class), + "{'_id': 'pId', '_t': 'org.bson.codecs.pojo.entities.conventions.CreatorAllFinalFieldsModel', 'firstName': 'Ada'}")); + + data.add(new TestData("Can handle custom Maps and Collections", + new ContainsAlternativeMapAndCollectionModel(BsonDocument.parse("{customList: [1,2,3], customMap: {'field': 'value'}}")), + getPojoCodecProviderBuilder(ContainsAlternativeMapAndCollectionModel.class), + "{customList: [1,2,3], customMap: {'field': 'value'}}")); + + data.add(new TestData("Collection of discriminators abstract classes", new CollectionDiscriminatorAbstractClassesModel().setList( + asList(new Subclass1Model().setName("abc").setValue(true), new Subclass2Model().setInteger(234).setValue(false))).setMap( + Collections.singletonMap("key", new Subclass2Model().setInteger(123).setValue(true))), + getPojoCodecProviderBuilder(CollectionDiscriminatorAbstractClassesModel.class, SuperClassModel.class, Subclass1Model.class, + Subclass2Model.class), + "{list: [{_t: 'org.bson.codecs.pojo.entities.conventions.Subclass1Model', value: true, name: 'abc'}," + + "{_t: 'org.bson.codecs.pojo.entities.conventions.Subclass2Model', value: false, integer: 234}]," + + "map: {key: {_t: 'org.bson.codecs.pojo.entities.conventions.Subclass2Model', value: true, integer: 123}}}")); + + data.add(new TestData("Collection of discriminators interfaces", new CollectionDiscriminatorInterfacesModel().setList( + asList(new InterfaceModelImplA().setName("abc").setValue(true), + new InterfaceModelImplB().setInteger(234).setValue(false))).setMap( + Collections.singletonMap("key", new InterfaceModelImplB().setInteger(123).setValue(true))), + getPojoCodecProviderBuilder(CollectionDiscriminatorInterfacesModel.class, InterfaceModelImplA.class, + InterfaceModelImplB.class, InterfaceModel.class), + "{list: [{_t: 'org.bson.codecs.pojo.entities.conventions.InterfaceModelImplA', value: true, name: 'abc'}," + + "{_t: 'org.bson.codecs.pojo.entities.conventions.InterfaceModelImplB', value: false, integer: 234}]," + + "map: {key: {_t: 'org.bson.codecs.pojo.entities.conventions.InterfaceModelImplB', value: true, integer: 123}}}")); + + data.add(new TestData("Creator in super class factory method", + CreatorInSuperClassModel.newInstance("a", "b"), + getPojoCodecProviderBuilder(CreatorInSuperClassModelImpl.class), + "{'propertyA': 'a', 'propertyB': 'b'}")); + + data.add(new TestData("Primitive field type doesn't match private property", + new FieldAndPropertyTypeMismatchModel("foo"), + getPojoCodecProviderBuilder(FieldAndPropertyTypeMismatchModel.class), + "{'stringField': 'foo'}")); + + data.add(new TestData("Enums support", + new SimpleEnumModel(SimpleEnum.BRAVO), + getPojoCodecProviderBuilder(SimpleEnumModel.class), + "{ 'myEnum': 'BRAVO' }")); + + data.add(new TestData("AnnotationBsonPropertyIdModel", new AnnotationBsonPropertyIdModel(99L), + getPojoCodecProviderBuilder(AnnotationBsonPropertyIdModel.class), + "{'id': {'$numberLong': '99' }}")); + + data.add(new TestData("Shape model - circle", + new ShapeHolderCircleModel(getShapeModelCircle()), + getPojoCodecProviderBuilder(ShapeModelCircle.class, ShapeHolderCircleModel.class), + "{'shape': {'_t': 'org.bson.codecs.pojo.entities.ShapeModelCircle', 'color': 'orange', 'radius': 4.2}}")); + + data.add(new TestData("BsonIgnore synthentic property", + new BsonIgnoreSyntheticProperty("string value"), + getPojoCodecProviderBuilder(BsonIgnoreSyntheticProperty.class).conventions(Conventions.DEFAULT_CONVENTIONS), + "{stringField: 'string value'}")); + + data.add(new TestData("SimpleIdModel with existing id", + new SimpleIdModel(new ObjectId("123412341234123412341234"), 42, "myString"), + getPojoCodecProviderBuilder(SimpleIdModel.class).conventions(Conventions.DEFAULT_CONVENTIONS), + "{'_id': {'$oid': '123412341234123412341234'}, 'integerField': 42, 'stringField': 'myString'}")); + + + data.add(new TestData("SimpleIdImmutableModel with existing id", + new SimpleIdImmutableModel(new ObjectId("123412341234123412341234"), 42, "myString"), + getPojoCodecProviderBuilder(SimpleIdImmutableModel.class).conventions(Conventions.DEFAULT_CONVENTIONS), + "{'_id': {'$oid': '123412341234123412341234'}, 'integerField': 42, 'stringField': 'myString'}")); + + data.add(new TestData("NestedSimpleIdModel", + new NestedSimpleIdModel(new SimpleIdModel(42, "myString")), + getPojoCodecProviderBuilder(NestedSimpleIdModel.class, SimpleIdModel.class).conventions(Conventions.DEFAULT_CONVENTIONS), + "{'nestedSimpleIdModel': {'integerField': 42, 'stringField': 'myString'}}")); + + data.add(new TestData("TreeWithIdModel", + new TreeWithIdModel(new ObjectId("123412341234123412341234"), "top", + new TreeWithIdModel("left-1", new TreeWithIdModel("left-2"), null), new TreeWithIdModel("right-1")), + getPojoCodecProviderBuilder(TreeWithIdModel.class).conventions(Conventions.DEFAULT_CONVENTIONS), + "{'_id': {'$oid': '123412341234123412341234'}, 'level': 'top'," + + "'left': {'level': 'left-1', 'left': {'level': 'left-2'}}," + + "'right': {'level': 'right-1'}}")); + + data.add(new TestData("DuplicateAnnotationAllowedModel", + new DuplicateAnnotationAllowedModel("abc"), + getPojoCodecProviderBuilder(DuplicateAnnotationAllowedModel.class).conventions(Conventions.DEFAULT_CONVENTIONS), + "{'_id': 'abc'}")); + + data.add(new TestData("BsonIgnore duplicate property with multiple types", + new BsonIgnoreDuplicatePropertyMultipleTypes("string value"), + getPojoCodecProviderBuilder(BsonIgnoreDuplicatePropertyMultipleTypes.class).conventions(Conventions.DEFAULT_CONVENTIONS), + "{stringField: 'string value'}")); + + return data; + } + + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + List data = new ArrayList(); + + for (TestData testData : testCases()) { + data.add(new Object[]{format("%s", testData.getName()), testData.getModel(), testData.getJson(), testData.getBuilder()}); + data.add(new Object[]{format("%s [Auto]", testData.getName()), testData.getModel(), testData.getJson(), AUTOMATIC_BUILDER}); + data.add(new Object[]{format("%s [Package]", testData.getName()), testData.getModel(), testData.getJson(), PACKAGE_BUILDER}); + } + return data; + } + + private static final PojoCodecProvider.Builder AUTOMATIC_BUILDER = PojoCodecProvider.builder().automatic(true); + private static final PojoCodecProvider.Builder PACKAGE_BUILDER = PojoCodecProvider.builder().register("org.bson.codecs.pojo.entities", + "org.bson.codecs.pojo.entities.conventions"); + + private static class TestData { + private final String name; + private final Object model; + private final PojoCodecProvider.Builder builder; + private final String json; + + TestData(final String name, final Object model, final PojoCodecProvider.Builder builder, final String json) { + this.name = name; + this.model = model; + this.builder = builder; + this.json = json; + } + + public String getName() { + return name; + } + + public Object getModel() { + return model; + } + + public PojoCodecProvider.Builder getBuilder() { + return builder; + } + + public String getJson() { + return json; + } + } + + +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/PojoTestCase.java b/bson/src/test/unit/org/bson/codecs/pojo/PojoTestCase.java new file mode 100644 index 00000000000..3ea4e1bafde --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/PojoTestCase.java @@ -0,0 +1,437 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.BsonBinaryReader; +import org.bson.BsonBinaryWriter; +import org.bson.BsonDocument; +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.ByteBufNIO; +import org.bson.codecs.BsonDocumentCodec; +import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.ValueCodecProvider; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.entities.CollectionNestedPojoModel; +import org.bson.codecs.pojo.entities.ConcreteCollectionsModel; +import org.bson.codecs.pojo.entities.ConventionModel; +import org.bson.codecs.pojo.entities.GenericHolderModel; +import org.bson.codecs.pojo.entities.GenericTreeModel; +import org.bson.codecs.pojo.entities.InvalidMapModel; +import org.bson.codecs.pojo.entities.MultipleBoundsModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderFieldWithMultipleTypeParamsModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderMapModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderModel; +import org.bson.codecs.pojo.entities.NestedGenericHolderSimpleGenericsModel; +import org.bson.codecs.pojo.entities.NestedReusedGenericsModel; +import org.bson.codecs.pojo.entities.NestedSelfReferentialGenericHolderModel; +import org.bson.codecs.pojo.entities.NestedSelfReferentialGenericModel; +import org.bson.codecs.pojo.entities.PrimitivesModel; +import org.bson.codecs.pojo.entities.PropertyWithMultipleTypeParamsModel; +import org.bson.codecs.pojo.entities.ReusedGenericsModel; +import org.bson.codecs.pojo.entities.SelfReferentialGenericModel; +import org.bson.codecs.pojo.entities.ShapeModelCircle; +import org.bson.codecs.pojo.entities.ShapeModelRectangle; +import org.bson.codecs.pojo.entities.SimpleEnum; +import org.bson.codecs.pojo.entities.SimpleGenericsModel; +import org.bson.codecs.pojo.entities.SimpleModel; +import org.bson.codecs.pojo.entities.SimpleNestedPojoModel; +import org.bson.io.BasicOutputBuffer; +import org.bson.io.ByteBufferBsonInput; +import org.bson.io.OutputBuffer; +import org.bson.types.ObjectId; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; +import static org.bson.codecs.pojo.Conventions.DEFAULT_CONVENTIONS; +import static org.junit.Assert.assertEquals; + +abstract class PojoTestCase { + + static final BsonDocumentCodec DOCUMENT_CODEC = new BsonDocumentCodec(); + + @SuppressWarnings("unchecked") + void roundTrip(final T value, final String json) { + roundTrip(PojoCodecProvider.builder().automatic(true), value, json); + } + + @SuppressWarnings("unchecked") + void roundTrip(final PojoCodecProvider.Builder builder, final T value, final String json) { + encodesTo(getCodecRegistry(builder), value, json); + decodesTo(getCodecRegistry(builder), json, value); + } + + @SuppressWarnings("unchecked") + void roundTrip(final CodecRegistry registry, final T value, final String json) { + encodesTo(registry, value, json); + decodesTo(registry, json, value); + } + + void encodesTo(final PojoCodecProvider.Builder builder, final T value, final String json) { + encodesTo(builder, value, json, false); + } + + void encodesTo(final PojoCodecProvider.Builder builder, final T value, final String json, final boolean collectible) { + encodesTo(getCodecRegistry(builder), value, json, collectible); + } + + void encodesTo(final CodecRegistry registry, final T value, final String json) { + encodesTo(registry, value, json, false); + } + + @SuppressWarnings("unchecked") + void encodesTo(final CodecRegistry registry, final T value, final String json, final boolean collectible) { + Codec codec = (Codec) registry.get(value.getClass()); + encodesTo(codec, value, json, collectible); + } + + @SuppressWarnings("unchecked") + void encodesTo(final Codec codec, final T value, final String json) { + encodesTo(codec, value, json, false); + } + + @SuppressWarnings("unchecked") + void encodesTo(final Codec codec, final T value, final String json, final boolean collectible) { + OutputBuffer encoded = encode(codec, value, collectible); + + BsonDocument asBsonDocument = decode(DOCUMENT_CODEC, encoded); + assertEquals("Encoded value", BsonDocument.parse(json), asBsonDocument); + } + + void decodesTo(final PojoCodecProvider.Builder builder, final String json, final T expected) { + decodesTo(getCodecRegistry(builder), json, expected); + } + + @SuppressWarnings("unchecked") + void decodesTo(final CodecRegistry registry, final String json, final T expected) { + Codec codec = (Codec) registry.get(expected.getClass()); + decodesTo(codec, json, expected); + } + + void decodesTo(final Codec codec, final String json, final T expected) { + OutputBuffer encoded = encode(DOCUMENT_CODEC, BsonDocument.parse(json), false); + T result = decode(codec, encoded); + assertEquals("Decoded value", expected, result); + } + + void decodingShouldFail(final Codec codec, final String json) { + decodesTo(codec, json, null); + } + + OutputBuffer encode(final Codec codec, final T value, final boolean collectible) { + OutputBuffer buffer = new BasicOutputBuffer(); + BsonWriter writer = new BsonBinaryWriter(buffer); + codec.encode(writer, value, EncoderContext.builder().isEncodingCollectibleDocument(collectible).build()); + return buffer; + } + + T decode(final Codec codec, final OutputBuffer buffer) { + BsonBinaryReader reader = new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(buffer.toByteArray())))); + return codec.decode(reader, DecoderContext.builder().build()); + } + + static PojoCodecProvider.Builder getPojoCodecProviderBuilder(final Class... classes) { + PojoCodecProvider.Builder builder = PojoCodecProvider.builder(); + for (final Class clazz : classes) { + builder.register(clazz); + } + builder.conventions(DEFAULT_CONVENTIONS); + return builder; + } + + PojoCodecImpl getCodec(final PojoCodecProvider.Builder builder, final Class clazz) { + return (PojoCodecImpl) getCodecRegistry(builder).get(clazz); + } + + PojoCodecImpl getCodec(final Class clazz) { + return getCodec(getPojoCodecProviderBuilder(clazz), clazz); + } + + PojoCodecProvider.Builder getPojoCodecProviderBuilder(final ClassModelBuilder... classModelBuilders) { + List> builders = new ArrayList>(); + for (ClassModelBuilder classModelBuilder : classModelBuilders) { + builders.add(classModelBuilder.build()); + } + return PojoCodecProvider.builder().register(builders.toArray(new ClassModel[builders.size()])); + } + + CodecRegistry getCodecRegistry(final PojoCodecProvider.Builder builder) { + return fromProviders(new BsonValueCodecProvider(), new ValueCodecProvider(), builder.build()); + } + + static SimpleModel getSimpleModel() { + return new SimpleModel(42, "myString"); + } + + static PrimitivesModel getPrimitivesModel() { + return new PrimitivesModel(true, Byte.parseByte("1", 2), '1', 1.0, 2f, 3, 5L, (short) 6); + } + + SimpleGenericsModel getSimpleGenericsModel() { + HashMap map = new HashMap(); + map.put("D", 2); + map.put("E", 3); + map.put("F", 4); + + return new SimpleGenericsModel(42, "A", asList("B", "C"), map); + } + + static SimpleGenericsModel getSimpleGenericsModelAlt() { + HashMap map = new HashMap(); + map.put("D", 2); + map.put("E", 3); + map.put("F", 4); + + return new SimpleGenericsModel(42, 101L, asList("B", "C"), map); + } + + static ConcreteCollectionsModel getConcreteCollectionsModel() { + Collection collection = asList(1, 2, 3); + List list = asList(4, 5, 6); + LinkedList linked = new LinkedList(asList(7, 8, 9)); + Map map = new HashMap(); + map.put("A", 1.1); + map.put("B", 2.2); + map.put("C", 3.3); + ConcurrentHashMap concurrent = new ConcurrentHashMap(); + concurrent.put("D", 4.4); + concurrent.put("E", 5.5); + concurrent.put("F", 6.6); + + return new ConcreteCollectionsModel(collection, list, linked, map, concurrent); + } + + + static ConcreteCollectionsModel getConcreteCollectionsModelWithNulls() { + Collection collection = asList(1, null, 3); + List list = asList(4, null, 6); + LinkedList linked = new LinkedList(asList(null, 8, 9)); + Map map = new HashMap(); + map.put("A", 1.1); + map.put("B", null); + map.put("C", 3.3); + + return new ConcreteCollectionsModel(collection, list, linked, map, null); + } + + static SimpleNestedPojoModel getSimpleNestedPojoModel() { + SimpleModel simpleModel = getSimpleModel(); + return new SimpleNestedPojoModel(simpleModel); + } + + static CollectionNestedPojoModel getCollectionNestedPojoModel() { + return getCollectionNestedPojoModel(false); + } + + static CollectionNestedPojoModel getCollectionNestedPojoModelWithNulls() { + return getCollectionNestedPojoModel(true); + } + + private static CollectionNestedPojoModel getCollectionNestedPojoModel(final boolean useNulls) { + List listSimple; + Set setSimple; + Map mapSimple; + + if (useNulls) { + listSimple = null; + setSimple = null; + mapSimple = null; + } else { + SimpleModel simpleModel = getSimpleModel(); + listSimple = singletonList(simpleModel); + setSimple = new HashSet(listSimple); + mapSimple = new HashMap(); + mapSimple.put("s", simpleModel); + } + + List> listListSimple = singletonList(listSimple); + Set> setSetSimple = new HashSet>(singletonList(setSimple)); + + Map> mapMapSimple = new HashMap>(); + mapMapSimple.put("ms", mapSimple); + + Map> mapListSimple = new HashMap>(); + mapListSimple.put("ls", listSimple); + + Map>> mapListMapSimple = new HashMap>>(); + mapListMapSimple.put("lm", singletonList(mapSimple)); + + Map> mapSetSimple = new HashMap>(); + mapSetSimple.put("s", setSimple); + + List> listMapSimple = singletonList(mapSimple); + List>> listMapListSimple = singletonList(mapListSimple); + List>> listMapSetSimple = singletonList(mapSetSimple); + + return new CollectionNestedPojoModel(listSimple, listListSimple, setSimple, setSetSimple, mapSimple, mapMapSimple, mapListSimple, + mapListMapSimple, mapSetSimple, listMapSimple, listMapListSimple, listMapSetSimple); + } + + static ConventionModel getConventionModel() { + SimpleModel simpleModel = getSimpleModel(); + ConventionModel child = new ConventionModel("child", null, simpleModel); + return new ConventionModel("id", child, null); + } + + static ShapeModelCircle getShapeModelCircle() { + return new ShapeModelCircle("orange", 4.2); + } + + static ShapeModelRectangle getShapeModelRectangle() { + return new ShapeModelRectangle("green", 22.1, 105.0); + } + + static MultipleBoundsModel getMultipleBoundsModel() { + HashMap map = new HashMap(); + map.put("key", "value"); + List list = asList(1, 2, 3); + return new MultipleBoundsModel(map, list, 2.2); + } + + static NestedGenericHolderFieldWithMultipleTypeParamsModel getNestedGenericHolderFieldWithMultipleTypeParamsModel() { + SimpleGenericsModel simple = getSimpleGenericsModelAlt(); + PropertyWithMultipleTypeParamsModel field = + new PropertyWithMultipleTypeParamsModel(simple); + GenericHolderModel> nested = new + GenericHolderModel>(field, 42L); + return new NestedGenericHolderFieldWithMultipleTypeParamsModel(nested); + } + + static NestedGenericHolderSimpleGenericsModel getNestedGenericHolderSimpleGenericsModel() { + SimpleModel simpleModel = getSimpleModel(); + Map map = new HashMap(); + map.put("A", simpleModel); + Map> mapB = new HashMap>(); + mapB.put("A", map); + SimpleGenericsModel, Map> simpleGenericsModel = + new SimpleGenericsModel, Map>(42, 42, + singletonList(singletonList(simpleModel)), mapB); + GenericHolderModel, Map>> nested = + new GenericHolderModel, Map>>(simpleGenericsModel, 42L); + + return new NestedGenericHolderSimpleGenericsModel(nested); + } + + static NestedSelfReferentialGenericHolderModel getNestedSelfReferentialGenericHolderModel() { + SelfReferentialGenericModel selfRef1 = new SelfReferentialGenericModel(true, 33L, + new SelfReferentialGenericModel(44L, false, null)); + SelfReferentialGenericModel selfRef2 = new SelfReferentialGenericModel(true, 3.14, + new SelfReferentialGenericModel(3.42, true, null)); + NestedSelfReferentialGenericModel nested = + new NestedSelfReferentialGenericModel(true, 42L, 44.0, selfRef1, selfRef2); + return new NestedSelfReferentialGenericHolderModel(nested); + } + + static NestedGenericHolderModel getNestedGenericHolderModel() { + return new NestedGenericHolderModel(new GenericHolderModel("generic", 1L)); + } + + static NestedGenericHolderMapModel getNestedGenericHolderMapModel() { + Map mapSimple = new HashMap(); + mapSimple.put("s", getSimpleModel()); + return new NestedGenericHolderMapModel(new GenericHolderModel>(mapSimple, 1L)); + } + + static NestedReusedGenericsModel getNestedReusedGenericsModel() { + return new NestedReusedGenericsModel(new ReusedGenericsModel, String>(1L, + singletonList(getSimpleModel()), "field3", 42, "field5", asList(getSimpleModel(), getSimpleModel()), 2L, "field8")); + } + + static GenericTreeModel getGenericTreeModel() { + return new GenericTreeModel("top", 1, + new GenericTreeModel("left", 2, + new GenericTreeModel("left", 3, null, null), null), + new GenericTreeModel("right", 4, + new GenericTreeModel("left", 5, null, null), null)); + } + + static GenericTreeModel getGenericTreeModelStrings() { + return new GenericTreeModel("top", "1", + new GenericTreeModel("left", "2", + new GenericTreeModel("left", "3", null, null), null), + new GenericTreeModel("right", "4", + new GenericTreeModel("left", "5", null, null), null)); + } + + static InvalidMapModel getInvalidMapModel() { + Map map = new HashMap(); + map.put(1, 1); + map.put(2, 2); + return new InvalidMapModel(map); + } + + static final String SIMPLE_MODEL_JSON = "{'integerField': 42, 'stringField': 'myString'}"; + + class StringToObjectIdCodec implements Codec { + + @Override + public void encode(final BsonWriter writer, final String value, final EncoderContext encoderContext) { + writer.writeObjectId(new ObjectId(value)); + } + + @Override + public Class getEncoderClass() { + return String.class; + } + + @Override + public String decode(final BsonReader reader, final DecoderContext decoderContext) { + return reader.readObjectId().toHexString(); + } + } + + class SimpleEnumCodec implements Codec { + + @Override + public void encode(final BsonWriter writer, final SimpleEnum value, final EncoderContext encoderContext) { + writer.writeInt32(value.ordinal()); + } + + @Override + public Class getEncoderClass() { + return SimpleEnum.class; + } + + @Override + public SimpleEnum decode(final BsonReader reader, final DecoderContext decoderContext) { + int ordinal = reader.readInt32(); + switch (ordinal){ + case 0: + return SimpleEnum.ALPHA; + case 1: + return SimpleEnum.BRAVO; + default: + return SimpleEnum.CHARLIE; + } + } + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/PropertyModelBuilderTest.java b/bson/src/test/unit/org/bson/codecs/pojo/PropertyModelBuilderTest.java new file mode 100644 index 00000000000..3bc54a9cdb8 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/PropertyModelBuilderTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.IntegerCodec; +import org.bson.codecs.pojo.annotations.BsonProperty; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; + +import static junit.framework.TestCase.assertTrue; +import static org.bson.codecs.pojo.PojoBuilderHelper.createPropertyModelBuilder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +public final class PropertyModelBuilderTest { + + private static final String FIELD_NAME = "myFieldName"; + private static final PropertyMetadata PROPERTY_METADATA = + new PropertyMetadata(FIELD_NAME, "MyClass", TypeData.builder(Integer.class).build()); + + @Test + public void testFieldMapping() throws NoSuchFieldException { + PropertyModelBuilder propertyModelBuilder = createPropertyModelBuilder(PROPERTY_METADATA); + assertEquals(FIELD_NAME, propertyModelBuilder.getName()); + assertEquals(FIELD_NAME, propertyModelBuilder.getWriteName()); + assertTrue(propertyModelBuilder.getReadAnnotations().isEmpty()); + assertNull(propertyModelBuilder.isDiscriminatorEnabled()); + } + + @Test + public void testFieldOverrides() throws NoSuchFieldException { + IntegerCodec codec = new IntegerCodec(); + PropertyModelBuilder propertyModelBuilder = createPropertyModelBuilder(PROPERTY_METADATA) + .codec(codec) + .writeName("altDocumentFieldName") + .readAnnotations(ANNOTATIONS) + .propertySerialization(CUSTOM_SERIALIZATION) + .typeData(TypeData.builder(Integer.class).build()) + .propertyAccessor(FIELD_ACCESSOR) + .discriminatorEnabled(false); + + assertEquals(FIELD_NAME, propertyModelBuilder.getName()); + assertEquals("altDocumentFieldName", propertyModelBuilder.getWriteName()); + assertEquals(codec, propertyModelBuilder.getCodec()); + assertEquals(Integer.class, propertyModelBuilder.getTypeData().getType()); + assertEquals(ANNOTATIONS, propertyModelBuilder.getReadAnnotations()); + assertEquals(CUSTOM_SERIALIZATION, propertyModelBuilder.getPropertySerialization()); + assertEquals(FIELD_ACCESSOR, propertyModelBuilder.getPropertyAccessor()); + assertFalse(propertyModelBuilder.isDiscriminatorEnabled()); + } + + @Test(expected = IllegalStateException.class) + public void testMustBeReadableOrWritable() { + createPropertyModelBuilder(PROPERTY_METADATA) + .readName(null) + .writeName(null) + .build(); + } + + private static final List ANNOTATIONS = Collections.singletonList( + new BsonProperty() { + @Override + public Class annotationType() { + return BsonProperty.class; + } + + @Override + public String value() { + return ""; + } + + @Override + public boolean useDiscriminator() { + return true; + } + }); + + private static final PropertySerialization CUSTOM_SERIALIZATION = new PropertySerialization() { + @Override + public boolean shouldSerialize(final Integer value) { + return false; + } + }; + + private static final PropertyAccessor FIELD_ACCESSOR = new PropertyAccessor() { + @Override + public Integer get(final S instance) { + return null; + } + @Override + public void set(final S instance, final Integer value) { + } + }; +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/PropertyModelTest.java b/bson/src/test/unit/org/bson/codecs/pojo/PropertyModelTest.java new file mode 100644 index 00000000000..be99fb56298 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/PropertyModelTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.IntegerCodec; +import org.bson.codecs.pojo.annotations.BsonProperty; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; + +import static junit.framework.TestCase.assertFalse; +import static org.bson.codecs.pojo.PojoBuilderHelper.createPropertyModelBuilder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public final class PropertyModelTest { + + private static final String FIELD_NAME = "myFieldName"; + private static final PropertyMetadata PROPERTY_METADATA = + new PropertyMetadata(FIELD_NAME, "MyClass", TypeData.builder(Integer.class).build()); + + @Test + public void testPropertyMapping() throws NoSuchFieldException { + PropertySerialization serializer = new PropertyModelSerializationImpl(); + PropertyAccessor accessor = new PropertyAccessorImpl(PROPERTY_METADATA); + PropertyModel propertyModel = createPropertyModelBuilder(PROPERTY_METADATA) + .propertySerialization(serializer) + .propertyAccessor(accessor) + .build(); + assertEquals(FIELD_NAME, propertyModel.getName()); + assertEquals(FIELD_NAME, propertyModel.getWriteName()); + assertEquals(serializer, propertyModel.getPropertySerialization()); + assertEquals(accessor, propertyModel.getPropertyAccessor()); + assertNull(propertyModel.getCodec()); + assertNull(propertyModel.getCachedCodec()); + assertNull(propertyModel.useDiscriminator()); + } + + @Test + public void testPropertyOverrides() throws NoSuchFieldException { + IntegerCodec codec = new IntegerCodec(); + PropertyModel propertyModel = createPropertyModelBuilder(PROPERTY_METADATA) + .codec(codec) + .writeName("altDocumentFieldName") + .readAnnotations(ANNOTATIONS) + .propertySerialization(CUSTOM_SERIALIZATION) + .typeData(TypeData.builder(Integer.class).build()) + .propertyAccessor(FIELD_ACCESSOR) + .discriminatorEnabled(false) + .build(); + + assertEquals(FIELD_NAME, propertyModel.getName()); + assertEquals("altDocumentFieldName", propertyModel.getWriteName()); + assertEquals(codec, propertyModel.getCodec()); + assertEquals(codec, propertyModel.getCachedCodec()); + assertEquals(Integer.class, propertyModel.getTypeData().getType()); + assertEquals(CUSTOM_SERIALIZATION, propertyModel.getPropertySerialization()); + assertEquals(FIELD_ACCESSOR, propertyModel.getPropertyAccessor()); + assertFalse(propertyModel.useDiscriminator()); + } + + private static final List ANNOTATIONS = Collections.singletonList( + new BsonProperty() { + @Override + public Class annotationType() { + return BsonProperty.class; + } + + @Override + public String value() { + return ""; + } + + @Override + public boolean useDiscriminator() { + return true; + } + }); + + private static final PropertySerialization CUSTOM_SERIALIZATION = new PropertySerialization() { + @Override + public boolean shouldSerialize(final Integer value) { + return false; + } + }; + + private static final PropertyAccessor FIELD_ACCESSOR = new PropertyAccessor() { + @Override + public Integer get(final S instance) { + return null; + } + @Override + public void set(final S instance, final Integer value) { + } + }; + +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/TypeDataTest.java b/bson/src/test/unit/org/bson/codecs/pojo/TypeDataTest.java new file mode 100644 index 00000000000..03c45dc4b1a --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/TypeDataTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.bson.codecs.pojo.entities.GenericHolderModel; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; + +@SuppressWarnings("rawtypes") +public final class TypeDataTest { + + @Test + public void testDefaults() { + TypeData typeData = TypeData.builder(String.class).build(); + + assertEquals(String.class, typeData.getType()); + assertTrue(typeData.getTypeParameters().isEmpty()); + } + + @Test + public void testListTypeParameters() { + TypeData subTypeData = TypeData.builder(String.class).build(); + TypeData typeData = TypeData.builder(List.class).addTypeParameter(subTypeData).build(); + + assertEquals(List.class, typeData.getType()); + assertEquals(singletonList(subTypeData), typeData.getTypeParameters()); + } + + @Test + public void testMapTypeParameters() { + TypeData keyTypeData = TypeData.builder(String.class).build(); + TypeData valueTypeData = TypeData.builder(Integer.class).build(); + TypeData typeData = TypeData.builder(Map.class).addTypeParameter(keyTypeData).addTypeParameter(valueTypeData).build(); + + assertEquals(Map.class, typeData.getType()); + assertEquals(Arrays.>asList(keyTypeData, valueTypeData), typeData.getTypeParameters()); + } + + @Test + public void testToString() { + TypeData stringType = TypeData.builder(String.class).build(); + TypeData mapTypeData = TypeData.builder(Map.class) + .addTypeParameter(stringType) + .addTypeParameter(TypeData.builder(Map.class).addTypeParameter(stringType).addTypeParameter(stringType).build()) + .build(); + + assertEquals("TypeData{type=String}", stringType.toString()); + assertEquals("TypeData{type=Map, typeParameters=[String, Map]}", mapTypeData.toString()); + } + + @Test + public void testRecursiveTypeData() { + TypeData typeData = TypeData.builder(GenericHolderModel.class) + .addTypeParameter(TypeData.builder(GenericHolderModel.class) + .addTypeParameter(TypeData.builder(GenericHolderModel.class).build()).build()).build(); + + typeData.toString(); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/TypeParameterMapTest.java b/bson/src/test/unit/org/bson/codecs/pojo/TypeParameterMapTest.java new file mode 100644 index 00000000000..95acff41ae0 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/TypeParameterMapTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public final class TypeParameterMapTest { + + @Test + public void testDefault() { + TypeParameterMap typeParameterMap = TypeParameterMap.builder().build(); + assertTrue(typeParameterMap.getPropertyToClassParamIndexMap().isEmpty()); + } + + @Test + public void testClassParamMapsToField() { + TypeParameterMap typeParameterMap = TypeParameterMap.builder().addIndex(1).build(); + Map expected = new HashMap(); + expected.put(-1, 1); + assertEquals(expected, typeParameterMap.getPropertyToClassParamIndexMap()); + } + + @Test + public void testMapsClassAndFieldIndices() { + TypeParameterMap typeParameterMap = TypeParameterMap.builder().addIndex(1, 2).addIndex(2, 2).build(); + Map expected = new HashMap(); + expected.put(1, 2); + expected.put(2, 2); + assertEquals(expected, typeParameterMap.getPropertyToClassParamIndexMap()); + } + + @Test(expected = IllegalStateException.class) + public void testFieldCannotBeGenericAndContainTypeParameters() { + TypeParameterMap.builder().addIndex(1).addIndex(2, 2).build(); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/AbstractCollectionSpecificReturnTypeCreatorModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/AbstractCollectionSpecificReturnTypeCreatorModel.java new file mode 100644 index 00000000000..b9b8226f522 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/AbstractCollectionSpecificReturnTypeCreatorModel.java @@ -0,0 +1,23 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; + +public abstract class AbstractCollectionSpecificReturnTypeCreatorModel { + public abstract List getProperties(); +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/AbstractInterfaceModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/AbstractInterfaceModel.java new file mode 100644 index 00000000000..309e11db127 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/AbstractInterfaceModel.java @@ -0,0 +1,62 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; + +@BsonDiscriminator +public abstract class AbstractInterfaceModel implements InterfaceBasedModel { + private String name; + + public AbstractInterfaceModel() { + } + + public AbstractInterfaceModel(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AbstractInterfaceModel that = (AbstractInterfaceModel) o; + + if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getName() != null ? getName().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalCreatorModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalCreatorModel.java new file mode 100644 index 00000000000..8021ab3cf82 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalCreatorModel.java @@ -0,0 +1,56 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class AsymmetricalCreatorModel { + private final String baz; + + @BsonCreator + public AsymmetricalCreatorModel(@BsonProperty("a") final String a, @BsonProperty("b") final String b) { + this.baz = a + b; + } + + public String getBaz() { + return baz; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AsymmetricalCreatorModel that = (AsymmetricalCreatorModel) o; + + if (getBaz() != null ? !getBaz().equals(that.getBaz()) : that.getBaz() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getBaz() != null ? getBaz().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalIgnoreModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalIgnoreModel.java new file mode 100644 index 00000000000..5c1005d07a3 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalIgnoreModel.java @@ -0,0 +1,114 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonIgnore; + +public final class AsymmetricalIgnoreModel { + @BsonIgnore + private String propertyIgnored; + + private String getterIgnored; + + private String setterIgnored; + + private String getterAndSetterIgnored; + + public AsymmetricalIgnoreModel() { + } + + public AsymmetricalIgnoreModel(final String propertyIgnored, final String getterIgnored, final String setterIgnored, + final String getterAndSetterIgnored) { + this.propertyIgnored = propertyIgnored; + this.getterIgnored = getterIgnored; + this.setterIgnored = setterIgnored; + this.getterAndSetterIgnored = getterAndSetterIgnored; + } + + public String getPropertyIgnored() { + return propertyIgnored; + } + + public void setPropertyIgnored(final String propertyIgnored) { + this.propertyIgnored = propertyIgnored; + } + + @BsonIgnore + public String getGetterIgnored() { + return getterIgnored; + } + + public void setGetterIgnored(final String getterIgnored) { + this.getterIgnored = getterIgnored; + } + + public String getSetterIgnored() { + return setterIgnored; + } + + @BsonIgnore + public void setSetterIgnored(final String setterIgnored) { + this.setterIgnored = setterIgnored; + } + + @BsonIgnore + public String getGetterAndSetterIgnored() { + return getterAndSetterIgnored; + } + + @BsonIgnore + public void setGetterAndSetterIgnored(final String getterAndSetterIgnored) { + this.getterAndSetterIgnored = getterAndSetterIgnored; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AsymmetricalIgnoreModel that = (AsymmetricalIgnoreModel) o; + + if (getPropertyIgnored() != null ? !getPropertyIgnored().equals(that.getPropertyIgnored()) : that.getPropertyIgnored() != null) { + return false; + } + if (getGetterIgnored() != null ? !getGetterIgnored().equals(that.getGetterIgnored()) : that.getGetterIgnored() != null) { + return false; + } + if (getSetterIgnored() != null ? !getSetterIgnored().equals(that.getSetterIgnored()) : that.getSetterIgnored() != null) { + return false; + } + if (getGetterAndSetterIgnored() != null ? !getGetterAndSetterIgnored().equals(that.getGetterAndSetterIgnored()) : that + .getGetterAndSetterIgnored() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getPropertyIgnored() != null ? getPropertyIgnored().hashCode() : 0; + result = 31 * result + (getGetterIgnored() != null ? getGetterIgnored().hashCode() : 0); + result = 31 * result + (getSetterIgnored() != null ? getSetterIgnored().hashCode() : 0); + result = 31 * result + (getGetterAndSetterIgnored() != null ? getGetterAndSetterIgnored().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalModel.java new file mode 100644 index 00000000000..f7366973023 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/AsymmetricalModel.java @@ -0,0 +1,63 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class AsymmetricalModel { + private int baz; + + public AsymmetricalModel() { + } + + public AsymmetricalModel(final int baz) { + this.baz = baz; + } + + @BsonProperty("foo") + public int getBaz() { + return baz; + } + + @BsonProperty("bar") + public void setBaz(final int bar) { + this.baz = bar; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AsymmetricalModel that = (AsymmetricalModel) o; + + if (getBaz() != that.getBaz()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getBaz(); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionNestedPojoModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionNestedPojoModel.java new file mode 100644 index 00000000000..ce81e18897d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionNestedPojoModel.java @@ -0,0 +1,261 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.singletonList; + +public final class CollectionNestedPojoModel { + + @SuppressWarnings("checkstyle:name") + private static List staticSimple = singletonList(new SimpleModel(1, "static")); + private List listSimple; + private List> listListSimple; + + private Set setSimple; + private Set> setSetSimple; + + private Map mapSimple; + private Map> mapMapSimple; + + private Map> mapListSimple; + private Map>> mapListMapSimple; + private Map> mapSetSimple; + + private List> listMapSimple; + private List>> listMapListSimple; + private List>> listMapSetSimple; + + public CollectionNestedPojoModel() { + } + + public CollectionNestedPojoModel(final List listSimple, final List> listListSimple, final + Set setSimple, final Set> setSetSimple, final Map mapSimple, final Map> mapMapSimple, final Map> mapListSimple, final Map>> mapListMapSimple, final Map> mapSetSimple, final List> listMapSimple, final List>> listMapListSimple, final List>> listMapSetSimple) { + this.listSimple = listSimple; + this.listListSimple = listListSimple; + this.setSimple = setSimple; + this.setSetSimple = setSetSimple; + this.mapSimple = mapSimple; + this.mapMapSimple = mapMapSimple; + this.mapListSimple = mapListSimple; + this.mapListMapSimple = mapListMapSimple; + this.mapSetSimple = mapSetSimple; + this.listMapSimple = listMapSimple; + this.listMapListSimple = listMapListSimple; + this.listMapSetSimple = listMapSetSimple; + } + + public static List getStaticSimple() { + return staticSimple; + } + + public static void setStaticSimple(final List staticSimple) { + CollectionNestedPojoModel.staticSimple = staticSimple; + } + + public List getListSimple() { + return listSimple; + } + + public void setListSimple(final List listSimple) { + this.listSimple = listSimple; + } + + public List> getListListSimple() { + return listListSimple; + } + + public void setListListSimple(final List> listListSimple) { + this.listListSimple = listListSimple; + } + + public Set getSetSimple() { + return setSimple; + } + + public void setSetSimple(final Set setSimple) { + this.setSimple = setSimple; + } + + public Set> getSetSetSimple() { + return setSetSimple; + } + + public void setSetSetSimple(final Set> setSetSimple) { + this.setSetSimple = setSetSimple; + } + + public Map getMapSimple() { + return mapSimple; + } + + public void setMapSimple(final Map mapSimple) { + this.mapSimple = mapSimple; + } + + public Map> getMapMapSimple() { + return mapMapSimple; + } + + public void setMapMapSimple(final Map> mapMapSimple) { + this.mapMapSimple = mapMapSimple; + } + + public Map> getMapListSimple() { + return mapListSimple; + } + + public void setMapListSimple(final Map> mapListSimple) { + this.mapListSimple = mapListSimple; + } + + public Map>> getMapListMapSimple() { + return mapListMapSimple; + } + + public void setMapListMapSimple(final Map>> mapListMapSimple) { + this.mapListMapSimple = mapListMapSimple; + } + + public Map> getMapSetSimple() { + return mapSetSimple; + } + + public void setMapSetSimple(final Map> mapSetSimple) { + this.mapSetSimple = mapSetSimple; + } + + public List> getListMapSimple() { + return listMapSimple; + } + + public void setListMapSimple(final List> listMapSimple) { + this.listMapSimple = listMapSimple; + } + + public List>> getListMapListSimple() { + return listMapListSimple; + } + + public void setListMapListSimple(final List>> listMapListSimple) { + this.listMapListSimple = listMapListSimple; + } + + public List>> getListMapSetSimple() { + return listMapSetSimple; + } + + public void setListMapSetSimple(final List>> listMapSetSimple) { + this.listMapSetSimple = listMapSetSimple; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CollectionNestedPojoModel that = (CollectionNestedPojoModel) o; + + if (getListSimple() != null ? !getListSimple().equals(that.getListSimple()) : that.getListSimple() != null) { + return false; + } + if (getListListSimple() != null ? !getListListSimple().equals(that.getListListSimple()) : that.getListListSimple() != null) { + return false; + } + if (getSetSimple() != null ? !getSetSimple().equals(that.getSetSimple()) : that.getSetSimple() != null) { + return false; + } + if (getSetSetSimple() != null ? !getSetSetSimple().equals(that.getSetSetSimple()) : that.getSetSetSimple() != null) { + return false; + } + if (getMapSimple() != null ? !getMapSimple().equals(that.getMapSimple()) : that.getMapSimple() != null) { + return false; + } + if (getMapMapSimple() != null ? !getMapMapSimple().equals(that.getMapMapSimple()) : that.getMapMapSimple() != null) { + return false; + } + if (getMapListSimple() != null ? !getMapListSimple().equals(that.getMapListSimple()) : that.getMapListSimple() != null) { + return false; + } + if (getMapListMapSimple() != null ? !getMapListMapSimple().equals(that.getMapListMapSimple()) + : that.getMapListMapSimple() != null) { + return false; + } + if (getMapSetSimple() != null ? !getMapSetSimple().equals(that.getMapSetSimple()) : that.getMapSetSimple() != null) { + return false; + } + if (getListMapSimple() != null ? !getListMapSimple().equals(that.getListMapSimple()) : that.getListMapSimple() != null) { + return false; + } + if (getListMapListSimple() != null ? !getListMapListSimple().equals(that.getListMapListSimple()) + : that.getListMapListSimple() != null) { + return false; + } + if (getListMapSetSimple() != null ? !getListMapSetSimple().equals(that.getListMapSetSimple()) + : that.getListMapSetSimple() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getListSimple() != null ? getListSimple().hashCode() : 0; + result = 31 * result + (getListListSimple() != null ? getListListSimple().hashCode() : 0); + result = 31 * result + (getSetSimple() != null ? getSetSimple().hashCode() : 0); + result = 31 * result + (getSetSetSimple() != null ? getSetSetSimple().hashCode() : 0); + result = 31 * result + (getMapSimple() != null ? getMapSimple().hashCode() : 0); + result = 31 * result + (getMapMapSimple() != null ? getMapMapSimple().hashCode() : 0); + result = 31 * result + (getMapListSimple() != null ? getMapListSimple().hashCode() : 0); + result = 31 * result + (getMapListMapSimple() != null ? getMapListMapSimple().hashCode() : 0); + result = 31 * result + (getMapSetSimple() != null ? getMapSetSimple().hashCode() : 0); + result = 31 * result + (getListMapSimple() != null ? getListMapSimple().hashCode() : 0); + result = 31 * result + (getListMapListSimple() != null ? getListMapListSimple().hashCode() : 0); + result = 31 * result + (getListMapSetSimple() != null ? getListMapSetSimple().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "CollectionNestedPojoModel{" + + "listSimple=" + listSimple + + ", listListSimple=" + listListSimple + + ", setSimple=" + setSimple + + ", setSetSimple=" + setSetSimple + + ", mapSimple=" + mapSimple + + ", mapMapSimple=" + mapMapSimple + + ", mapListSimple=" + mapListSimple + + ", mapListMapSimple=" + mapListMapSimple + + ", mapSetSimple=" + mapSetSimple + + ", listMapSimple=" + listMapSimple + + ", listMapListSimple=" + listMapListSimple + + ", listMapSetSimple=" + listMapSetSimple + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionSpecificReturnTypeCreatorModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionSpecificReturnTypeCreatorModel.java new file mode 100644 index 00000000000..761b7fe9a7b --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionSpecificReturnTypeCreatorModel.java @@ -0,0 +1,54 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.util.List; + +public class CollectionSpecificReturnTypeCreatorModel extends AbstractCollectionSpecificReturnTypeCreatorModel { + private final ImmutableList properties; + + @BsonCreator + public CollectionSpecificReturnTypeCreatorModel(@BsonProperty("properties") final List properties) { + this.properties = ImmutableList.copyOf(properties); + } + + public ImmutableList getProperties() { + return properties; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CollectionSpecificReturnTypeCreatorModel that = (CollectionSpecificReturnTypeCreatorModel) o; + + return properties != null ? properties.equals(that.properties) : that.properties == null; + } + + @Override + public int hashCode() { + return properties != null ? properties.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionSpecificReturnTypeModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionSpecificReturnTypeModel.java new file mode 100644 index 00000000000..cf984f6c731 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/CollectionSpecificReturnTypeModel.java @@ -0,0 +1,57 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; + +public class CollectionSpecificReturnTypeModel { + private ImmutableList properties; + + public CollectionSpecificReturnTypeModel() { + } + + public CollectionSpecificReturnTypeModel(final List properties) { + this.properties = ImmutableList.copyOf(properties); + } + + public ImmutableList getProperties() { + return properties; + } + + public void setProperties(final List properties) { + this.properties = ImmutableList.copyOf(properties); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CollectionSpecificReturnTypeModel that = (CollectionSpecificReturnTypeModel) o; + + return properties != null ? properties.equals(that.properties) : that.properties == null; + } + + @Override + public int hashCode() { + return properties != null ? properties.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteAndNestedAbstractInterfaceModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteAndNestedAbstractInterfaceModel.java new file mode 100644 index 00000000000..9d56540005a --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteAndNestedAbstractInterfaceModel.java @@ -0,0 +1,87 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + + +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.util.List; + +public final class ConcreteAndNestedAbstractInterfaceModel extends AbstractInterfaceModel { + @BsonProperty(useDiscriminator = true) + private InterfaceBasedModel child; + private List wildcardList; + + public ConcreteAndNestedAbstractInterfaceModel() { + super(); + } + + public ConcreteAndNestedAbstractInterfaceModel(final String name, final InterfaceBasedModel child) { + super(name); + this.child = child; + } + + public ConcreteAndNestedAbstractInterfaceModel(final String name, final List wildcardList) { + super(name); + this.child = null; + this.wildcardList = wildcardList; + } + + public InterfaceBasedModel getChild() { + return child; + } + + public void setChild(final InterfaceBasedModel child) { + this.child = child; + } + + public List getWildcardList() { + return wildcardList; + } + + public void setWildcardList(final List wildcardList) { + this.wildcardList = wildcardList; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + ConcreteAndNestedAbstractInterfaceModel that = (ConcreteAndNestedAbstractInterfaceModel) o; + + if (getChild() != null ? !getChild().equals(that.getChild()) : that.getChild() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (getChild() != null ? getChild().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteCollectionsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteCollectionsModel.java new file mode 100644 index 00000000000..e29e45e309d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteCollectionsModel.java @@ -0,0 +1,123 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class ConcreteCollectionsModel { + private Collection collection; + private List list; + private LinkedList linked; + private Map map; + private ConcurrentHashMap concurrent; + + public ConcreteCollectionsModel() { + } + + public ConcreteCollectionsModel(final Collection collection, final List list, final LinkedList linked, + final Map map, final ConcurrentHashMap concurrent) { + this.collection = collection; + this.list = list; + this.linked = linked; + this.map = map; + this.concurrent = concurrent; + } + + public Collection getCollection() { + return collection; + } + + public void setCollection(final Collection collection) { + this.collection = collection; + } + + public List getList() { + return list; + } + + public void setList(final List list) { + this.list = list; + } + + public LinkedList getLinked() { + return linked; + } + + public void setLinked(final LinkedList linked) { + this.linked = linked; + } + + public Map getMap() { + return map; + } + + public void setMap(final Map map) { + this.map = map; + } + + public ConcurrentHashMap getConcurrent() { + return concurrent; + } + + public void setConcurrent(final ConcurrentHashMap concurrent) { + this.concurrent = concurrent; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ConcreteCollectionsModel that = (ConcreteCollectionsModel) o; + + if (getCollection() != null ? !getCollection().equals(that.getCollection()) : that.getCollection() != null) { + return false; + } + if (getList() != null ? !getList().equals(that.getList()) : that.getList() != null) { + return false; + } + if (getLinked() != null ? !getLinked().equals(that.getLinked()) : that.getLinked() != null) { + return false; + } + if (getMap() != null ? !getMap().equals(that.getMap()) : that.getMap() != null) { + return false; + } + if (getConcurrent() != null ? !getConcurrent().equals(that.getConcurrent()) : that.getConcurrent() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getCollection() != null ? getCollection().hashCode() : 0; + result = 31 * result + (getList() != null ? getList().hashCode() : 0); + result = 31 * result + (getLinked() != null ? getLinked().hashCode() : 0); + result = 31 * result + (getMap() != null ? getMap().hashCode() : 0); + result = 31 * result + (getConcurrent() != null ? getConcurrent().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteInterfaceGenericModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteInterfaceGenericModel.java new file mode 100644 index 00000000000..de73e8d46a7 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteInterfaceGenericModel.java @@ -0,0 +1,57 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public class ConcreteInterfaceGenericModel implements InterfaceGenericModel { + private String property; + + public ConcreteInterfaceGenericModel() { + } + + public ConcreteInterfaceGenericModel(final String property) { + this.property = property; + } + + @Override + public String getPropertyA() { + return property; + } + + @Override + public void setPropertyA(final String property) { + this.property = property; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ConcreteInterfaceGenericModel that = (ConcreteInterfaceGenericModel) o; + + return property != null ? property.equals(that.property) : that.property == null; + } + + @Override + public int hashCode() { + return property != null ? property.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteStandAloneAbstractInterfaceModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteStandAloneAbstractInterfaceModel.java new file mode 100644 index 00000000000..701897aef60 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConcreteStandAloneAbstractInterfaceModel.java @@ -0,0 +1,29 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class ConcreteStandAloneAbstractInterfaceModel extends AbstractInterfaceModel { + + public ConcreteStandAloneAbstractInterfaceModel() { + super(); + } + + public ConcreteStandAloneAbstractInterfaceModel(final String name) { + super(name); + } + +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ConstructorNotPublicModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConstructorNotPublicModel.java new file mode 100644 index 00000000000..1f9f93ec76e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConstructorNotPublicModel.java @@ -0,0 +1,64 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class ConstructorNotPublicModel { + private final Integer integerField; + + ConstructorNotPublicModel(final Integer integerField) { + this.integerField = integerField; + } + + public static ConstructorNotPublicModel create(final Integer integerField) { + return new ConstructorNotPublicModel(integerField); + } + + public Integer getIntegerField() { + return integerField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ConstructorNotPublicModel that = (ConstructorNotPublicModel) o; + + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + return result; + } + + @Override + public String toString() { + return "ConstructorNotPublicModel{" + + "integerField=" + integerField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ContainsAlternativeMapAndCollectionModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ContainsAlternativeMapAndCollectionModel.java new file mode 100644 index 00000000000..82176f55df8 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ContainsAlternativeMapAndCollectionModel.java @@ -0,0 +1,85 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.BsonArray; +import org.bson.BsonDocument; + +public final class ContainsAlternativeMapAndCollectionModel { + private BsonArray customList; + private BsonDocument customMap; + + public ContainsAlternativeMapAndCollectionModel() { + } + + public ContainsAlternativeMapAndCollectionModel(final BsonDocument source) { + this.customList = source.getArray("customList"); + this.customMap = source.getDocument("customMap"); + } + + public void setCustomList(final BsonArray customList) { + this.customList = customList; + } + + public void setCustomMap(final BsonDocument customMap) { + this.customMap = customMap; + } + + public BsonArray getCustomList() { + return customList; + } + + public BsonDocument getCustomMap() { + return customMap; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ContainsAlternativeMapAndCollectionModel that = (ContainsAlternativeMapAndCollectionModel) o; + + if (getCustomList() != null ? !getCustomList().equals(that.getCustomList()) : that.getCustomList() != null) { + return false; + } + if (getCustomMap() != null ? !getCustomMap().equals(that.getCustomMap()) : that.getCustomMap() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getCustomList() != null ? getCustomList().hashCode() : 0; + result = 31 * result + (getCustomMap() != null ? getCustomMap().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ContainsAlternativeMapAndCollectionModel{" + + "customList=" + customList + + ", customMap=" + customMap + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ConventionModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConventionModel.java new file mode 100644 index 00000000000..dbf4ac9ab21 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConventionModel.java @@ -0,0 +1,134 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonProperty; + + +@BsonDiscriminator(value = "AnnotatedConventionModel", key = "_cls") +public final class ConventionModel { + private static final int myStaticField = 10; + private transient int myTransientField = 10; + private final int myFinalField = 10; + private int myIntField = 10; + + @BsonId() + private String customId; + + @BsonProperty(useDiscriminator = false) + private ConventionModel child; + + @BsonProperty(value = "model", useDiscriminator = false) + private SimpleModel simpleModel; + + public ConventionModel(){ + } + + public ConventionModel(final String customId, final ConventionModel child, final SimpleModel simpleModel) { + this.myIntField = myIntField; + this.customId = customId; + this.child = child; + this.simpleModel = simpleModel; + } + + public int getMyIntField() { + return myIntField; + } + + public void setMyIntField(final int myIntField) { + this.myIntField = myIntField; + } + + public int getMyFinalField() { + return myFinalField; + } + + public int getMyTransientField() { + return myTransientField; + } + + public void setMyTransientField(final int myTransientField) { + this.myTransientField = myTransientField; + } + + public String getCustomId() { + return customId; + } + + public void setCustomId(final String customId) { + this.customId = customId; + } + + public ConventionModel getChild() { + return child; + } + + public void setChild(final ConventionModel child) { + this.child = child; + } + + public SimpleModel getSimpleModel() { + return simpleModel; + } + + public void setSimpleModel(final SimpleModel simpleModel) { + this.simpleModel = simpleModel; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ConventionModel that = (ConventionModel) o; + + if (getCustomId() != null ? !getCustomId().equals(that.getCustomId()) : that.getCustomId() != null) { + return false; + } + if (getChild() != null ? !getChild().equals(that.getChild()) : that.getChild() != null) { + return false; + } + if (getSimpleModel() != null ? !getSimpleModel().equals(that.getSimpleModel()) : that.getSimpleModel() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getCustomId() != null ? getCustomId().hashCode() : 0; + result = 31 * result + (getChild() != null ? getChild().hashCode() : 0); + result = 31 * result + (getSimpleModel() != null ? getSimpleModel().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ConventionModel{" + + "customId='" + customId + "'" + + ", child=" + child + + ", simpleModel=" + simpleModel + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ConverterModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConverterModel.java new file mode 100644 index 00000000000..f27d28aa529 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ConverterModel.java @@ -0,0 +1,74 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class ConverterModel { + private String id; + private String name; + + public ConverterModel() { + } + + public ConverterModel(final String id, final String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ConverterModel that = (ConverterModel) o; + + if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) { + return false; + } + if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getId() != null ? getId().hashCode() : 0; + result = 31 * result + (getName() != null ? getName().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/CustomPropertyCodecOptionalModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/CustomPropertyCodecOptionalModel.java new file mode 100644 index 00000000000..c28da6da915 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/CustomPropertyCodecOptionalModel.java @@ -0,0 +1,58 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public class CustomPropertyCodecOptionalModel { + private final Optional optionalField; + + @BsonCreator + public CustomPropertyCodecOptionalModel(final @BsonProperty("optionalField") Optional optionalField) { + this.optionalField = optionalField == null ? Optional.empty() : optionalField; + } + + public Optional getOptionalField() { + return optionalField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CustomPropertyCodecOptionalModel that = (CustomPropertyCodecOptionalModel) o; + + return optionalField != null ? optionalField.equals(that.optionalField) : that.optionalField == null; + } + + @Override + public int hashCode() { + return optionalField != null ? optionalField.hashCode() : 0; + } + + @Override + public String toString() { + return "CustomPropertyCodecOptionalModel{" + + "optionalField=" + optionalField + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/DuplicateAnnotationAllowedModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/DuplicateAnnotationAllowedModel.java new file mode 100644 index 00000000000..dd9aed8dc49 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/DuplicateAnnotationAllowedModel.java @@ -0,0 +1,89 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import javax.annotation.Nullable; + +public class DuplicateAnnotationAllowedModel { + + @Nullable + private String id; + + @BsonIgnore + private String ignoredString; + + @BsonProperty("property") + private String propertyString; + + public DuplicateAnnotationAllowedModel() { + } + + public DuplicateAnnotationAllowedModel(final String id) { + this.id = id; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable final String id) { + this.id = id; + } + + @BsonIgnore + public String getIgnoredString() { + return ignoredString; + } + + @BsonIgnore + public void setIgnoredString(final String ignoredString) { + this.ignoredString = ignoredString; + } + + @BsonProperty("property") + public String getPropertyString() { + return propertyString; + } + + @BsonProperty("property") + public void setPropertyString(final String propertyString) { + this.propertyString = propertyString; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DuplicateAnnotationAllowedModel that = (DuplicateAnnotationAllowedModel) o; + + return (id != null ? id.equals(that.id) : that.id == null); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/FieldAndPropertyTypeMismatchModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/FieldAndPropertyTypeMismatchModel.java new file mode 100644 index 00000000000..c6cb93ce6a7 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/FieldAndPropertyTypeMismatchModel.java @@ -0,0 +1,64 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.Arrays; + +public class FieldAndPropertyTypeMismatchModel { + private byte[] stringField; + + public FieldAndPropertyTypeMismatchModel() { + } + + public FieldAndPropertyTypeMismatchModel(final String stringField) { + this.stringField = stringField.getBytes(); + } + + public String getStringField() { + return new String(stringField); + } + + public void setStringField(final String stringField) { + this.stringField = stringField.getBytes(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FieldAndPropertyTypeMismatchModel that = (FieldAndPropertyTypeMismatchModel) o; + + return Arrays.equals(stringField, that.stringField); + } + + @Override + public int hashCode() { + return Arrays.hashCode(stringField); + } + + @Override + public String toString() { + return "FieldAndPropertyTypeMismatchModel{" + + "stringField=" + new String(stringField) + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/GenericHolderModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/GenericHolderModel.java new file mode 100644 index 00000000000..1a40376cd95 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/GenericHolderModel.java @@ -0,0 +1,75 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public class GenericHolderModel

    { + + private P myGenericField; + private Long myLongField; + + public GenericHolderModel() { + } + + public GenericHolderModel(final P myGenericField, final Long myLongField) { + this.myGenericField = myGenericField; + this.myLongField = myLongField; + } + + public P getMyGenericField() { + return myGenericField; + } + + public void setMyGenericField(final P myGenericField) { + this.myGenericField = myGenericField; + } + + public Long getMyLongField() { + return myLongField; + } + + public void setMyLongField(final Long myLongField) { + this.myLongField = myLongField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof GenericHolderModel)) { + return false; + } + + GenericHolderModel that = (GenericHolderModel) o; + + if (getMyGenericField() != null ? !getMyGenericField().equals(that.getMyGenericField()) : that.getMyGenericField() != null) { + return false; + } + if (getMyLongField() != null ? !getMyLongField().equals(that.getMyLongField()) : that.getMyLongField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getMyGenericField() != null ? getMyGenericField().hashCode() : 0; + result = 31 * result + (getMyLongField() != null ? getMyLongField().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/GenericTreeModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/GenericTreeModel.java new file mode 100644 index 00000000000..92ee9d9f9c6 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/GenericTreeModel.java @@ -0,0 +1,103 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class GenericTreeModel { + + private A field1; + private B field2; + private GenericTreeModel left; + private GenericTreeModel right; + + public GenericTreeModel() { + } + + public GenericTreeModel(final A field1, final B field2, final GenericTreeModel left, final GenericTreeModel right) { + this.field1 = field1; + this.field2 = field2; + this.left = left; + this.right = right; + } + + public A getField1() { + return field1; + } + + public void setField1(final A field1) { + this.field1 = field1; + } + + public B getField2() { + return field2; + } + + public void setField2(final B field2) { + this.field2 = field2; + } + + public GenericTreeModel getLeft() { + return left; + } + + public void setLeft(final GenericTreeModel left) { + this.left = left; + } + + public GenericTreeModel getRight() { + return right; + } + + public void setRight(final GenericTreeModel right) { + this.right = right; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + GenericTreeModel that = (GenericTreeModel) o; + + if (getField1() != null ? !getField1().equals(that.getField1()) : that.getField1() != null) { + return false; + } + if (getField2() != null ? !getField2().equals(that.getField2()) : that.getField2() != null) { + return false; + } + if (getLeft() != null ? !getLeft().equals(that.getLeft()) : that.getLeft() != null) { + return false; + } + if (getRight() != null ? !getRight().equals(that.getRight()) : that.getRight() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getField1() != null ? getField1().hashCode() : 0; + result = 31 * result + (getField2() != null ? getField2().hashCode() : 0); + result = 31 * result + (getLeft() != null ? getLeft().hashCode() : 0); + result = 31 * result + (getRight() != null ? getRight().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ImmutableList.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ImmutableList.java new file mode 100644 index 00000000000..de65db6537e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ImmutableList.java @@ -0,0 +1,166 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + + +public final class ImmutableList implements List { + + final List list; + + public static ImmutableList copyOf(final List list) { + if (list instanceof ImmutableList) { + return (ImmutableList) list; + } else { + return new ImmutableList(new ArrayList(list)); + } + } + + private ImmutableList(final List list) { + this.list = list; + } + + @Override + public int size() { + return list.size(); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + + @Override + public boolean contains(final Object o) { + return list.contains(o); + } + + @Override + public Iterator iterator() { + return list.iterator(); + } + + @Override + public Object[] toArray() { + return list.toArray(); + } + + @Override + public T1[] toArray(final T1[] a) { + return list.toArray(a); + } + + @Override + public boolean add(final T t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(final Collection c) { + return list.containsAll(c); + } + + @Override + public boolean addAll(final Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final int index, final Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + + } + + @Override + public boolean equals(final Object o) { + return list.equals(o); + } + + @Override + public int hashCode() { + return list.hashCode(); + } + + @Override + public T get(final int index) { + return list.get(index); + } + + @Override + public T set(final int index, final T element) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(final int index, final T element) { + throw new UnsupportedOperationException(); + } + + @Override + public T remove(final int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int indexOf(final Object o) { + return list.indexOf(o); + } + + @Override + public int lastIndexOf(final Object o) { + return list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return list.listIterator(); + } + + @Override + public ListIterator listIterator(final int index) { + return list.listIterator(index); + } + + @Override + public List subList(final int fromIndex, final int toIndex) { + return new ImmutableList(list.subList(fromIndex, toIndex)); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceBasedModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceBasedModel.java new file mode 100644 index 00000000000..b641879b7df --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceBasedModel.java @@ -0,0 +1,20 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public interface InterfaceBasedModel { +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceGenericModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceGenericModel.java new file mode 100644 index 00000000000..dab1683daae --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceGenericModel.java @@ -0,0 +1,25 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + + +public interface InterfaceGenericModel { + + T getPropertyA(); + + void setPropertyA(T property); +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelA.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelA.java new file mode 100644 index 00000000000..a4771f4b3f5 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelA.java @@ -0,0 +1,25 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + + +public interface InterfaceModelA { + + String getPropertyA(); + + void setPropertyA(String property); +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelAbstract.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelAbstract.java new file mode 100644 index 00000000000..34a1a7c4350 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelAbstract.java @@ -0,0 +1,62 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public abstract class InterfaceModelAbstract implements InterfaceModelA { + + private String propertyA; + + public InterfaceModelAbstract() { + } + + public InterfaceModelAbstract(final String propertyA) { + this.propertyA = propertyA; + } + + @Override + public String getPropertyA() { + return propertyA; + } + + @Override + public void setPropertyA(final String property) { + this.propertyA = property; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InterfaceModelAbstract that = (InterfaceModelAbstract) o; + + if (getPropertyA() != null ? !getPropertyA().equals(that.getPropertyA()) : that.getPropertyA() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getPropertyA() != null ? getPropertyA().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelB.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelB.java new file mode 100644 index 00000000000..687ac46af7e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelB.java @@ -0,0 +1,25 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + + +public interface InterfaceModelB extends InterfaceModelA { + + String getPropertyB(); + + void setPropertyB(String propertyB); +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelImpl.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelImpl.java new file mode 100644 index 00000000000..90828dc8e6e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceModelImpl.java @@ -0,0 +1,69 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public class InterfaceModelImpl extends InterfaceModelAbstract implements InterfaceModelB { + + private String propertyB; + + public InterfaceModelImpl() { + } + + public InterfaceModelImpl(final String propertyA, final String propertyB) { + super(propertyA); + this.propertyB = propertyB; + } + + @Override + public String getPropertyB() { + return propertyB; + } + + @Override + public void setPropertyB(final String propertyB) { + this.propertyB = propertyB; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InterfaceModelImpl that = (InterfaceModelImpl) o; + + if (getPropertyA() != null ? !getPropertyA().equals(that.getPropertyA()) : that.getPropertyA() != null) { + return false; + } + + if (getPropertyB() != null ? !getPropertyB().equals(that.getPropertyB()) : that.getPropertyB() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getPropertyA() != null ? getPropertyA().hashCode() : 0; + result = 31 * result + getPropertyB() != null ? getPropertyB().hashCode() : 0; + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModel.java new file mode 100644 index 00000000000..e74cf54c5df --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModel.java @@ -0,0 +1,21 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public interface InterfaceUpperBoundsModel { + T getNestedModel(); +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModelAbstract.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModelAbstract.java new file mode 100644 index 00000000000..451615aebe7 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModelAbstract.java @@ -0,0 +1,21 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +abstract class InterfaceUpperBoundsModelAbstract implements InterfaceUpperBoundsModel { + public abstract String getName(); +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModelAbstractImpl.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModelAbstractImpl.java new file mode 100644 index 00000000000..88f2df94325 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InterfaceUpperBoundsModelAbstractImpl.java @@ -0,0 +1,72 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public class InterfaceUpperBoundsModelAbstractImpl extends InterfaceUpperBoundsModelAbstract { + private String name; + private InterfaceModelImpl nestedModel; + + public InterfaceUpperBoundsModelAbstractImpl() { + } + + public InterfaceUpperBoundsModelAbstractImpl(final String name, final InterfaceModelImpl nestedModel) { + this.name = name; + this.nestedModel = nestedModel; + } + + @Override + public String getName() { + return name; + } + + @Override + public InterfaceModelImpl getNestedModel() { + return nestedModel; + } + + public void setName(final String name) { + this.name = name; + } + + public void setNestedModel(final InterfaceModelImpl nestedModel) { + this.nestedModel = nestedModel; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InterfaceUpperBoundsModelAbstractImpl that = (InterfaceUpperBoundsModelAbstractImpl) o; + + if (name != null ? !name.equals(that.name) : that.name != null) { + return false; + } + return nestedModel != null ? nestedModel.equals(that.nestedModel) : that.nestedModel == null; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (nestedModel != null ? nestedModel.hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidCollection.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidCollection.java new file mode 100644 index 00000000000..47df811674e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidCollection.java @@ -0,0 +1,113 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +@SuppressWarnings("rawtypes") +public class InvalidCollection implements Collection { + private final List wrapped; + + public InvalidCollection(final List wrapped) { + this.wrapped = new ArrayList(wrapped); + } + + @Override + public int size() { + return wrapped.size(); + } + + @Override + public boolean isEmpty() { + return wrapped.isEmpty(); + } + + @Override + public boolean contains(final Object o) { + return wrapped.contains(o); + } + + @Override + public Iterator iterator() { + return wrapped.iterator(); + } + + @Override + public Object[] toArray() { + return wrapped.toArray(); + } + + @Override + public boolean add(final Object o) { + return false; + } + + @Override + public boolean remove(final Object o) { + return false; + } + + @Override + public boolean addAll(final Collection c) { + return false; + } + + @Override + public void clear() { + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + InvalidCollection that = (InvalidCollection) o; + return wrapped.equals(that.wrapped); + } + + @Override + public int hashCode() { + return wrapped.hashCode(); + } + + @Override + public boolean retainAll(final Collection c) { + return false; + } + + @Override + public boolean removeAll(final Collection c) { + return false; + } + + @Override + public boolean containsAll(final Collection c) { + return wrapped.containsAll(c); + } + + @Override + public Object[] toArray(final Object[] a) { + return wrapped.toArray(a); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidCollectionModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidCollectionModel.java new file mode 100644 index 00000000000..7a5f045bc67 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidCollectionModel.java @@ -0,0 +1,56 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; + +public class InvalidCollectionModel { + + private InvalidCollection collectionField; + + public InvalidCollectionModel() { + } + + public InvalidCollectionModel(final List list) { + this.collectionField = new InvalidCollection(list); + } + + public InvalidCollection getCollectionField() { + return collectionField; + } + + public void setCollectionField(final InvalidCollection collectionField) { + this.collectionField = collectionField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + InvalidCollectionModel that = (InvalidCollectionModel) o; + return collectionField.equals(that.collectionField); + } + + @Override + public int hashCode() { + return collectionField.hashCode(); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidGetterAndSetterModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidGetterAndSetterModel.java new file mode 100644 index 00000000000..c185505f17b --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidGetterAndSetterModel.java @@ -0,0 +1,82 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class InvalidGetterAndSetterModel { + private Integer integerField; + private String stringField; + + public InvalidGetterAndSetterModel(){ + } + + public InvalidGetterAndSetterModel(final Integer integerField, final String stringField) { + this.integerField = integerField; + this.stringField = stringField; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(final Integer integerField) { + throw new UnsupportedOperationException("Nope"); + } + + public String getStringField() { + throw new UnsupportedOperationException("Nope"); + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InvalidGetterAndSetterModel that = (InvalidGetterAndSetterModel) o; + + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "InvalidGetterAndSetterModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidMapModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidMapModel.java new file mode 100644 index 00000000000..c7e7ea67e58 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidMapModel.java @@ -0,0 +1,57 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.Map; + +public final class InvalidMapModel { + private Map invalidMap; + + public InvalidMapModel() { + } + + public InvalidMapModel(final Map invalidMap) { + this.invalidMap = invalidMap; + } + + public Map getInvalidMap() { + return invalidMap; + } + + public void setInvalidMap(final Map invalidMap) { + this.invalidMap = invalidMap; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InvalidMapModel that = (InvalidMapModel) o; + + return invalidMap != null ? invalidMap.equals(that.invalidMap) : that.invalidMap == null; + } + + @Override + public int hashCode() { + return invalidMap != null ? invalidMap.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidMapPropertyCodecProvider.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidMapPropertyCodecProvider.java new file mode 100644 index 00000000000..4a66f47fe97 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidMapPropertyCodecProvider.java @@ -0,0 +1,79 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.BsonReader; +import org.bson.BsonType; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.pojo.PropertyCodecProvider; +import org.bson.codecs.pojo.PropertyCodecRegistry; +import org.bson.codecs.pojo.TypeWithTypeParameters; + +import java.util.HashMap; +import java.util.Map; + +public class InvalidMapPropertyCodecProvider implements PropertyCodecProvider { + + @SuppressWarnings("unchecked") + @Override + public Codec get(final TypeWithTypeParameters type, final PropertyCodecRegistry registry) { + if (Map.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 2 + && type.getTypeParameters().get(0).getType().equals(Integer.class) + && type.getTypeParameters().get(1).getType().equals(Integer.class)) { + return (Codec) new InvalidMapModelCodec((Class>) type.getType()); + } else { + return null; + } + } + + private static final class InvalidMapModelCodec implements Codec> { + private Class> encoderClass; + + private InvalidMapModelCodec(final Class> encoderClass) { + this.encoderClass = encoderClass; + } + + @Override + public Map decode(final BsonReader reader, final DecoderContext decoderContext) { + Map map = new HashMap(); + + reader.readStartDocument(); + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + map.put(Integer.valueOf(reader.readName()), reader.readInt32()); + } + reader.readEndDocument(); + return map; + } + + @Override + public void encode(final BsonWriter writer, final Map value, final EncoderContext encoderContext) { + writer.writeStartDocument(); + for (Map.Entry entry : value.entrySet()) { + writer.writeInt32(entry.getKey().toString(), entry.getValue()); + } + writer.writeEndDocument(); + } + + @Override + public Class> getEncoderClass() { + return encoderClass; + } + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidSetterArgsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidSetterArgsModel.java new file mode 100644 index 00000000000..f913025e3aa --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/InvalidSetterArgsModel.java @@ -0,0 +1,82 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class InvalidSetterArgsModel { + private Integer integerField; + private String stringField; + + public InvalidSetterArgsModel(){ + } + + public InvalidSetterArgsModel(final Integer integerField, final String stringField) { + this.integerField = integerField; + this.stringField = stringField; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(final Integer integerField) { + this.integerField = integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final Integer stringField) { + this.stringField = stringField.toString(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InvalidSetterArgsModel that = (InvalidSetterArgsModel) o; + + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "InvalidSetterArgsModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/MapStringObjectModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/MapStringObjectModel.java new file mode 100644 index 00000000000..45bd893f918 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/MapStringObjectModel.java @@ -0,0 +1,57 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.Map; + +public class MapStringObjectModel { + private Map map; + + public MapStringObjectModel() { + } + + public MapStringObjectModel(final Map map) { + this.map = map; + } + + public Map getMap() { + return map; + } + + public void setMap(final Map map) { + this.map = map; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MapStringObjectModel that = (MapStringObjectModel) o; + + return getMap() != null ? getMap().equals(that.getMap()) : that.getMap() == null; + } + + @Override + public int hashCode() { + return getMap() != null ? getMap().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel1.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel1.java new file mode 100644 index 00000000000..39fe4e156b2 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel1.java @@ -0,0 +1,69 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; +import java.util.Map; + +public class MultipleBoundsLevel1 extends MultipleBoundsLevel2 { + private T level1; + + public MultipleBoundsLevel1() { + super(); + } + + public MultipleBoundsLevel1(final Map level3, final List level2, final T level1) { + super(level3, level2); + this.level1 = level1; + } + + public T getLevel1() { + return level1; + } + + public void setLevel1(final T level1) { + this.level1 = level1; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + MultipleBoundsLevel1 that = (MultipleBoundsLevel1) o; + + if (level1 != null ? !level1.equals(that.level1) : that.level1 != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (level1 != null ? level1.hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel2.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel2.java new file mode 100644 index 00000000000..67406dae3aa --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel2.java @@ -0,0 +1,69 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; +import java.util.Map; + +public class MultipleBoundsLevel2 extends MultipleBoundsLevel3 { + private List level2; + + public MultipleBoundsLevel2() { + super(); + } + + public MultipleBoundsLevel2(final Map level3, final List level2) { + super(level3); + this.level2 = level2; + } + + public List getLevel2() { + return level2; + } + + public void setLevel2(final List level2) { + this.level2 = level2; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + MultipleBoundsLevel2 that = (MultipleBoundsLevel2) o; + + if (level2 != null ? !level2.equals(that.level2) : that.level2 != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (level2 != null ? level2.hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel3.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel3.java new file mode 100644 index 00000000000..f5313c05e71 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsLevel3.java @@ -0,0 +1,61 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.Map; + +public class MultipleBoundsLevel3 { + private Map level3; + + public MultipleBoundsLevel3() { + } + + public MultipleBoundsLevel3(final Map level3) { + this.level3 = level3; + } + + public Map getLevel3() { + return level3; + } + + public void setLevel3(final Map level3) { + this.level3 = level3; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MultipleBoundsLevel3 that = (MultipleBoundsLevel3) o; + + if (level3 != null ? !level3.equals(that.level3) : that.level3 != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return level3 != null ? level3.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsModel.java new file mode 100644 index 00000000000..66c8e080c09 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleBoundsModel.java @@ -0,0 +1,31 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; +import java.util.Map; + +public final class MultipleBoundsModel extends MultipleBoundsLevel1 { + + public MultipleBoundsModel() { + super(); + } + + public MultipleBoundsModel(final Map level3, final List level2, final Double level1) { + super(level3, level2, level1); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleLevelGenericModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleLevelGenericModel.java new file mode 100644 index 00000000000..dc25ee57b29 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/MultipleLevelGenericModel.java @@ -0,0 +1,83 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class MultipleLevelGenericModel { + + private A stringField; + private GenericTreeModel nested; + + public MultipleLevelGenericModel() { + } + + public MultipleLevelGenericModel(final A stringField, final GenericTreeModel nested) { + this.stringField = stringField; + this.nested = nested; + } + + public A getStringField() { + return stringField; + } + + public void setStringField(final A stringField) { + this.stringField = stringField; + } + + public GenericTreeModel getNested() { + return nested; + } + + public void setNested(final GenericTreeModel nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MultipleLevelGenericModel that = (MultipleLevelGenericModel) o; + + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + if (getNested() != null ? !getNested().equals(that.getNested()) : that.getNested() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getStringField() != null ? getStringField().hashCode() : 0; + result = 31 * result + (getNested() != null ? getNested().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "MultipleLevelGenericModel{" + + "stringField=" + stringField + + ", nested=" + nested + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedFieldReusingClassTypeParameter.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedFieldReusingClassTypeParameter.java new file mode 100644 index 00000000000..98313294865 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedFieldReusingClassTypeParameter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class NestedFieldReusingClassTypeParameter { + public PropertyReusingClassTypeParameter nested; + + public NestedFieldReusingClassTypeParameter() { + } + + public NestedFieldReusingClassTypeParameter(final PropertyReusingClassTypeParameter nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + NestedFieldReusingClassTypeParameter that = (NestedFieldReusingClassTypeParameter) o; + + if (nested != null ? !nested.equals(that.nested) : that.nested != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return nested != null ? nested.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderFieldWithMultipleTypeParamsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderFieldWithMultipleTypeParamsModel.java new file mode 100644 index 00000000000..fd5aefcd0b1 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderFieldWithMultipleTypeParamsModel.java @@ -0,0 +1,71 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class NestedGenericHolderFieldWithMultipleTypeParamsModel { + + @BsonProperty(useDiscriminator = false) + private GenericHolderModel> nested; + + public NestedGenericHolderFieldWithMultipleTypeParamsModel() { + } + + public NestedGenericHolderFieldWithMultipleTypeParamsModel( + final GenericHolderModel> nested) { + this.nested = nested; + } + + public GenericHolderModel> getNested() { + return nested; + } + + public void setNested(final GenericHolderModel> nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + NestedGenericHolderFieldWithMultipleTypeParamsModel that = (NestedGenericHolderFieldWithMultipleTypeParamsModel) o; + + if (getNested() != null ? !getNested().equals(that.getNested()) : that.getNested() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getNested() != null ? getNested().hashCode() : 0; + } + + @Override + public String toString() { + return "NestedGenericHolderFieldWithMultipleTypeParamsModel{" + + "nested=" + nested + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderMapModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderMapModel.java new file mode 100644 index 00000000000..50c8cf30ec6 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderMapModel.java @@ -0,0 +1,70 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.Map; + +public final class NestedGenericHolderMapModel { + + private GenericHolderModel> nested; + + public NestedGenericHolderMapModel() { + } + + public NestedGenericHolderMapModel(final GenericHolderModel> nested) { + this.nested = nested; + } + + public GenericHolderModel> getNested() { + return nested; + } + + public void setNested(final GenericHolderModel> nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NestedGenericHolderMapModel)) { + return false; + } + + NestedGenericHolderMapModel that = (NestedGenericHolderMapModel) o; + + if (getNested() != null ? !getNested().equals(that.getNested()) : that.getNested() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getNested() != null ? getNested().hashCode() : 0; + return result; + } + + @Override + public String toString() { + return "NestedGenericHolderMapModel{" + + "nested=" + nested + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderModel.java new file mode 100644 index 00000000000..0979a25389c --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderModel.java @@ -0,0 +1,59 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class NestedGenericHolderModel { + + private GenericHolderModel nested; + + public NestedGenericHolderModel() { + } + + public NestedGenericHolderModel(final GenericHolderModel nested) { + this.nested = nested; + } + + public GenericHolderModel getNested() { + return nested; + } + + public void setNested(final GenericHolderModel nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NestedGenericHolderModel)) { + return false; + } + + NestedGenericHolderModel that = (NestedGenericHolderModel) o; + + if (getNested() != null ? !getNested().equals(that.getNested()) : that.getNested() != null) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return getNested() != null ? getNested().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderSimpleGenericsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderSimpleGenericsModel.java new file mode 100644 index 00000000000..46285ed0411 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericHolderSimpleGenericsModel.java @@ -0,0 +1,70 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; +import java.util.Map; + +public final class NestedGenericHolderSimpleGenericsModel { + private GenericHolderModel, Map>> nested; + + public NestedGenericHolderSimpleGenericsModel() { + } + + public NestedGenericHolderSimpleGenericsModel( + final GenericHolderModel, Map>> nested) { + this.nested = nested; + } + + public GenericHolderModel, Map>> getNested() { + return nested; + } + + public void setNested(final GenericHolderModel, Map>> nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NestedGenericHolderSimpleGenericsModel)) { + return false; + } + + NestedGenericHolderSimpleGenericsModel that = (NestedGenericHolderSimpleGenericsModel) o; + + if (nested != null ? !nested.equals(that.nested) : that.nested != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return nested != null ? nested.hashCode() : 0; + } + + @Override + public String toString() { + return "NestedGenericHolderSimpleGenericsModel{" + + "nested=" + nested + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericTreeModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericTreeModel.java new file mode 100644 index 00000000000..17b384795ed --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedGenericTreeModel.java @@ -0,0 +1,82 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class NestedGenericTreeModel { + private Integer intField; + private GenericTreeModel nested; + + public NestedGenericTreeModel() { + } + + public NestedGenericTreeModel(final Integer intField, final GenericTreeModel nested) { + this.intField = intField; + this.nested = nested; + } + + public Integer getIntField() { + return intField; + } + + public void setIntField(final Integer intField) { + this.intField = intField; + } + + public GenericTreeModel getNested() { + return nested; + } + + public void setNested(final GenericTreeModel nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + NestedGenericTreeModel that = (NestedGenericTreeModel) o; + + if (getIntField() != null ? !getIntField().equals(that.getIntField()) : that.getIntField() != null) { + return false; + } + if (getNested() != null ? !getNested().equals(that.getNested()) : that.getNested() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntField() != null ? getIntField().hashCode() : 0; + result = 31 * result + (getNested() != null ? getNested().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "NestedGenericTreeModel{" + + "intField=" + intField + + ", nested=" + nested + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedMultipleLevelGenericModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedMultipleLevelGenericModel.java new file mode 100644 index 00000000000..ca8af3a3113 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedMultipleLevelGenericModel.java @@ -0,0 +1,82 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class NestedMultipleLevelGenericModel { + private Integer intField; + private MultipleLevelGenericModel nested; + + public NestedMultipleLevelGenericModel() { + } + + public NestedMultipleLevelGenericModel(final Integer intField, final MultipleLevelGenericModel nested) { + this.intField = intField; + this.nested = nested; + } + + public Integer getIntField() { + return intField; + } + + public void setIntField(final Integer intField) { + this.intField = intField; + } + + public MultipleLevelGenericModel getNested() { + return nested; + } + + public void setNested(final MultipleLevelGenericModel nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + NestedMultipleLevelGenericModel that = (NestedMultipleLevelGenericModel) o; + + if (getIntField() != null ? !getIntField().equals(that.getIntField()) : that.getIntField() != null) { + return false; + } + if (getNested() != null ? !getNested().equals(that.getNested()) : that.getNested() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntField() != null ? getIntField().hashCode() : 0; + result = 31 * result + (getNested() != null ? getNested().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "NestedMultipleLevelGenericModel{" + + "intField=" + intField + + ", nested=" + nested + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedReusedGenericsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedReusedGenericsModel.java new file mode 100644 index 00000000000..f3756f5b4bc --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedReusedGenericsModel.java @@ -0,0 +1,74 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; + +public final class NestedReusedGenericsModel { + + private ReusedGenericsModel, String> nested; + + public NestedReusedGenericsModel() { + } + + public NestedReusedGenericsModel(final ReusedGenericsModel, String> nested) { + this.nested = nested; + } + + /** + * Returns the nested + * + * @return the nested + */ + public ReusedGenericsModel, String> getNested() { + return nested; + } + + public void setNested(final ReusedGenericsModel, String> nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NestedReusedGenericsModel)) { + return false; + } + + NestedReusedGenericsModel that = (NestedReusedGenericsModel) o; + + if (getNested() != null ? !getNested().equals(that.getNested()) : that.getNested() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getNested() != null ? getNested().hashCode() : 0; + } + + @Override + public String toString() { + return "NestedReusedGenericsModel{" + + "nested=" + nested + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSelfReferentialGenericHolderModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSelfReferentialGenericHolderModel.java new file mode 100644 index 00000000000..4374ba92077 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSelfReferentialGenericHolderModel.java @@ -0,0 +1,66 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class NestedSelfReferentialGenericHolderModel { + private NestedSelfReferentialGenericModel nested; + + public NestedSelfReferentialGenericHolderModel() { + } + + public NestedSelfReferentialGenericHolderModel(final NestedSelfReferentialGenericModel nested) { + this.nested = nested; + } + + public NestedSelfReferentialGenericModel getNested() { + return nested; + } + + public void setNested(final NestedSelfReferentialGenericModel nested) { + this.nested = nested; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + NestedSelfReferentialGenericHolderModel that = (NestedSelfReferentialGenericHolderModel) o; + + if (nested != null ? !nested.equals(that.nested) : that.nested != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return nested != null ? nested.hashCode() : 0; + } + + @Override + public String toString() { + return "NestedSelfReferentialGenericHolderModel{" + + "nested=" + nested + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSelfReferentialGenericModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSelfReferentialGenericModel.java new file mode 100644 index 00000000000..37f201bd088 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSelfReferentialGenericModel.java @@ -0,0 +1,128 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class NestedSelfReferentialGenericModel { + private T t; + private V v; + private Z z; + private SelfReferentialGenericModel selfRef1; + private SelfReferentialGenericModel selfRef2; + + public NestedSelfReferentialGenericModel() { + } + + public NestedSelfReferentialGenericModel(final T t, final V v, final Z z, final SelfReferentialGenericModel selfRef1, + final SelfReferentialGenericModel selfRef2) { + this.t = t; + this.v = v; + this.z = z; + this.selfRef1 = selfRef1; + this.selfRef2 = selfRef2; + } + + public T getT() { + return t; + } + + public void setT(final T t) { + this.t = t; + } + + public V getV() { + return v; + } + + public void setV(final V v) { + this.v = v; + } + + public Z getZ() { + return z; + } + + public void setZ(final Z z) { + this.z = z; + } + + public SelfReferentialGenericModel getSelfRef1() { + return selfRef1; + } + + public void setSelfRef1(final SelfReferentialGenericModel selfRef1) { + this.selfRef1 = selfRef1; + } + + public SelfReferentialGenericModel getSelfRef2() { + return selfRef2; + } + + public void setSelfRef2(final SelfReferentialGenericModel selfRef2) { + this.selfRef2 = selfRef2; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + NestedSelfReferentialGenericModel that = (NestedSelfReferentialGenericModel) o; + + if (t != null ? !t.equals(that.t) : that.t != null) { + return false; + } + if (v != null ? !v.equals(that.v) : that.v != null) { + return false; + } + if (z != null ? !z.equals(that.z) : that.z != null) { + return false; + } + if (selfRef1 != null ? !selfRef1.equals(that.selfRef1) : that.selfRef1 != null) { + return false; + } + if (selfRef2 != null ? !selfRef2.equals(that.selfRef2) : that.selfRef2 != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = t != null ? t.hashCode() : 0; + result = 31 * result + (v != null ? v.hashCode() : 0); + result = 31 * result + (z != null ? z.hashCode() : 0); + result = 31 * result + (selfRef1 != null ? selfRef1.hashCode() : 0); + result = 31 * result + (selfRef2 != null ? selfRef2.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "NestedSelfReferentialGenericModel{" + + "t=" + t + + ", v=" + v + + ", z=" + z + + ", selfRef1=" + selfRef1 + + ", selfRef2=" + selfRef2 + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSimpleIdModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSimpleIdModel.java new file mode 100644 index 00000000000..3bfa82e1ab3 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/NestedSimpleIdModel.java @@ -0,0 +1,82 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public class NestedSimpleIdModel { + private String id; + private SimpleIdModel nestedSimpleIdModel; + + public NestedSimpleIdModel(){ + } + + public NestedSimpleIdModel(final SimpleIdModel nestedSimpleIdModel) { + this(null, nestedSimpleIdModel); + } + + public NestedSimpleIdModel(final String id, final SimpleIdModel nestedSimpleIdModel) { + this.id = id; + this.nestedSimpleIdModel = nestedSimpleIdModel; + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public SimpleIdModel getNestedSimpleIdModel() { + return nestedSimpleIdModel; + } + + public void setNestedSimpleIdModel(final SimpleIdModel nestedSimpleIdModel) { + this.nestedSimpleIdModel = nestedSimpleIdModel; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + NestedSimpleIdModel that = (NestedSimpleIdModel) o; + + if (id != null ? !id.equals(that.id) : that.id != null) { + return false; + } + return nestedSimpleIdModel != null ? nestedSimpleIdModel.equals(that.nestedSimpleIdModel) : that.nestedSimpleIdModel == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (nestedSimpleIdModel != null ? nestedSimpleIdModel.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "NestedSimpleIdModel{" + + "id='" + id + '\'' + + ", nestedSimpleIdModel=" + nestedSimpleIdModel + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/Optional.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/Optional.java new file mode 100644 index 00000000000..4ac1ae0e325 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/Optional.java @@ -0,0 +1,103 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + + +import java.util.NoSuchElementException; + +public abstract class Optional { + + private static final Optional NONE = new Optional() { + @Override + public Object get() { + throw new NoSuchElementException(".get call on None!"); + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + @SuppressWarnings("unchecked") + public static Optional empty() { + return (Optional) NONE; + } + + @SuppressWarnings("unchecked") + public static Optional of(final T it) { + if (it == null) { + return (Optional) Optional.NONE; + } else { + return new Optional.Some(it); + } + } + + public abstract T get(); + + public abstract boolean isEmpty(); + + @Override + public String toString() { + return "None"; + } + + public boolean isDefined() { + return !isEmpty(); + } + + public static class Some extends Optional { + private final T value; + + Some(final T value) { + this.value = value; + } + + @Override + public T get() { + return value; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return String.format("Some(%s)", value); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Some some = (Some) o; + return value.equals(some.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/OptionalPropertyCodecProvider.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/OptionalPropertyCodecProvider.java new file mode 100644 index 00000000000..12fa603ccc0 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/OptionalPropertyCodecProvider.java @@ -0,0 +1,67 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bson.codecs.pojo.entities; + +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.pojo.PropertyCodecProvider; +import org.bson.codecs.pojo.PropertyCodecRegistry; +import org.bson.codecs.pojo.TypeWithTypeParameters; + +public class OptionalPropertyCodecProvider implements PropertyCodecProvider { + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public Codec get(final TypeWithTypeParameters type, final PropertyCodecRegistry registry) { + if (Optional.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 1) { + Codec valueCodec = registry.get(type.getTypeParameters().get(0)); + return new OptionalCodec(type.getType(), valueCodec); + } else { + return null; + } + } + + private static final class OptionalCodec implements Codec> { + private final Class> encoderClass; + private final Codec codec; + + private OptionalCodec(final Class> encoderClass, final Codec codec) { + this.encoderClass = encoderClass; + this.codec = codec; + } + + @Override + public void encode(final BsonWriter writer, final Optional optionalValue, final EncoderContext encoderContext) { + if (optionalValue == null || optionalValue.isEmpty()) { + writer.writeNull(); + } else { + codec.encode(writer, optionalValue.get(), encoderContext); + } + } + + @Override + public Optional decode(final BsonReader reader, final DecoderContext context) { + return Optional.of(codec.decode(reader, context)); + } + + @Override + public Class> getEncoderClass() { + return encoderClass; + } + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/PrimitivesModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/PrimitivesModel.java new file mode 100644 index 00000000000..bc8dcc633ea --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/PrimitivesModel.java @@ -0,0 +1,163 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class PrimitivesModel { + + private boolean myBoolean; + private byte myByte; + private char myCharacter; + private double myDouble; + private float myFloat; + private int myInteger; + private long myLong; + private short myShort; + + public PrimitivesModel() { + } + + public PrimitivesModel(final boolean myBoolean, final byte myByte, final char myCharacter, final double myDouble, + final float myFloat, final int myInteger, final long myLong, final short myShort) { + this.myBoolean = myBoolean; + this.myByte = myByte; + this.myCharacter = myCharacter; + this.myDouble = myDouble; + this.myFloat = myFloat; + this.myInteger = myInteger; + this.myLong = myLong; + this.myShort = myShort; + } + + public boolean isMyBoolean() { + return myBoolean; + } + + public void setMyBoolean(final boolean myBoolean) { + this.myBoolean = myBoolean; + } + + public byte getMyByte() { + return myByte; + } + + public void setMyByte(final byte myByte) { + this.myByte = myByte; + } + + public char getMyCharacter() { + return myCharacter; + } + + public void setMyCharacter(final char myCharacter) { + this.myCharacter = myCharacter; + } + + public double getMyDouble() { + return myDouble; + } + + public void setMyDouble(final double myDouble) { + this.myDouble = myDouble; + } + + public float getMyFloat() { + return myFloat; + } + + public void setMyFloat(final float myFloat) { + this.myFloat = myFloat; + } + + public int getMyInteger() { + return myInteger; + } + + public void setMyInteger(final int myInteger) { + this.myInteger = myInteger; + } + + public long getMyLong() { + return myLong; + } + + public void setMyLong(final long myLong) { + this.myLong = myLong; + } + + public short getMyShort() { + return myShort; + } + + public void setMyShort(final short myShort) { + this.myShort = myShort; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PrimitivesModel that = (PrimitivesModel) o; + + if (isMyBoolean() != that.isMyBoolean()) { + return false; + } + if (getMyByte() != that.getMyByte()) { + return false; + } + if (getMyCharacter() != that.getMyCharacter()) { + return false; + } + if (Double.compare(that.getMyDouble(), getMyDouble()) != 0) { + return false; + } + if (Float.compare(that.getMyFloat(), getMyFloat()) != 0) { + return false; + } + if (getMyInteger() != that.getMyInteger()) { + return false; + } + if (getMyLong() != that.getMyLong()) { + return false; + } + if (getMyShort() != that.getMyShort()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result; + long temp; + result = (isMyBoolean() ? 1 : 0); + result = 31 * result + (int) getMyByte(); + result = 31 * result + (int) getMyCharacter(); + temp = Double.doubleToLongBits(getMyDouble()); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + (getMyFloat() != +0.0f ? Float.floatToIntBits(getMyFloat()) : 0); + result = 31 * result + getMyInteger(); + result = 31 * result + (int) (getMyLong() ^ (getMyLong() >>> 32)); + result = 31 * result + (int) getMyShort(); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/PrivateSetterFieldModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/PrivateSetterFieldModel.java new file mode 100644 index 00000000000..8580aec4dec --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/PrivateSetterFieldModel.java @@ -0,0 +1,79 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; + +public final class PrivateSetterFieldModel { + + private Integer integerField; + private String stringField; + private List listField; + + public PrivateSetterFieldModel(){ + } + + public PrivateSetterFieldModel(final Integer integerField, final String stringField, final List listField) { + this.integerField = integerField; + this.stringField = stringField; + this.listField = listField; + } + + public String getSomeMethod() { + return "some method"; + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public List getListField() { + return listField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PrivateSetterFieldModel that = (PrivateSetterFieldModel) o; + + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + return getListField() != null ? getListField().equals(that.getListField()) : that.getListField() == null; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (getListField() != null ? getListField().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertyReusingClassTypeParameter.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertyReusingClassTypeParameter.java new file mode 100644 index 00000000000..219c1e3ed14 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertyReusingClassTypeParameter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class PropertyReusingClassTypeParameter { + + public GenericTreeModel tree; + + public PropertyReusingClassTypeParameter(){ + } + + public PropertyReusingClassTypeParameter(final GenericTreeModel tree) { + this.tree = tree; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PropertyReusingClassTypeParameter that = (PropertyReusingClassTypeParameter) o; + + if (tree != null ? !tree.equals(that.tree) : that.tree != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return tree != null ? tree.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertySelectionModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertySelectionModel.java new file mode 100644 index 00000000000..3b935ccb2de --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertySelectionModel.java @@ -0,0 +1,150 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonIgnore; + +public final class PropertySelectionModel { + private String stringField = "stringField"; + + private final String finalStringField = "finalStringField"; + + @BsonIgnore + private String ignoredStringField = "ignoreMe"; + + private String anotherIgnoredStringField = "ignoreMe"; + + private static final String staticFinalStringField = "staticFinalStringField"; + + private static String staticStringField = "staticStringField"; + + private transient String transientString = "transientString"; + + public PropertySelectionModel() { + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + public String getFinalStringField() { + return finalStringField; + } + + public void setFinalStringField(final String finalStringField) { + throw new IllegalStateException("Not allowed"); + } + + public static String getStaticFinalStringField() { + return staticFinalStringField; + } + + public void setStaticFinalStringField(final String staticFinalStringField) { + throw new IllegalStateException("Not allowed"); + } + + public static String getStaticStringField() { + return staticStringField; + } + + public static void setStaticStringField(final String staticStringField) { + throw new IllegalStateException("Not allowed"); + } + + public String getTransientString() { + return transientString; + } + + public void setTransientString(final String transientString) { + throw new IllegalStateException("Not allowed"); + } + + public String getIgnoredStringField() { + return ignoredStringField; + } + + public void setIgnoredStringField(final String ignoredStringField) { + this.ignoredStringField = ignoredStringField; + } + + @BsonIgnore + public String getAnotherIgnoredStringField() { + return anotherIgnoredStringField; + } + + @BsonIgnore + public void setAnotherIgnoredStringField(final String anotherIgnoredStringField) { + this.anotherIgnoredStringField = anotherIgnoredStringField; + } + + public int getfoo() { + return 42; + } + + public void setfoo(final int foo) { + } + + public void is() { + } + + public void isfoo() { + } + + public int get() { + return 42; + } + + public void set(final int foo) { + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PropertySelectionModel that = (PropertySelectionModel) o; + + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + if (getFinalStringField() != null ? !getFinalStringField().equals(that.getFinalStringField()) + : that.getFinalStringField() != null) { + return false; + } + if (getTransientString() != null ? !getTransientString().equals(that.getTransientString()) : that.getTransientString() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getStringField() != null ? getStringField().hashCode() : 0; + result = 31 * result + (getFinalStringField() != null ? getFinalStringField().hashCode() : 0); + result = 31 * result + (getTransientString() != null ? getTransientString().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertyWithMultipleTypeParamsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertyWithMultipleTypeParamsModel.java new file mode 100644 index 00000000000..c0dbe349b34 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/PropertyWithMultipleTypeParamsModel.java @@ -0,0 +1,73 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +@BsonDiscriminator("PropertyWithMultipleTypeParamsModel") +public final class PropertyWithMultipleTypeParamsModel { + + @BsonProperty(useDiscriminator = true) + private SimpleGenericsModel simpleGenericsModel; + + public PropertyWithMultipleTypeParamsModel() { + } + + public PropertyWithMultipleTypeParamsModel(final SimpleGenericsModel simpleGenericsModel) { + this.simpleGenericsModel = simpleGenericsModel; + } + + public SimpleGenericsModel getSimpleGenericsModel() { + return simpleGenericsModel; + } + + public void setSimpleGenericsModel(final SimpleGenericsModel simpleGenericsModel) { + this.simpleGenericsModel = simpleGenericsModel; + } + + @Override + public String toString() { + return "PropertyWithMultipleTypeParamsModel{" + + "nested=" + simpleGenericsModel + + "}"; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PropertyWithMultipleTypeParamsModel)) { + return false; + } + + PropertyWithMultipleTypeParamsModel that = (PropertyWithMultipleTypeParamsModel) o; + + if (getSimpleGenericsModel() != null ? !getSimpleGenericsModel().equals(that.getSimpleGenericsModel()) + : that.getSimpleGenericsModel() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getSimpleGenericsModel() != null ? getSimpleGenericsModel().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ReusedGenericsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ReusedGenericsModel.java new file mode 100644 index 00000000000..66d31ab4721 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ReusedGenericsModel.java @@ -0,0 +1,214 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class ReusedGenericsModel { + + private A field1; + private B field2; + private C field3; + private Integer field4; + private C field5; + private B field6; + private A field7; + private String field8; + + public ReusedGenericsModel() { + } + + public ReusedGenericsModel(final A field1, final B field2, final C field3, final Integer field4, final C field5, final B field6, + final A field7, final String field8) { + this.field1 = field1; + this.field2 = field2; + this.field3 = field3; + this.field4 = field4; + this.field5 = field5; + this.field6 = field6; + this.field7 = field7; + this.field8 = field8; + } + + /** + * Returns the field1 + * + * @return the field1 + */ + public A getField1() { + return field1; + } + + public void setField1(final A field1) { + this.field1 = field1; + } + + /** + * Returns the field2 + * + * @return the field2 + */ + public B getField2() { + return field2; + } + + public void setField2(final B field2) { + this.field2 = field2; + } + + /** + * Returns the field3 + * + * @return the field3 + */ + public C getField3() { + return field3; + } + + public void setField3(final C field3) { + this.field3 = field3; + } + + /** + * Returns the field4 + * + * @return the field4 + */ + public Integer getField4() { + return field4; + } + + public void setField4(final Integer field4) { + this.field4 = field4; + } + + /** + * Returns the field5 + * + * @return the field5 + */ + public C getField5() { + return field5; + } + + public void setField5(final C field5) { + this.field5 = field5; + } + + /** + * Returns the field6 + * + * @return the field6 + */ + public B getField6() { + return field6; + } + + public void setField6(final B field6) { + this.field6 = field6; + } + + /** + * Returns the field7 + * + * @return the field7 + */ + public A getField7() { + return field7; + } + + public void setField7(final A field7) { + this.field7 = field7; + } + + /** + * Returns the field8 + * + * @return the field8 + */ + public String getField8() { + return field8; + } + + public void setField8(final String field8) { + this.field8 = field8; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ReusedGenericsModel)) { + return false; + } + + ReusedGenericsModel that = (ReusedGenericsModel) o; + + if (getField1() != null ? !getField1().equals(that.getField1()) : that.getField1() != null) { + return false; + } + if (getField2() != null ? !getField2().equals(that.getField2()) : that.getField2() != null) { + return false; + } + if (getField3() != null ? !getField3().equals(that.getField3()) : that.getField3() != null) { + return false; + } + if (getField4() != null ? !getField4().equals(that.getField4()) : that.getField4() != null) { + return false; + } + if (getField5() != null ? !getField5().equals(that.getField5()) : that.getField5() != null) { + return false; + } + if (getField6() != null ? !getField6().equals(that.getField6()) : that.getField6() != null) { + return false; + } + if (getField7() != null ? !getField7().equals(that.getField7()) : that.getField7() != null) { + return false; + } + if (getField8() != null ? !getField8().equals(that.getField8()) : that.getField8() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getField1() != null ? getField1().hashCode() : 0; + result = 31 * result + (getField2() != null ? getField2().hashCode() : 0); + result = 31 * result + (getField3() != null ? getField3().hashCode() : 0); + result = 31 * result + (getField4() != null ? getField4().hashCode() : 0); + result = 31 * result + (getField5() != null ? getField5().hashCode() : 0); + result = 31 * result + (getField6() != null ? getField6().hashCode() : 0); + result = 31 * result + (getField7() != null ? getField7().hashCode() : 0); + result = 31 * result + (getField8() != null ? getField8().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ReusedGenericsModel{" + + "field1=" + field1 + + ", field2=" + field2 + + ", field3=" + field3 + + ", field4=" + field4 + + ", field5=" + field5 + + ", field6=" + field6 + + ", field7=" + field7 + + ", field8='" + field8 + "'" + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/SelfReferentialGenericModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/SelfReferentialGenericModel.java new file mode 100644 index 00000000000..c6bb12f9b9d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/SelfReferentialGenericModel.java @@ -0,0 +1,97 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class SelfReferentialGenericModel { + private T t; + private V v; + private SelfReferentialGenericModel child; + + public SelfReferentialGenericModel() { + } + + public SelfReferentialGenericModel(final T t, final V v, final SelfReferentialGenericModel child) { + this.t = t; + this.v = v; + this.child = child; + } + + public T getT() { + return t; + } + + public void setT(final T t) { + this.t = t; + } + + public V getV() { + return v; + } + + public void setV(final V v) { + this.v = v; + } + + public SelfReferentialGenericModel getChild() { + return child; + } + + public void setChild(final SelfReferentialGenericModel child) { + this.child = child; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SelfReferentialGenericModel that = (SelfReferentialGenericModel) o; + + if (t != null ? !t.equals(that.t) : that.t != null) { + return false; + } + if (v != null ? !v.equals(that.v) : that.v != null) { + return false; + } + if (child != null ? !child.equals(that.child) : that.child != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = t != null ? t.hashCode() : 0; + result = 31 * result + (v != null ? v.hashCode() : 0); + result = 31 * result + (child != null ? child.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SelfReferentialGenericModel{" + + "t=" + t + + ", v=" + v + + ", child=" + child + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeHolderCircleModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeHolderCircleModel.java new file mode 100644 index 00000000000..ca2dd40afa6 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeHolderCircleModel.java @@ -0,0 +1,33 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public class ShapeHolderCircleModel extends ShapeHolderModel { + + public ShapeHolderCircleModel() { + } + + public ShapeHolderCircleModel(final ShapeModelCircle shape) { + super(shape); + } + + @Override + public ShapeModelCircle getShape() { + return (ShapeModelCircle) super.getShape(); + } +} + diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeHolderModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeHolderModel.java new file mode 100644 index 00000000000..2ccd6c4f477 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeHolderModel.java @@ -0,0 +1,61 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public class ShapeHolderModel { + + private ShapeModelAbstract shape; + + public ShapeHolderModel() { + } + + public ShapeHolderModel(final ShapeModelAbstract shape) { + this.shape = shape; + } + + public ShapeModelAbstract getShape() { + return shape; + } + + public void setShape(final ShapeModelAbstract shape) { + this.shape = shape; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ShapeHolderModel)) { + return false; + } + + ShapeHolderModel that = (ShapeHolderModel) o; + + if (getShape() != null ? !getShape().equals(that.getShape()) : that.getShape() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getShape() != null ? getShape().hashCode() : 0; + } +} + diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelAbstract.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelAbstract.java new file mode 100644 index 00000000000..9b0524fe26c --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelAbstract.java @@ -0,0 +1,64 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; + +@BsonDiscriminator() +public abstract class ShapeModelAbstract { + + private String color; + + public ShapeModelAbstract() { + } + + public ShapeModelAbstract(final String color) { + this.color = color; + } + + public String getColor() { + return color; + } + + public void setColor(final String color) { + this.color = color; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ShapeModelAbstract)) { + return false; + } + + ShapeModelAbstract that = (ShapeModelAbstract) o; + + if (getColor() != null ? !getColor().equals(that.getColor()) : that.getColor() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getColor() != null ? getColor().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelCircle.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelCircle.java new file mode 100644 index 00000000000..d2d544e3f84 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelCircle.java @@ -0,0 +1,67 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class ShapeModelCircle extends ShapeModelAbstract { + + private Double radius; + + public ShapeModelCircle() { + } + + public ShapeModelCircle(final String color, final Double radius) { + super(color); + this.radius = radius; + } + + public Double getRadius() { + return radius; + } + + public void setRadius(final Double radius) { + this.radius = radius; + } + + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ShapeModelCircle)) { + return false; + } + if (!super.equals(o)) { + return false; + } + + ShapeModelCircle that = (ShapeModelCircle) o; + + if (getRadius() != null ? !getRadius().equals(that.getRadius()) : that.getRadius() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (getRadius() != null ? getRadius().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelRectangle.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelRectangle.java new file mode 100644 index 00000000000..d644cea85b8 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/ShapeModelRectangle.java @@ -0,0 +1,80 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class ShapeModelRectangle extends ShapeModelAbstract { + + private Double width; + private Double height; + + public ShapeModelRectangle() { + } + + public ShapeModelRectangle(final String color, final Double width, final Double height) { + super(color); + this.width = width; + this.height = height; + } + + public Double getWidth() { + return width; + } + + public void setWidth(final Double width) { + this.width = width; + } + + public Double getHeight() { + return height; + } + + public void setHeight(final Double height) { + this.height = height; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ShapeModelRectangle)) { + return false; + } + if (!super.equals(o)) { + return false; + } + + ShapeModelRectangle that = (ShapeModelRectangle) o; + + if (getWidth() != null ? !getWidth().equals(that.getWidth()) : that.getWidth() != null) { + return false; + } + if (getHeight() != null ? !getHeight().equals(that.getHeight()) : that.getHeight() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (getWidth() != null ? getWidth().hashCode() : 0); + result = 31 * result + (getHeight() != null ? getHeight().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleEnum.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleEnum.java new file mode 100644 index 00000000000..a7830c1991a --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleEnum.java @@ -0,0 +1,23 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public enum SimpleEnum { + ALPHA, + BRAVO, + CHARLIE +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleEnumModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleEnumModel.java new file mode 100644 index 00000000000..394f2a72b24 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleEnumModel.java @@ -0,0 +1,72 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class SimpleEnumModel { + + private SimpleEnum myEnum; + + public SimpleEnumModel() { + } + + public SimpleEnumModel(final SimpleEnum myEnum) { + this.myEnum = myEnum; + } + + /** + * Returns the myEnum + * + * @return the myEnum + */ + public SimpleEnum getMyEnum() { + return myEnum; + } + + /** + * Sets the myEnum + * + * @param myEnum the myEnum + * @return this + */ + public SimpleEnumModel setMyEnum(final SimpleEnum myEnum) { + this.myEnum = myEnum; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SimpleEnumModel that = (SimpleEnumModel) o; + + if (getMyEnum() != that.getMyEnum()) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getMyEnum() != null ? getMyEnum().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleGenericsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleGenericsModel.java new file mode 100644 index 00000000000..4753953240e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleGenericsModel.java @@ -0,0 +1,116 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import java.util.List; +import java.util.Map; + +public final class SimpleGenericsModel { + private Integer myIntegerField; + private T myGenericField; + private List myListField; + private Map myMapField; + + public SimpleGenericsModel() { + } + + public SimpleGenericsModel(final Integer myIntegerField, final T myGenericField, final List myListField, final Map + myMapField) { + this.myIntegerField = myIntegerField; + this.myGenericField = myGenericField; + this.myListField = myListField; + this.myMapField = myMapField; + } + + public Integer getMyIntegerField() { + return myIntegerField; + } + + public void setMyIntegerField(final Integer myIntegerField) { + this.myIntegerField = myIntegerField; + } + + public T getMyGenericField() { + return myGenericField; + } + + public void setMyGenericField(final T myGenericField) { + this.myGenericField = myGenericField; + } + + public List getMyListField() { + return myListField; + } + + public void setMyListField(final List myListField) { + this.myListField = myListField; + } + + public Map getMyMapField() { + return myMapField; + } + + public void setMyMapField(final Map myMapField) { + this.myMapField = myMapField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SimpleGenericsModel that = (SimpleGenericsModel) o; + + if (getMyIntegerField() != null ? !getMyIntegerField().equals(that.getMyIntegerField()) : that.getMyIntegerField() != null) { + return false; + } + if (getMyGenericField() != null ? !getMyGenericField().equals(that.getMyGenericField()) : that.getMyGenericField() != null) { + return false; + } + if (getMyListField() != null ? !getMyListField().equals(that.getMyListField()) : that.getMyListField() != null) { + return false; + } + if (getMyMapField() != null ? !getMyMapField().equals(that.getMyMapField()) : that.getMyMapField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getMyIntegerField() != null ? getMyIntegerField().hashCode() : 0; + result = 31 * result + (getMyGenericField() != null ? getMyGenericField().hashCode() : 0); + result = 31 * result + (getMyListField() != null ? getMyListField().hashCode() : 0); + result = 31 * result + (getMyMapField() != null ? getMyMapField().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SimpleGenericsModel{" + + "myIntegerField=" + myIntegerField + + ", myGenericField=" + myGenericField + + ", myListField=" + myListField + + ", myMapField=" + myMapField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleIdImmutableModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleIdImmutableModel.java new file mode 100644 index 00000000000..fe604680e09 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleIdImmutableModel.java @@ -0,0 +1,89 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; +import org.bson.types.ObjectId; + +public class SimpleIdImmutableModel { + private final ObjectId id; + private final Integer integerField; + private final String stringField; + + public SimpleIdImmutableModel(final Integer integerField, final String stringField){ + this(null, integerField, stringField); + } + + @BsonCreator + public SimpleIdImmutableModel(@BsonProperty("id") final ObjectId id, + @BsonProperty("integerField") final Integer integerField, + @BsonProperty("stringField") final String stringField) { + this.id = id; + this.integerField = integerField; + this.stringField = stringField; + } + + public ObjectId getId() { + return id; + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SimpleIdImmutableModel that = (SimpleIdImmutableModel) o; + + if (id != null ? !id.equals(that.id) : that.id != null) { + return false; + } + if (integerField != null ? !integerField.equals(that.integerField) : that.integerField != null) { + return false; + } + return stringField != null ? stringField.equals(that.stringField) : that.stringField == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (integerField != null ? integerField.hashCode() : 0); + result = 31 * result + (stringField != null ? stringField.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SimpleIdImmutableModel{" + + "id=" + id + + ", integerField=" + integerField + + ", stringField='" + stringField + '\'' + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleIdModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleIdModel.java new file mode 100644 index 00000000000..801bb6fbe33 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleIdModel.java @@ -0,0 +1,99 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.types.ObjectId; + +public class SimpleIdModel { + private ObjectId id; + private Integer integerField; + private String stringField; + + public SimpleIdModel(){ + } + + public SimpleIdModel(final Integer integerField, final String stringField) { + this(null, integerField, stringField); + } + + public SimpleIdModel(final ObjectId objectId, final Integer integerField, final String stringField) { + this.id = objectId; + this.integerField = integerField; + this.stringField = stringField; + } + + public ObjectId getId() { + return id; + } + + public void setId(final ObjectId id) { + this.id = id; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(final Integer integerField) { + this.integerField = integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SimpleIdModel that = (SimpleIdModel) o; + + if (id != null ? !id.equals(that.id) : that.id != null) { + return false; + } + if (integerField != null ? !integerField.equals(that.integerField) : that.integerField != null) { + return false; + } + return stringField != null ? stringField.equals(that.stringField) : that.stringField == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (integerField != null ? integerField.hashCode() : 0); + result = 31 * result + (stringField != null ? stringField.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SimpleIdModel{" + + "id=" + id + + ", integerField=" + integerField + + ", stringField='" + stringField + '\'' + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleModel.java new file mode 100644 index 00000000000..91e43e1e415 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleModel.java @@ -0,0 +1,82 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class SimpleModel { + private Integer integerField; + private String stringField; + + public SimpleModel(){ + } + + public SimpleModel(final Integer integerField, final String stringField) { + this.integerField = integerField; + this.stringField = stringField; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(final Integer integerField) { + this.integerField = integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SimpleModel that = (SimpleModel) o; + + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "SimpleModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleNestedPojoModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleNestedPojoModel.java new file mode 100644 index 00000000000..6682b5c376d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/SimpleNestedPojoModel.java @@ -0,0 +1,59 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class SimpleNestedPojoModel { + private SimpleModel simple; + + public SimpleNestedPojoModel() { + } + + public SimpleNestedPojoModel(final SimpleModel simple) { + this.simple = simple; + } + + public SimpleModel getSimple() { + return simple; + } + + public void setSimple(final SimpleModel simple) { + this.simple = simple; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SimpleNestedPojoModel that = (SimpleNestedPojoModel) o; + + if (getSimple() != null ? !getSimple().equals(that.getSimple()) : that.getSimple() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return getSimple() != null ? getSimple().hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/TreeWithIdModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/TreeWithIdModel.java new file mode 100644 index 00000000000..106ea33a9d8 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/TreeWithIdModel.java @@ -0,0 +1,118 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +import org.bson.types.ObjectId; + +public class TreeWithIdModel { + private ObjectId id; + private String level; + private TreeWithIdModel left; + private TreeWithIdModel right; + + public TreeWithIdModel() { + } + + public TreeWithIdModel(final String level) { + this(null, level, null, null); + } + + public TreeWithIdModel(final String level, final TreeWithIdModel left, final TreeWithIdModel right) { + this(null, level, left, right); + } + + public TreeWithIdModel(final ObjectId id, final String level, final TreeWithIdModel left, final TreeWithIdModel right) { + this.id = id; + this.level = level; + this.left = left; + this.right = right; + } + + public ObjectId getId() { + return id; + } + + public void setId(final ObjectId id) { + this.id = id; + } + + public String getLevel() { + return level; + } + + public void setLevel(final String level) { + this.level = level; + } + + public TreeWithIdModel getLeft() { + return left; + } + + public void setLeft(final TreeWithIdModel left) { + this.left = left; + } + + public TreeWithIdModel getRight() { + return right; + } + + public void setRight(final TreeWithIdModel right) { + this.right = right; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TreeWithIdModel that = (TreeWithIdModel) o; + + if (id != null ? !id.equals(that.id) : that.id != null) { + return false; + } + if (level != null ? !level.equals(that.level) : that.level != null) { + return false; + } + if (left != null ? !left.equals(that.left) : that.left != null) { + return false; + } + return right != null ? right.equals(that.right) : that.right == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (level != null ? level.hashCode() : 0); + result = 31 * result + (left != null ? left.hashCode() : 0); + result = 31 * result + (right != null ? right.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "TreeWithIdModel{" + + "id=" + id + + ", level=" + level + + ", left=" + left + + ", right=" + right + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/UpperBoundsConcreteModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/UpperBoundsConcreteModel.java new file mode 100644 index 00000000000..b0b6964334c --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/UpperBoundsConcreteModel.java @@ -0,0 +1,28 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public final class UpperBoundsConcreteModel extends UpperBoundsModel { + + public UpperBoundsConcreteModel() { + super(); + } + + public UpperBoundsConcreteModel(final Long myGenericField) { + super(myGenericField); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/UpperBoundsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/UpperBoundsModel.java new file mode 100644 index 00000000000..31cfe6acc53 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/UpperBoundsModel.java @@ -0,0 +1,59 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities; + +public class UpperBoundsModel { + private T myGenericField; + + public UpperBoundsModel() { + } + + public UpperBoundsModel(final T myGenericField) { + this.myGenericField = myGenericField; + } + + public T getMyGenericField() { + return myGenericField; + } + + public void setMyGenericField(final T myGenericField) { + this.myGenericField = myGenericField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + UpperBoundsModel that = (UpperBoundsModel) o; + + if (myGenericField != null ? !myGenericField.equals(that.myGenericField) : that.myGenericField != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return myGenericField != null ? myGenericField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationAbstract.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationAbstract.java new file mode 100644 index 00000000000..0257ee71b17 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationAbstract.java @@ -0,0 +1,27 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +@BsonDiscriminator(key = "_key") +public abstract class AnnotationAbstract { + + @BsonProperty + public AnnotationModel child; +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationBsonPropertyIdModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationBsonPropertyIdModel.java new file mode 100644 index 00000000000..906b458026a --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationBsonPropertyIdModel.java @@ -0,0 +1,57 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonProperty; + +public class AnnotationBsonPropertyIdModel { + @BsonProperty("id") + private long id; + + public AnnotationBsonPropertyIdModel() { + } + + public AnnotationBsonPropertyIdModel(final long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AnnotationBsonPropertyIdModel that = (AnnotationBsonPropertyIdModel) o; + return getId() == that.getId(); + } + + @Override + public int hashCode() { + return (int) (getId() ^ (getId() >>> 32)); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationCollision.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationCollision.java new file mode 100644 index 00000000000..8d6a2856e39 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationCollision.java @@ -0,0 +1,44 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class AnnotationCollision { + + public String id; + + @BsonProperty("color") + private String color; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + @BsonProperty("theme") + public String getColor() { + return color; + } + + public void setColor(final String color) { + this.color = color; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationDefaultsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationDefaultsModel.java new file mode 100644 index 00000000000..01277592677 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationDefaultsModel.java @@ -0,0 +1,32 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonProperty; + +@BsonDiscriminator("AnnotationDefaultsModel") +public final class AnnotationDefaultsModel { + + @BsonId + public String customId; + + @BsonProperty + public AnnotationModel child; + +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationInheritedModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationInheritedModel.java new file mode 100644 index 00000000000..4bf23fc5fda --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationInheritedModel.java @@ -0,0 +1,31 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonProperty; + +@BsonDiscriminator(key = "_cls") +public final class AnnotationInheritedModel extends AnnotationAbstract { + + @BsonId + public String customId; + + @BsonProperty(useDiscriminator = true) + public AnnotationModel child; +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationModel.java new file mode 100644 index 00000000000..b45d092d54e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationModel.java @@ -0,0 +1,99 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonProperty; + +@BsonDiscriminator(value = "MyAnnotationModel", key = "_cls") +public final class AnnotationModel { + + @BsonId() + public String customId; + + @BsonProperty(useDiscriminator = false) + public AnnotationModel child; + + @BsonProperty("renamed") + public AnnotationModel alternative; + + public AnnotationModel() { + } + + public AnnotationModel(final String customId, final AnnotationModel child, final AnnotationModel alternative) { + this.customId = customId; + this.child = child; + this.alternative = alternative; + } + + public String getCustomId() { + return customId; + } + + public void setCustomId(final String customId) { + this.customId = customId; + } + + public AnnotationModel getChild() { + return child; + } + + public void setChild(final AnnotationModel child) { + this.child = child; + } + + public AnnotationModel getAlternative() { + return alternative; + } + + public void setAlternative(final AnnotationModel alternative) { + this.alternative = alternative; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AnnotationModel that = (AnnotationModel) o; + + if (getCustomId() != null ? !getCustomId().equals(that.getCustomId()) : that.getCustomId() != null) { + return false; + } + if (getChild() != null ? !getChild().equals(that.getChild()) : that.getChild() != null) { + return false; + } + if (getAlternative() != null ? !getAlternative().equals(that.getAlternative()) : that.getAlternative() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getCustomId() != null ? getCustomId().hashCode() : 0; + result = 31 * result + (getChild() != null ? getChild().hashCode() : 0); + result = 31 * result + (getAlternative() != null ? getAlternative().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationNameCollision.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationNameCollision.java new file mode 100644 index 00000000000..4161eea087d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationNameCollision.java @@ -0,0 +1,27 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class AnnotationNameCollision { + + public String id; + + @BsonProperty("id") + public String alternative; +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationWithObjectIdModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationWithObjectIdModel.java new file mode 100644 index 00000000000..8cddf1e6160 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationWithObjectIdModel.java @@ -0,0 +1,101 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonProperty; +import org.bson.types.ObjectId; + +@BsonDiscriminator(value = "MyAnnotationModel", key = "_cls") +public final class AnnotationWithObjectIdModel { + + @BsonId() + public ObjectId customId; + + @BsonProperty(useDiscriminator = false) + public AnnotationWithObjectIdModel child; + + @BsonProperty("renamed") + public AnnotationWithObjectIdModel alternative; + + public AnnotationWithObjectIdModel() { + } + + public AnnotationWithObjectIdModel(final ObjectId customId, final AnnotationWithObjectIdModel child, + final AnnotationWithObjectIdModel alternative) { + this.customId = customId; + this.child = child; + this.alternative = alternative; + } + + public ObjectId getCustomId() { + return customId; + } + + public void setCustomId(final ObjectId customId) { + this.customId = customId; + } + + public AnnotationWithObjectIdModel getChild() { + return child; + } + + public void setChild(final AnnotationWithObjectIdModel child) { + this.child = child; + } + + public AnnotationWithObjectIdModel getAlternative() { + return alternative; + } + + public void setAlternative(final AnnotationWithObjectIdModel alternative) { + this.alternative = alternative; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + AnnotationWithObjectIdModel that = (AnnotationWithObjectIdModel) o; + + if (getCustomId() != null ? !getCustomId().equals(that.getCustomId()) : that.getCustomId() != null) { + return false; + } + if (getChild() != null ? !getChild().equals(that.getChild()) : that.getChild() != null) { + return false; + } + if (getAlternative() != null ? !getAlternative().equals(that.getAlternative()) : that.getAlternative() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getCustomId() != null ? getCustomId().hashCode() : 0; + result = 31 * result + (getChild() != null ? getChild().hashCode() : 0); + result = 31 * result + (getAlternative() != null ? getAlternative().hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationWriteCollision.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationWriteCollision.java new file mode 100644 index 00000000000..1d826cc5689 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/AnnotationWriteCollision.java @@ -0,0 +1,45 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class AnnotationWriteCollision { + + public String id; + + @BsonProperty("color") + private String color; + + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getColor() { + return color; + } + + @BsonProperty("theme") + public void setColor(final String color) { + this.color = color; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreDuplicatePropertyMultipleTypes.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreDuplicatePropertyMultipleTypes.java new file mode 100644 index 00000000000..6c6aa62d415 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreDuplicatePropertyMultipleTypes.java @@ -0,0 +1,82 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public class BsonIgnoreDuplicatePropertyMultipleTypes { + private final String stringField; + private String altStringField; + + @BsonCreator + public BsonIgnoreDuplicatePropertyMultipleTypes(@BsonProperty("stringField") final String stringField) { + this.stringField = stringField; + } + + public String getStringField() { + return stringField; + } + + @BsonIgnore + public String getAltStringField() { + return altStringField; + } + + @BsonIgnore + public void setAltStringField(final String altStringField) { + this.altStringField = altStringField; + } + + @BsonIgnore + public void setAltStringField(final Integer i) { + this.altStringField = i.toString(); + } + + @Override + public String toString() { + return "BsonIgnoreDuplicatePropertyMultipleTypes{" + + "stringField='" + stringField + '\'' + + ", altStringField='" + altStringField + '\'' + + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BsonIgnoreDuplicatePropertyMultipleTypes that = (BsonIgnoreDuplicatePropertyMultipleTypes) o; + + if (stringField != null ? !stringField.equals(that.stringField) : that.stringField != null) { + return false; + } + return altStringField != null ? altStringField.equals(that.altStringField) : that.altStringField == null; + } + + @Override + public int hashCode() { + int result = stringField != null ? stringField.hashCode() : 0; + result = 31 * result + (altStringField != null ? altStringField.hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreInvalidMapModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreInvalidMapModel.java new file mode 100644 index 00000000000..9a964811b48 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreInvalidMapModel.java @@ -0,0 +1,76 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonIgnore; + +import java.util.Map; + +public class BsonIgnoreInvalidMapModel { + + private String stringField; + + @BsonIgnore + private Map invalidMap; + + public BsonIgnoreInvalidMapModel() { + } + + public BsonIgnoreInvalidMapModel(final String stringField) { + this.stringField = stringField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + public Map getInvalidMap() { + return invalidMap; + } + + public void setInvalidMap(final Map invalidMap) { + this.invalidMap = invalidMap; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BsonIgnoreInvalidMapModel that = (BsonIgnoreInvalidMapModel) o; + + if (stringField != null ? !stringField.equals(that.stringField) : that.stringField != null) { + return false; + } + return invalidMap != null ? invalidMap.equals(that.invalidMap) : that.invalidMap == null; + } + + @Override + public int hashCode() { + int result = stringField != null ? stringField.hashCode() : 0; + result = 31 * result + (invalidMap != null ? invalidMap.hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreSyntheticProperty.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreSyntheticProperty.java new file mode 100644 index 00000000000..cf93764411e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/BsonIgnoreSyntheticProperty.java @@ -0,0 +1,65 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public class BsonIgnoreSyntheticProperty { + private final String stringField; + + @BsonCreator + public BsonIgnoreSyntheticProperty(@BsonProperty("stringField") final String stringField) { + this.stringField = stringField; + } + + public String getStringField() { + return stringField; + } + + @BsonIgnore + public Object getSyntheticProperty() { + return null; + } + + @Override + public String toString() { + return "BsonIgnoreSyntheticProperty{" + + "stringField='" + stringField + '\'' + + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BsonIgnoreSyntheticProperty that = (BsonIgnoreSyntheticProperty) o; + + return stringField != null ? stringField.equals(that.stringField) : that.stringField == null; + } + + @Override + public int hashCode() { + return stringField != null ? stringField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionDiscriminatorAbstractClassesModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionDiscriminatorAbstractClassesModel.java new file mode 100644 index 00000000000..b719145c8f8 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionDiscriminatorAbstractClassesModel.java @@ -0,0 +1,75 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.List; +import java.util.Map; + +public class CollectionDiscriminatorAbstractClassesModel { + private List list; + private Map map; + + public List getList() { + return list; + } + + public CollectionDiscriminatorAbstractClassesModel setList(final List list) { + this.list = list; + return this; + } + + public Map getMap() { + return map; + } + + public CollectionDiscriminatorAbstractClassesModel setMap(final Map map) { + this.map = map; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CollectionDiscriminatorAbstractClassesModel that = (CollectionDiscriminatorAbstractClassesModel) o; + + if (getList() != null ? !getList().equals(that.getList()) : that.getList() != null) { + return false; + } + return getMap() != null ? getMap().equals(that.getMap()) : that.getMap() == null; + } + + @Override + public int hashCode() { + int result = getList() != null ? getList().hashCode() : 0; + result = 31 * result + (getMap() != null ? getMap().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "CollectionDiscriminatorModel{" + + "list=" + list + + ", map=" + map + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionDiscriminatorInterfacesModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionDiscriminatorInterfacesModel.java new file mode 100644 index 00000000000..b9155e738d1 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionDiscriminatorInterfacesModel.java @@ -0,0 +1,75 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.List; +import java.util.Map; + +public class CollectionDiscriminatorInterfacesModel { + private List list; + private Map map; + + public List getList() { + return list; + } + + public CollectionDiscriminatorInterfacesModel setList(final List list) { + this.list = list; + return this; + } + + public Map getMap() { + return map; + } + + public CollectionDiscriminatorInterfacesModel setMap(final Map map) { + this.map = map; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CollectionDiscriminatorInterfacesModel that = (CollectionDiscriminatorInterfacesModel) o; + + if (getList() != null ? !getList().equals(that.getList()) : that.getList() != null) { + return false; + } + return getMap() != null ? getMap().equals(that.getMap()) : that.getMap() == null; + } + + @Override + public int hashCode() { + int result = getList() != null ? getList().hashCode() : 0; + result = 31 * result + (getMap() != null ? getMap().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "CollectionDiscriminatorModel{" + + "list=" + list + + ", map=" + map + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionNameModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionNameModel.java new file mode 100644 index 00000000000..225982c9ebc --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionNameModel.java @@ -0,0 +1,20 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +public final class CollectionNameModel { +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterImmutableModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterImmutableModel.java new file mode 100644 index 00000000000..2b9ff3cfc7c --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterImmutableModel.java @@ -0,0 +1,56 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.Collections; +import java.util.List; + +public class CollectionsGetterImmutableModel { + + private final List listField; + + public CollectionsGetterImmutableModel() { + this(Collections.emptyList()); + } + + public CollectionsGetterImmutableModel(final List listField) { + this.listField = Collections.unmodifiableList(listField); + } + + public List getListField() { + return listField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + CollectionsGetterImmutableModel that = (CollectionsGetterImmutableModel) o; + + return listField != null ? listField.equals(that.listField) : that.listField == null; + } + + @Override + public int hashCode() { + return listField != null ? listField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterMutableModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterMutableModel.java new file mode 100644 index 00000000000..ed320f74f99 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterMutableModel.java @@ -0,0 +1,55 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.ArrayList; +import java.util.List; + +public class CollectionsGetterMutableModel { + + private final List listField; + + public CollectionsGetterMutableModel() { + this(new ArrayList()); + } + + public CollectionsGetterMutableModel(final List listField) { + this.listField = listField; + } + + public List getListField() { + return listField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CollectionsGetterMutableModel that = (CollectionsGetterMutableModel) o; + return listField != null ? listField.equals(that.listField) : that.listField == null; + } + + @Override + public int hashCode() { + return listField != null ? listField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterNonEmptyModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterNonEmptyModel.java new file mode 100644 index 00000000000..4a8bee9aa93 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterNonEmptyModel.java @@ -0,0 +1,57 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.List; + +import static java.util.Arrays.asList; + +public class CollectionsGetterNonEmptyModel { + + private final List listField; + + public CollectionsGetterNonEmptyModel() { + this(asList(1, 2)); + } + + public CollectionsGetterNonEmptyModel(final List listField) { + this.listField = listField; + } + + public List getListField() { + return listField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + CollectionsGetterNonEmptyModel that = (CollectionsGetterNonEmptyModel) o; + + return listField != null ? listField.equals(that.listField) : that.listField == null; + } + + @Override + public int hashCode() { + return listField != null ? listField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterNullModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterNullModel.java new file mode 100644 index 00000000000..f827c0a37bd --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CollectionsGetterNullModel.java @@ -0,0 +1,54 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.List; + +public class CollectionsGetterNullModel { + + private final List listField; + + public CollectionsGetterNullModel() { + this(null); + } + + public CollectionsGetterNullModel(final List listField) { + this.listField = listField; + } + + public List getListField() { + return listField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + CollectionsGetterNullModel that = (CollectionsGetterNullModel) o; + return listField != null ? listField.equals(that.listField) : that.listField == null; + } + + @Override + public int hashCode() { + return listField != null ? listField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorAllFinalFieldsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorAllFinalFieldsModel.java new file mode 100644 index 00000000000..d7c3215f943 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorAllFinalFieldsModel.java @@ -0,0 +1,83 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonDiscriminator; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonProperty; + +@BsonDiscriminator +public final class CreatorAllFinalFieldsModel { + private final String pid; + private final String fName; + private final String lName; + + @BsonCreator + public CreatorAllFinalFieldsModel(@BsonProperty("personId") final String personId, + @BsonProperty("firstName") final String firstName, + @BsonProperty("lastName") final String lastName) { + this.pid = personId; + this.fName = firstName; + this.lName = lastName; + } + + @BsonId + public String getPersonId() { + return pid; + } + + public String getFirstName() { + return fName; + } + + public String getLastName() { + return lName; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorAllFinalFieldsModel that = (CreatorAllFinalFieldsModel) o; + + if (pid != null ? !pid.equals(that.pid) : that.pid != null) { + return false; + } + if (fName != null ? !fName.equals(that.fName) : that.fName != null) { + return false; + } + if (lName != null ? !lName.equals(that.lName) : that.lName != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = pid != null ? pid.hashCode() : 0; + result = 31 * result + (fName != null ? fName.hashCode() : 0); + result = 31 * result + (lName != null ? lName.hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorIdModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorIdModel.java new file mode 100644 index 00000000000..aeb84821e18 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorIdModel.java @@ -0,0 +1,112 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.util.List; + +public class CreatorConstructorIdModel { + private final String id; + private final List integersField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorConstructorIdModel(final @BsonId String id, @BsonProperty("integersField") final List integerField, + @BsonProperty("longField") final long longField) { + this.id = id; + this.integersField = integerField; + this.longField = longField; + } + + public CreatorConstructorIdModel(final String id, final List integersField, final String stringField, final long longField) { + this.id = id; + this.integersField = integersField; + this.stringField = stringField; + this.longField = longField; + } + + public String getId() { + return id; + } + + public List getIntegersField() { + return integersField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + public long getLongField() { + return longField; + } + + public void setLongField(final long longField) { + this.longField = longField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorConstructorIdModel that = (CreatorConstructorIdModel) o; + + if (getLongField() != that.getLongField()) { + return false; + } + if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) { + return false; + } + if (getIntegersField() != null ? !getIntegersField().equals(that.getIntegersField()) + : that.getIntegersField() != null) { + return false; + } + return getStringField() != null ? getStringField().equals(that.getStringField()) : that.getStringField() == null; + } + + @Override + public int hashCode() { + int result = getId() != null ? getId().hashCode() : 0; + result = 31 * result + (getIntegersField() != null ? getIntegersField().hashCode() : 0); + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (getLongField() ^ (getLongField() >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorConstructorIdModel{" + + "id='" + id + '\'' + + ", integersField=" + integersField + + ", stringField='" + stringField + '\'' + + ", longField=" + longField + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorLegacyBsonPropertyModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorLegacyBsonPropertyModel.java new file mode 100644 index 00000000000..81011970500 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorLegacyBsonPropertyModel.java @@ -0,0 +1,104 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.util.List; + +public final class CreatorConstructorLegacyBsonPropertyModel { + private final List integersField; + private String stringField; + @BsonProperty("longField") + private long myLongField; + + // Here we use the @BsonProperty using the actual field name, that has been set to read and write to "longField" + @BsonCreator + public CreatorConstructorLegacyBsonPropertyModel(@BsonProperty("integersField") final List integerField, + @BsonProperty("myLongField") final long longField) { + this.integersField = integerField; + this.myLongField = longField; + } + + public CreatorConstructorLegacyBsonPropertyModel(final List integersField, final String stringField, final long longField) { + this.integersField = integersField; + this.stringField = stringField; + this.myLongField = longField; + } + + public List getIntegersField() { + return integersField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + public long getMyLongField() { + return myLongField; + } + + public void setMyLongField(final long myLongField) { + this.myLongField = myLongField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorConstructorLegacyBsonPropertyModel that = (CreatorConstructorLegacyBsonPropertyModel) o; + + if (getMyLongField() != that.getMyLongField()) { + return false; + } + if (getIntegersField() != null ? !getIntegersField().equals(that.getIntegersField()) : that.getIntegersField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegersField() != null ? getIntegersField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (getMyLongField() ^ (getMyLongField() >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorConstructorModel{" + + "integersField=" + integersField + + ", stringField='" + stringField + "'" + + ", myLongField=" + myLongField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorModel.java new file mode 100644 index 00000000000..d3a5c028b3b --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorModel.java @@ -0,0 +1,102 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.util.List; + +public final class CreatorConstructorModel { + private final List integersField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorConstructorModel(@BsonProperty("integersField") final List integerField, + @BsonProperty("longField") final long longField) { + this.integersField = integerField; + this.longField = longField; + } + + public CreatorConstructorModel(final List integersField, final String stringField, final long longField) { + this.integersField = integersField; + this.stringField = stringField; + this.longField = longField; + } + + public List getIntegersField() { + return integersField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + public long getLongField() { + return longField; + } + + public void setLongField(final long longField) { + this.longField = longField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorConstructorModel that = (CreatorConstructorModel) o; + + if (getLongField() != that.getLongField()) { + return false; + } + if (getIntegersField() != null ? !getIntegersField().equals(that.getIntegersField()) : that.getIntegersField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegersField() != null ? getIntegersField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (getLongField() ^ (getLongField() >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorConstructorModel{" + + "integersField=" + integersField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorPrimitivesModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorPrimitivesModel.java new file mode 100644 index 00000000000..ded3c18516e --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorPrimitivesModel.java @@ -0,0 +1,104 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorConstructorPrimitivesModel { + private final int intField; + private final String stringField; + private final long longField; + + + @BsonCreator + public CreatorConstructorPrimitivesModel(@BsonProperty("intField") final int intField, + @BsonProperty("stringField") final String stringField, + @BsonProperty("longField") final long longField) { + this.intField = intField; + this.stringField = stringField; + this.longField = longField; + } + + /** + * Returns the intField + * + * @return the intField + */ + public int getIntField() { + return intField; + } + + /** + * Returns the stringField + * + * @return the stringField + */ + public String getStringField() { + return stringField; + } + + /** + * Returns the longField + * + * @return the longField + */ + public long getLongField() { + return longField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorConstructorPrimitivesModel that = (CreatorConstructorPrimitivesModel) o; + + if (getIntField() != that.getIntField()) { + return false; + } + if (getLongField() != that.getLongField()) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntField(); + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (getLongField() ^ (getLongField() >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorConstructorPrimativesModel{" + + "intField=" + intField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorRenameModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorRenameModel.java new file mode 100644 index 00000000000..ee94086fe98 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorRenameModel.java @@ -0,0 +1,103 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import java.util.List; + +public class CreatorConstructorRenameModel { + private final List integersField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorConstructorRenameModel(@BsonProperty("integerList") final List integerField, + @BsonProperty("longField") final long longField) { + this.integersField = integerField; + this.longField = longField; + } + + public CreatorConstructorRenameModel(final List integersField, final String stringField, final long longField) { + this.integersField = integersField; + this.stringField = stringField; + this.longField = longField; + } + + @BsonProperty("integerList") + public List getIntegersField() { + return integersField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + public long getLongField() { + return longField; + } + + public void setLongField(final long longField) { + this.longField = longField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorConstructorRenameModel that = (CreatorConstructorRenameModel) o; + + if (getLongField() != that.getLongField()) { + return false; + } + if (getIntegersField() != null ? !getIntegersField().equals(that.getIntegersField()) : that.getIntegersField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegersField() != null ? getIntegersField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (getLongField() ^ (getLongField() >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorConstructorRenameModel{" + + "integersField=" + integersField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorThrowsExceptionModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorThrowsExceptionModel.java new file mode 100644 index 00000000000..10c2721395d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorConstructorThrowsExceptionModel.java @@ -0,0 +1,87 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; + +public final class CreatorConstructorThrowsExceptionModel { + private Integer integerField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorConstructorThrowsExceptionModel(){ + throw new UnsupportedOperationException("Nope"); + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(final Integer integerField) { + this.integerField = integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorConstructorThrowsExceptionModel that = (CreatorConstructorThrowsExceptionModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorNoArgsConstructorModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInSuperClassModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInSuperClassModel.java new file mode 100644 index 00000000000..2064663ff88 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInSuperClassModel.java @@ -0,0 +1,30 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public abstract class CreatorInSuperClassModel { + @BsonCreator + public static CreatorInSuperClassModel newInstance(@BsonProperty("propertyA") final String propertyA, + @BsonProperty("propertyB") final String propertyB) { + return new CreatorInSuperClassModelImpl(propertyA, propertyB); + } + public abstract String getPropertyA(); + public abstract String getPropertyB(); +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInSuperClassModelImpl.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInSuperClassModelImpl.java new file mode 100644 index 00000000000..72461cdabd5 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInSuperClassModelImpl.java @@ -0,0 +1,61 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +public class CreatorInSuperClassModelImpl extends CreatorInSuperClassModel { + private final String propertyA; + private final String propertyB; + + CreatorInSuperClassModelImpl(final String propertyA, final String propertyB) { + this.propertyA = propertyA; + this.propertyB = propertyB; + } + + @Override + public String getPropertyA() { + return propertyA; + } + + @Override + public String getPropertyB() { + return propertyB; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorInSuperClassModelImpl that = (CreatorInSuperClassModelImpl) o; + + if (propertyA != null ? !propertyA.equals(that.propertyA) : that.propertyA != null) { + return false; + } + return propertyB != null ? propertyB.equals(that.propertyB) : that.propertyB == null; + } + + @Override + public int hashCode() { + int result = propertyA != null ? propertyA.hashCode() : 0; + result = 31 * result + (propertyB != null ? propertyB.hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidConstructorModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidConstructorModel.java new file mode 100644 index 00000000000..efe2ecac762 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidConstructorModel.java @@ -0,0 +1,85 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorInvalidConstructorModel { + private final Integer integerField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorInvalidConstructorModel(@BsonProperty("integerField") final Integer integerField, final String stringField) { + this.integerField = integerField; + this.stringField = stringField; + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorInvalidConstructorModel that = (CreatorInvalidConstructorModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorInvalidConstructorModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMethodModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMethodModel.java new file mode 100644 index 00000000000..26d60ea5d24 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMethodModel.java @@ -0,0 +1,89 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorInvalidMethodModel { + private final Integer integerField; + private String stringField; + public long longField; + + private CreatorInvalidMethodModel(final Integer integerField, final String stringField) { + this.integerField = integerField; + this.stringField = stringField; + } + + @BsonCreator + public static CreatorInvalidMethodModel create(@BsonProperty("integerField") final Integer integerField, final String stringField) { + return new CreatorInvalidMethodModel(integerField, stringField); + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorMethodModel that = (CreatorMethodModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorInvalidMethodModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMethodReturnTypeModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMethodReturnTypeModel.java new file mode 100644 index 00000000000..5929d979ca7 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMethodReturnTypeModel.java @@ -0,0 +1,89 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorInvalidMethodReturnTypeModel { + private final Integer integerField; + private String stringField; + public long longField; + + private CreatorInvalidMethodReturnTypeModel(final Integer integerField, final String stringField) { + this.integerField = integerField; + this.stringField = stringField; + } + + @BsonCreator + public static String create(@BsonProperty("integerField") final Integer integerField) { + return "Nope"; + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorMethodModel that = (CreatorMethodModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorInvalidMethodModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleConstructorsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleConstructorsModel.java new file mode 100644 index 00000000000..9451be68824 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleConstructorsModel.java @@ -0,0 +1,98 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorInvalidMultipleConstructorsModel { + private final Integer integerField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorInvalidMultipleConstructorsModel(@BsonProperty("integerField") final Integer integerField) { + this.integerField = integerField; + } + + @BsonCreator + public CreatorInvalidMultipleConstructorsModel(@BsonProperty("integerField") final Integer integerField, + @BsonProperty("stringField") final String string) { + this(integerField); + setStringField(stringField); + } + + + public CreatorInvalidMultipleConstructorsModel(final Integer integerField, final String stringField, final long longField) { + this.integerField = integerField; + this.stringField = stringField; + this.longField = longField; + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorInvalidMultipleConstructorsModel that = (CreatorInvalidMultipleConstructorsModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorInvalidMultipleModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleCreatorsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleCreatorsModel.java new file mode 100644 index 00000000000..034c78fa530 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleCreatorsModel.java @@ -0,0 +1,95 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorInvalidMultipleCreatorsModel { + private final Integer integerField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorInvalidMultipleCreatorsModel(@BsonProperty("integerField") final Integer integerField) { + this.integerField = integerField; + } + + public CreatorInvalidMultipleCreatorsModel(final Integer integerField, final String stringField, final long longField) { + this.integerField = integerField; + this.stringField = stringField; + this.longField = longField; + } + + @BsonCreator + public static CreatorInvalidMultipleCreatorsModel create(@BsonProperty("integerField") final Integer integerField) { + return new CreatorInvalidMultipleCreatorsModel(integerField); + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorInvalidMultipleCreatorsModel that = (CreatorInvalidMultipleCreatorsModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorInvalidMultipleModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleStaticCreatorsModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleStaticCreatorsModel.java new file mode 100644 index 00000000000..fa3d445219c --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidMultipleStaticCreatorsModel.java @@ -0,0 +1,102 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorInvalidMultipleStaticCreatorsModel { + private final Integer integerField; + private String stringField; + public long longField; + + private CreatorInvalidMultipleStaticCreatorsModel(final Integer integerField) { + this.integerField = integerField; + } + + public CreatorInvalidMultipleStaticCreatorsModel(final Integer integerField, final String stringField, final long longField) { + this.integerField = integerField; + this.stringField = stringField; + this.longField = longField; + } + + @BsonCreator + public static CreatorInvalidMultipleStaticCreatorsModel create(@BsonProperty("integerField") final Integer integerField) { + return new CreatorInvalidMultipleStaticCreatorsModel(integerField); + } + + @BsonCreator + public static CreatorInvalidMultipleStaticCreatorsModel create(@BsonProperty("integerField") final Integer integerField, + @BsonProperty("stringField") final String stringField) { + CreatorInvalidMultipleStaticCreatorsModel model = new CreatorInvalidMultipleStaticCreatorsModel(integerField); + model.setStringField(stringField); + return model; + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorInvalidMultipleStaticCreatorsModel that = (CreatorInvalidMultipleStaticCreatorsModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorInvalidMultipleModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidTypeConstructorModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidTypeConstructorModel.java new file mode 100644 index 00000000000..f5e3a242b13 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidTypeConstructorModel.java @@ -0,0 +1,84 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorInvalidTypeConstructorModel { + private final Integer integerField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorInvalidTypeConstructorModel(@BsonProperty("integerField") final String integerField) { + this.integerField = Integer.parseInt(integerField); + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorInvalidTypeConstructorModel that = (CreatorInvalidTypeConstructorModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorInvalidConstructorModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidTypeMethodModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidTypeMethodModel.java new file mode 100644 index 00000000000..c5cb636add3 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorInvalidTypeMethodModel.java @@ -0,0 +1,88 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorInvalidTypeMethodModel { + private final Integer integerField; + private String stringField; + public long longField; + + private CreatorInvalidTypeMethodModel(final Integer integerField) { + this.integerField = integerField; + } + + @BsonCreator + public static CreatorInvalidTypeMethodModel create(@BsonProperty("integerField") final String integerField) { + return new CreatorInvalidTypeMethodModel(Integer.parseInt(integerField)); + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorMethodModel that = (CreatorMethodModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorInvalidMethodModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorMethodModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorMethodModel.java new file mode 100644 index 00000000000..55924e9c94d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorMethodModel.java @@ -0,0 +1,94 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorMethodModel { + private final Integer integerField; + private String stringField; + public long longField; + + private CreatorMethodModel(final Integer integerField) { + this.integerField = integerField; + } + + @BsonCreator + public static CreatorMethodModel create(@BsonProperty("integerField") final Integer integerField) { + return new CreatorMethodModel(integerField); + } + + public CreatorMethodModel(final Integer integerField, final String stringField, final long longField) { + this.integerField = integerField; + this.stringField = stringField; + this.longField = longField; + } + + public Integer getIntegerField() { + return integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorMethodModel that = (CreatorMethodModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorMethodModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorMethodThrowsExceptionModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorMethodThrowsExceptionModel.java new file mode 100644 index 00000000000..4b09c75b76d --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorMethodThrowsExceptionModel.java @@ -0,0 +1,91 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; +import org.bson.codecs.pojo.annotations.BsonProperty; + +public final class CreatorMethodThrowsExceptionModel { + private Integer integerField; + private String stringField; + public long longField; + + @BsonCreator + public static CreatorMethodThrowsExceptionModel create(@BsonProperty("integerField") final Integer integerField) { + throw new UnsupportedOperationException("Nope"); + } + + private CreatorMethodThrowsExceptionModel() { + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(final Integer integerField) { + this.integerField = integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorNoArgsMethodModel that = (CreatorNoArgsMethodModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorMethodThrowsExceptionModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorNoArgsConstructorModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorNoArgsConstructorModel.java new file mode 100644 index 00000000000..c4e5b4e69c9 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorNoArgsConstructorModel.java @@ -0,0 +1,92 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; + +public final class CreatorNoArgsConstructorModel { + private Integer integerField; + private String stringField; + public long longField; + + @BsonCreator + public CreatorNoArgsConstructorModel(){ + } + + public CreatorNoArgsConstructorModel(final Integer integerField, final String stringField, final long longField) { + this.integerField = integerField; + this.stringField = stringField; + this.longField = longField; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(final Integer integerField) { + this.integerField = integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorNoArgsConstructorModel that = (CreatorNoArgsConstructorModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorNoArgsConstructorModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorNoArgsMethodModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorNoArgsMethodModel.java new file mode 100644 index 00000000000..ddee7f48668 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/CreatorNoArgsMethodModel.java @@ -0,0 +1,96 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonCreator; + +public final class CreatorNoArgsMethodModel { + private Integer integerField; + private String stringField; + public long longField; + + @BsonCreator + public static CreatorNoArgsMethodModel create() { + return new CreatorNoArgsMethodModel(); + } + + private CreatorNoArgsMethodModel() { + } + + public CreatorNoArgsMethodModel(final Integer integerField, final String stringField, final long longField) { + this.integerField = integerField; + this.stringField = stringField; + this.longField = longField; + } + + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(final Integer integerField) { + this.integerField = integerField; + } + + public String getStringField() { + return stringField; + } + + public void setStringField(final String stringField) { + this.stringField = stringField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CreatorNoArgsMethodModel that = (CreatorNoArgsMethodModel) o; + + if (longField != that.longField) { + return false; + } + if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) { + return false; + } + if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = getIntegerField() != null ? getIntegerField().hashCode() : 0; + result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0); + result = 31 * result + (int) (longField ^ (longField >>> 32)); + return result; + } + + @Override + public String toString() { + return "CreatorNoArgsMethodModel{" + + "integerField=" + integerField + + ", stringField='" + stringField + "'" + + ", longField=" + longField + + "}"; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/DiscriminatorNameModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/DiscriminatorNameModel.java new file mode 100644 index 00000000000..dd9bde84836 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/DiscriminatorNameModel.java @@ -0,0 +1,20 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +public final class DiscriminatorNameModel { +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/FieldStorageModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/FieldStorageModel.java new file mode 100644 index 00000000000..f0609c0c8b2 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/FieldStorageModel.java @@ -0,0 +1,21 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +public final class FieldStorageModel { + private String id; +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModel.java new file mode 100644 index 00000000000..b5def72fb17 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModel.java @@ -0,0 +1,23 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; + +@BsonDiscriminator +public interface InterfaceModel { +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModelImplA.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModelImplA.java new file mode 100644 index 00000000000..3b6d5c7f4d0 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModelImplA.java @@ -0,0 +1,67 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; + +@BsonDiscriminator +public class InterfaceModelImplA implements InterfaceModel { + private boolean value; + private String name; + + public boolean isValue() { + return value; + } + + public InterfaceModelImplA setValue(final boolean value) { + this.value = value; + return this; + } + + public String getName() { + return name; + } + + public InterfaceModelImplA setName(final String name) { + this.name = name; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InterfaceModelImplA that = (InterfaceModelImplA) o; + + if (value != that.value) { + return false; + } + return name != null ? name.equals(that.name) : that.name == null; + } + + @Override + public int hashCode() { + int result = (value ? 1 : 0); + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModelImplB.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModelImplB.java new file mode 100644 index 00000000000..5cf71117115 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/InterfaceModelImplB.java @@ -0,0 +1,67 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; + +@BsonDiscriminator +public class InterfaceModelImplB implements InterfaceModel { + private boolean value; + private int integer; + + public boolean isValue() { + return value; + } + + public InterfaceModelImplB setValue(final boolean value) { + this.value = value; + return this; + } + + public int getInteger() { + return integer; + } + + public InterfaceModelImplB setInteger(final int integer) { + this.integer = integer; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + InterfaceModelImplB that = (InterfaceModelImplB) o; + + if (value != that.value) { + return false; + } + return integer == that.integer; + } + + @Override + public int hashCode() { + int result = (value ? 1 : 0); + result = 31 * result + integer; + return result; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterImmutableModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterImmutableModel.java new file mode 100644 index 00000000000..440f2109798 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterImmutableModel.java @@ -0,0 +1,56 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.Collections; +import java.util.Map; + +public class MapGetterImmutableModel { + + private final Map mapField; + + public MapGetterImmutableModel() { + this(Collections.emptyMap()); + } + + public MapGetterImmutableModel(final Map mapField) { + this.mapField = Collections.unmodifiableMap(mapField); + } + + public Map getMapField() { + return mapField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + MapGetterImmutableModel that = (MapGetterImmutableModel) o; + + return mapField != null ? mapField.equals(that.mapField) : that.mapField == null; + } + + @Override + public int hashCode() { + return mapField != null ? mapField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterMutableModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterMutableModel.java new file mode 100644 index 00000000000..1f4eb1a7391 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterMutableModel.java @@ -0,0 +1,55 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.HashMap; +import java.util.Map; + +public class MapGetterMutableModel { + + private final Map mapField; + + public MapGetterMutableModel() { + this.mapField = new HashMap(); + } + + public MapGetterMutableModel(final Map mapField) { + this.mapField = mapField; + } + + public Map getMapField() { + return mapField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + MapGetterMutableModel that = (MapGetterMutableModel) o; + return mapField != null ? mapField.equals(that.mapField) : that.mapField == null; + } + + @Override + public int hashCode() { + return mapField != null ? mapField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterNonEmptyModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterNonEmptyModel.java new file mode 100644 index 00000000000..e4d5aa712bc --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterNonEmptyModel.java @@ -0,0 +1,56 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.Collections; +import java.util.Map; + +public class MapGetterNonEmptyModel { + + private final Map mapField; + + public MapGetterNonEmptyModel() { + this(Collections.singletonMap("a", 1)); + } + + public MapGetterNonEmptyModel(final Map mapField) { + this.mapField = mapField; + } + + public Map getMapField() { + return mapField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + MapGetterNonEmptyModel that = (MapGetterNonEmptyModel) o; + + return mapField != null ? mapField.equals(that.mapField) : that.mapField == null; + } + + @Override + public int hashCode() { + return mapField != null ? mapField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterNullModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterNullModel.java new file mode 100644 index 00000000000..c1f60b07d37 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/MapGetterNullModel.java @@ -0,0 +1,55 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import java.util.Map; + +public class MapGetterNullModel { + + private final Map mapField; + + public MapGetterNullModel() { + this(null); + } + + public MapGetterNullModel(final Map mapField) { + this.mapField = mapField; + } + + public Map getMapField() { + return mapField; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()){ + return false; + } + + MapGetterNullModel that = (MapGetterNullModel) o; + + return mapField != null ? mapField.equals(that.mapField) : that.mapField == null; + } + + @Override + public int hashCode() { + return mapField != null ? mapField.hashCode() : 0; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/PropertyNameModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/PropertyNameModel.java new file mode 100644 index 00000000000..0f87d599cb4 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/PropertyNameModel.java @@ -0,0 +1,22 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +public final class PropertyNameModel { + private int myModelField; + private int myModel2Field; +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/Subclass1Model.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/Subclass1Model.java new file mode 100644 index 00000000000..51bbdd46583 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/Subclass1Model.java @@ -0,0 +1,64 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; + +@BsonDiscriminator +public class Subclass1Model extends SuperClassModel { + private String name; + + public String getName() { + return name; + } + + public Subclass1Model setName(final String name) { + this.name = name; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + Subclass1Model that = (Subclass1Model) o; + + return getName() != null ? getName().equals(that.getName()) : that.getName() == null; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (getName() != null ? getName().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Subclass1Model{" + + "name='" + name + '\'' + + "} " + super.toString(); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/Subclass2Model.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/Subclass2Model.java new file mode 100644 index 00000000000..1fd69138651 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/Subclass2Model.java @@ -0,0 +1,64 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; + +@BsonDiscriminator +public class Subclass2Model extends SuperClassModel { + private int integer; + + public int getInteger() { + return integer; + } + + public Subclass2Model setInteger(final int integer) { + this.integer = integer; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + + Subclass2Model that = (Subclass2Model) o; + + return getInteger() == that.getInteger(); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + getInteger(); + return result; + } + + @Override + public String toString() { + return "Subclass2Model{" + + "integer=" + integer + + "} " + super.toString(); + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/SuperClassModel.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/SuperClassModel.java new file mode 100644 index 00000000000..b46ebbd699b --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/SuperClassModel.java @@ -0,0 +1,59 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.codecs.pojo.entities.conventions; + +import org.bson.codecs.pojo.annotations.BsonDiscriminator; + +@BsonDiscriminator +public abstract class SuperClassModel { + private boolean value; + + public boolean isValue() { + return value; + } + + public SuperClassModel setValue(final boolean value) { + this.value = value; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SuperClassModel that = (SuperClassModel) o; + + return isValue() == that.isValue(); + } + + @Override + public int hashCode() { + return (isValue() ? 1 : 0); + } + + @Override + public String toString() { + return "SuperClassModel{" + + "value=" + value + + '}'; + } +} diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/package-info.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/package-info.java new file mode 100644 index 00000000000..44ac0e845ad --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/conventions/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Package contains test models for conventions + */ +package org.bson.codecs.pojo.entities.conventions; diff --git a/bson/src/test/unit/org/bson/codecs/pojo/entities/package-info.java b/bson/src/test/unit/org/bson/codecs/pojo/entities/package-info.java new file mode 100644 index 00000000000..abd43766a50 --- /dev/null +++ b/bson/src/test/unit/org/bson/codecs/pojo/entities/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Test entities and related classes for the conventions tests + */ +package org.bson.codecs.pojo.entities; diff --git a/bson/src/test/unit/org/bson/codecs/configuration/CodecCacheSpecification.groovy b/bson/src/test/unit/org/bson/internal/CodecCacheSpecification.groovy similarity index 86% rename from bson/src/test/unit/org/bson/codecs/configuration/CodecCacheSpecification.groovy rename to bson/src/test/unit/org/bson/internal/CodecCacheSpecification.groovy index bb68235e92f..e4eab0b34a2 100644 --- a/bson/src/test/unit/org/bson/codecs/configuration/CodecCacheSpecification.groovy +++ b/bson/src/test/unit/org/bson/internal/CodecCacheSpecification.groovy @@ -1,11 +1,11 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,9 +14,10 @@ * limitations under the License. */ -package org.bson.codecs.configuration +package org.bson.internal import org.bson.codecs.MinKeyCodec +import org.bson.codecs.configuration.CodecConfigurationException import org.bson.types.MinKey import spock.lang.Specification diff --git a/bson/src/test/unit/org/bson/internal/OverridableUuidRepresentationCodecRegistrySpecification.groovy b/bson/src/test/unit/org/bson/internal/OverridableUuidRepresentationCodecRegistrySpecification.groovy new file mode 100644 index 00000000000..756c03af788 --- /dev/null +++ b/bson/src/test/unit/org/bson/internal/OverridableUuidRepresentationCodecRegistrySpecification.groovy @@ -0,0 +1,58 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal + +import org.bson.BsonBinaryReader +import org.bson.BsonBinaryWriter +import org.bson.ByteBufNIO +import org.bson.UuidRepresentation +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext +import org.bson.io.BasicOutputBuffer +import org.bson.io.ByteBufferBsonInput +import spock.lang.Specification + +import java.nio.ByteBuffer + + +class OverridableUuidRepresentationCodecRegistrySpecification extends Specification { + def 'should handle cycles'() { + given: + def registry = new OverridableUuidRepresentationCodecRegistry(new ClassModelCodecProvider(), UuidRepresentation.STANDARD) + + when: + Codec topCodec = registry.get(Top) + + then: + topCodec instanceof TopCodec + + when: + def top = new Top('Bob', + new Top('Jim', null, null), + new Nested('George', new Top('Joe', null, null))) + def writer = new BsonBinaryWriter(new BasicOutputBuffer()) + topCodec.encode(writer, top, EncoderContext.builder().build()) + ByteArrayOutputStream os = new ByteArrayOutputStream() + writer.getBsonOutput().pipe(os) + writer.close() + + then: + topCodec.decode(new BsonBinaryReader(new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap(os.toByteArray())))), + DecoderContext.builder().build()) == top + } +} diff --git a/bson/src/test/unit/org/bson/codecs/configuration/ProvidersCodecRegistrySpecification.groovy b/bson/src/test/unit/org/bson/internal/ProvidersCodecRegistrySpecification.groovy similarity index 97% rename from bson/src/test/unit/org/bson/codecs/configuration/ProvidersCodecRegistrySpecification.groovy rename to bson/src/test/unit/org/bson/internal/ProvidersCodecRegistrySpecification.groovy index 97b05f6f832..7aad6cb9c15 100644 --- a/bson/src/test/unit/org/bson/codecs/configuration/ProvidersCodecRegistrySpecification.groovy +++ b/bson/src/test/unit/org/bson/internal/ProvidersCodecRegistrySpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.bson.codecs.configuration +package org.bson.internal import org.bson.BsonBinaryReader import org.bson.BsonBinaryWriter @@ -26,6 +26,9 @@ import org.bson.codecs.Codec import org.bson.codecs.DecoderContext import org.bson.codecs.EncoderContext import org.bson.codecs.MinKeyCodec +import org.bson.codecs.configuration.CodecConfigurationException +import org.bson.codecs.configuration.CodecProvider +import org.bson.codecs.configuration.CodecRegistry import org.bson.io.BasicOutputBuffer import org.bson.io.ByteBufferBsonInput import org.bson.types.MaxKey diff --git a/bson/src/test/unit/org/bson/internal/UnsignedLongsTest.java b/bson/src/test/unit/org/bson/internal/UnsignedLongsTest.java new file mode 100644 index 00000000000..4dcc50fed37 --- /dev/null +++ b/bson/src/test/unit/org/bson/internal/UnsignedLongsTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * Copyright 2010 The Guava Authors + * Copyright 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.internal; + +import org.junit.Test; + +import java.math.BigInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class UnsignedLongsTest { + + @Test + public void testCompare() { + // max value + assertTrue(UnsignedLongs.compare(0, 0xffffffffffffffffL) < 0); + assertTrue(UnsignedLongs.compare(0xffffffffffffffffL, 0) > 0); + + // both with high bit set + assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0xffffffffffffffffL) < 0); + assertTrue(UnsignedLongs.compare(0xffffffffffffffffL, 0xff1a618b7f65ea12L) > 0); + + // one with high bit set + assertTrue(UnsignedLongs.compare(0x5a4316b8c153ac4dL, 0xff1a618b7f65ea12L) < 0); + assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0x5a4316b8c153ac4dL) > 0); + + // neither with high bit set + assertTrue(UnsignedLongs.compare(0x5a4316b8c153ac4dL, 0x6cf78a4b139a4e2aL) < 0); + assertTrue(UnsignedLongs.compare(0x6cf78a4b139a4e2aL, 0x5a4316b8c153ac4dL) > 0); + + // same value + assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0xff1a618b7f65ea12L) == 0); + } + + @Test + public void testParseLong() { + assertEquals(0xffffffffffffffffL, UnsignedLongs.parse("18446744073709551615")); + assertEquals(0x7fffffffffffffffL, UnsignedLongs.parse("9223372036854775807")); + assertEquals(0xff1a618b7f65ea12L, UnsignedLongs.parse("18382112080831834642")); + assertEquals(0x5a4316b8c153ac4dL, UnsignedLongs.parse("6504067269626408013")); + assertEquals(0x6cf78a4b139a4e2aL, UnsignedLongs.parse("7851896530399809066")); + } + + @Test + public void testToString() { + String[] tests = { + "ffffffffffffffff", + "7fffffffffffffff", + "ff1a618b7f65ea12", + "5a4316b8c153ac4d", + "6cf78a4b139a4e2a" + }; + for (String x : tests) { + BigInteger xValue = new BigInteger(x, 16); + long xLong = xValue.longValue(); // signed + assertEquals(xValue.toString(10), UnsignedLongs.toString(xLong)); + } + } + +} diff --git a/bson/src/test/unit/org/bson/internal/UuidHelperSpecification.groovy b/bson/src/test/unit/org/bson/internal/UuidHelperSpecification.groovy new file mode 100644 index 00000000000..6bd1fb219dc --- /dev/null +++ b/bson/src/test/unit/org/bson/internal/UuidHelperSpecification.groovy @@ -0,0 +1,52 @@ +package org.bson.internal + +import org.bson.BSONException +import org.bson.UuidRepresentation +import spock.lang.Specification +import spock.lang.Unroll + +class UuidHelperSpecification extends Specification { + + @Unroll + def 'should encode different types of UUID'() { + given: + def expectedUuid = UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09') + + expect: + bytes == UuidHelper.encodeUuidToBinary(expectedUuid, uuidRepresentation) + + where: + bytes | uuidRepresentation + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] | UuidRepresentation.JAVA_LEGACY + [8, 7, 6, 5, 4, 3, 2, 1, 16, 15, 14, 13, 12, 11, 10, 9] | UuidRepresentation.STANDARD + [8, 7, 6, 5, 4, 3, 2, 1, 16, 15, 14, 13, 12, 11, 10, 9] | UuidRepresentation.PYTHON_LEGACY + [5, 6, 7, 8, 3, 4, 1, 2, 16, 15, 14, 13, 12, 11, 10, 9] | UuidRepresentation.C_SHARP_LEGACY + } + + @Unroll + def 'should decode different types of UUID'() { + given: + byte[] expectedBytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + + expect: + uuid == UuidHelper.decodeBinaryToUuid(expectedBytes, (byte) type, uuidRepresentation) + + where: + uuid | type | uuidRepresentation + UUID.fromString('08070605-0403-0201-100f-0e0d0c0b0a09') | 3 | UuidRepresentation.JAVA_LEGACY + UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | 3 | UuidRepresentation.PYTHON_LEGACY + UUID.fromString('04030201-0605-0807-090a-0b0c0d0e0f10') | 3 | UuidRepresentation.C_SHARP_LEGACY + UUID.fromString('01020304-0506-0708-090a-0b0c0d0e0f10') | 4 | UuidRepresentation.STANDARD + } + + def 'should error when decoding a subtype 3 binary to standard representation'() { + given: + byte[] expectedBytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + + when: + UuidHelper.decodeBinaryToUuid(expectedBytes, (byte) 3, UuidRepresentation.STANDARD) + + then: + thrown(BSONException) + } +} diff --git a/bson/src/test/unit/org/bson/io/BasicOutputBufferSpecification.groovy b/bson/src/test/unit/org/bson/io/BasicOutputBufferSpecification.groovy index 0ebaea2a92f..38de06bf8cf 100644 --- a/bson/src/test/unit/org/bson/io/BasicOutputBufferSpecification.groovy +++ b/bson/src/test/unit/org/bson/io/BasicOutputBufferSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -309,6 +309,17 @@ class BasicOutputBufferSpecification extends Specification { bsonOutput.size == 10 } + def 'should get byte buffer as little endian'() { + given: + def bsonOutput = new BasicOutputBuffer(4) + + when: + bsonOutput.writeBytes([1, 0, 0, 0] as byte[]) + + then: + bsonOutput.getByteBuffers()[0].getInt() == 1 + } + def 'should get internal buffer'() { given: def bsonOutput = new BasicOutputBuffer(4) diff --git a/bson/src/test/unit/org/bson/io/BasicOutputBufferTest.java b/bson/src/test/unit/org/bson/io/BasicOutputBufferTest.java index 054c2d41128..1531c7d0678 100644 --- a/bson/src/test/unit/org/bson/io/BasicOutputBufferTest.java +++ b/bson/src/test/unit/org/bson/io/BasicOutputBufferTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/driver/src/test/unit/org/bson/io/BitsTest.java b/bson/src/test/unit/org/bson/io/BitsTest.java similarity index 98% rename from driver/src/test/unit/org/bson/io/BitsTest.java rename to bson/src/test/unit/org/bson/io/BitsTest.java index e86b3acc2f6..1ff8d6130e4 100644 --- a/driver/src/test/unit/org/bson/io/BitsTest.java +++ b/bson/src/test/unit/org/bson/io/BitsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/org/bson/io/ByteBufferBsonInputSpecification.groovy b/bson/src/test/unit/org/bson/io/ByteBufferBsonInputSpecification.groovy index e9887754b15..80623280c80 100644 --- a/bson/src/test/unit/org/bson/io/ByteBufferBsonInputSpecification.groovy +++ b/bson/src/test/unit/org/bson/io/ByteBufferBsonInputSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2008-2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,6 @@ class ByteBufferBsonInputSpecification extends Specification { expect: bytesRead == bytes stream.position == 3 - } def 'should read into a byte array at offset until length'() { @@ -270,6 +269,35 @@ class ByteBufferBsonInputSpecification extends Specification { stream.position == 2 } + def 'should reset to the BsonInputMark'() { + given: + def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([0x4a, 0x61, 0x76, 0x61, 0] as byte[]))) + + when: + BsonInputMark markOne = null + BsonInputMark markTwo = null + + stream.with { + readByte() + readByte() + markOne = getMark(1024) + readByte() + readByte() + markTwo = getMark(1025) + readByte() + } + markOne.reset() + + then: + stream.position == 2 + + when: + markTwo.reset() + + then: + stream.position == 4 + } + def 'should have remaining when there are more bytes'() { given: def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([0x4a, 0x61, 0x76, 0x61, 0] as byte[]))) @@ -396,4 +424,15 @@ class ByteBufferBsonInputSpecification extends Specification { then: thrown(BsonSerializationException) } + + def 'should throw BsonSerializationException if a one-byte BSON string is not null-terminated'() { + given: + def stream = new ByteBufferBsonInput(new ByteBufNIO(ByteBuffer.wrap([2, 0, 0, 0, 1, 3] as byte[]))) + + when: + stream.readString() + + then: + thrown(BsonSerializationException) + } } diff --git a/bson/src/test/unit/org/bson/json/Base64Specification.groovy b/bson/src/test/unit/org/bson/json/Base64Specification.groovy new file mode 100644 index 00000000000..0bc2566bbcc --- /dev/null +++ b/bson/src/test/unit/org/bson/json/Base64Specification.groovy @@ -0,0 +1,42 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json + +import org.bson.internal.Base64 +import spock.lang.Specification +import spock.lang.Unroll + +class Base64Specification extends Specification { + + @Unroll + def 'encodes #encoded into #decoded'() { + expect: + Base64.encode(encoded.getBytes()) == decoded + Base64.decode(decoded) == encoded.getBytes() + + where: + encoded | decoded + '' | '' + 'f' | 'Zg==' + 'fo' | 'Zm8=' + 'foo' | 'Zm9v' + 'foob' | 'Zm9vYg==' + 'fooba' | 'Zm9vYmE=' + 'foobar' | 'Zm9vYmFy' + } + +} diff --git a/bson/src/test/unit/org/bson/json/JsonBufferTest.java b/bson/src/test/unit/org/bson/json/JsonBufferTest.java deleted file mode 100644 index 281d8c98437..00000000000 --- a/bson/src/test/unit/org/bson/json/JsonBufferTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2008-2014 MongoDB, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.bson.json; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class JsonBufferTest { - - @Test - public void testRead() { - JsonBuffer buffer = new JsonBuffer("ABC"); - assertEquals('A', buffer.read()); - assertEquals('B', buffer.read()); - assertEquals('C', buffer.read()); - assertEquals(-1, buffer.read()); - } - - @Test - public void testUnRead() { - JsonBuffer buffer = new JsonBuffer("A"); - buffer.unread(buffer.read()); - assertEquals('A', buffer.read()); - assertEquals(-1, buffer.read()); - } - - @Test - public void testPosition() { - JsonBuffer buffer = new JsonBuffer("ABC"); - - buffer.setPosition(2); - assertEquals(2, buffer.getPosition()); - } - - @Test(expected = JsonParseException.class) - public void testEOFCheck() { - JsonBuffer buffer = new JsonBuffer(""); - - buffer.read(); - buffer.read(); - } -} diff --git a/bson/src/test/unit/org/bson/json/JsonReaderSpecification.groovy b/bson/src/test/unit/org/bson/json/JsonReaderSpecification.groovy new file mode 100644 index 00000000000..70417300595 --- /dev/null +++ b/bson/src/test/unit/org/bson/json/JsonReaderSpecification.groovy @@ -0,0 +1,53 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json + +import org.bson.BsonDocument +import org.bson.BsonHelper +import spock.lang.Specification +import spock.lang.Unroll + +import static org.bson.AbstractBsonReader.State.DONE +import static org.bson.AbstractBsonReader.State.TYPE + +class JsonReaderSpecification extends Specification { + + @Unroll + def 'should skip value #value'() { + given: + def document = new BsonDocument('name', value) + def reader = new JsonReader(document.toJson()) + reader.readStartDocument() + reader.readBsonType() + + when: + reader.skipName() + reader.skipValue() + + then: + reader.getState() == TYPE + + when: + reader.readEndDocument() + + then: + reader.getState() == DONE + + where: + value << BsonHelper.valuesOfEveryType() + } +} diff --git a/bson/src/test/unit/org/bson/json/JsonReaderTest.java b/bson/src/test/unit/org/bson/json/JsonReaderTest.java index bcf97c345ba..260c3784338 100644 --- a/bson/src/test/unit/org/bson/json/JsonReaderTest.java +++ b/bson/src/test/unit/org/bson/json/JsonReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,794 +20,1491 @@ import org.bson.BsonBinary; import org.bson.BsonBinarySubType; import org.bson.BsonDbPointer; +import org.bson.BsonReaderMark; import org.bson.BsonRegularExpression; import org.bson.BsonTimestamp; import org.bson.BsonType; +import org.bson.types.Decimal128; import org.bson.types.ObjectId; import org.junit.Test; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import java.util.Locale; +import static java.util.Arrays.asList; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class JsonReaderTest { - private AbstractBsonReader bsonReader; + + // Define our own until Java 8 is the minimum supported version + interface Function { + R apply(T t); + } @Test public void testArrayEmpty() { String json = "[]"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.ARRAY, bsonReader.readBsonType()); - bsonReader.readStartArray(); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndArray(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.ARRAY, bsonReader.readBsonType()); + bsonReader.readStartArray(); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndArray(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testArrayOneElement() { String json = "[1]"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.ARRAY, bsonReader.readBsonType()); - bsonReader.readStartArray(); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals(1, bsonReader.readInt32()); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndArray(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.ARRAY, bsonReader.readBsonType()); + bsonReader.readStartArray(); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals(1, bsonReader.readInt32()); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndArray(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testArrayTwoElements() { String json = "[1, 2]"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.ARRAY, bsonReader.readBsonType()); - bsonReader.readStartArray(); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals(1, bsonReader.readInt32()); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals(2, bsonReader.readInt32()); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndArray(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.ARRAY, bsonReader.readBsonType()); + bsonReader.readStartArray(); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals(1, bsonReader.readInt32()); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals(2, bsonReader.readInt32()); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndArray(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testBooleanFalse() { String json = "false"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.BOOLEAN, bsonReader.readBsonType()); - assertEquals(false, bsonReader.readBoolean()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BOOLEAN, bsonReader.readBsonType()); + assertFalse(bsonReader.readBoolean()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testBooleanTrue() { String json = "true"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.BOOLEAN, bsonReader.readBsonType()); - assertEquals(true, bsonReader.readBoolean()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BOOLEAN, bsonReader.readBsonType()); + assertTrue(bsonReader.readBoolean()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeMinBson() { String json = "new Date(-9223372036854775808)"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(-9223372036854775808L, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(-9223372036854775808L, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeMaxBson() { String json = "new Date(9223372036854775807)"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - long k = bsonReader.readDateTime(); - assertEquals(9223372036854775807L, k); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + long k = bsonReader.readDateTime(); + assertEquals(9223372036854775807L, k); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeShell() { String json = "ISODate(\"1970-01-01T00:00:00Z\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(0, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(0, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeShellWith24HourTimeSpecification() { String json = "ISODate(\"2013-10-04T12:07:30.443Z\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(1380888450443L, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(1380888450443L, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeStrict() { String json = "{ \"$date\" : 0 }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(0, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(0, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testNestedDateTimeStrict() { + String json = "{d1 : { \"$date\" : 0 }, d2 : { \"$date\" : 1 } }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals(0L, bsonReader.readDateTime("d1")); + assertEquals(1L, bsonReader.readDateTime("d2")); + bsonReader.readEndDocument(); + return null; + } + }); } @Test public void testDateTimeISOString() { String json = "{ \"$date\" : \"2015-04-16T14:55:57.626Z\" }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(1429196157626L, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(1429196157626L, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeISOStringWithTimeOffset() { String json = "{ \"$date\" : \"2015-04-16T16:55:57.626+02:00\" }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(1429196157626L, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - } - - @Test(expected = JsonParseException.class) - public void testInvalidDateTimeISOString1() { - String json = "{ \"$date\" : \"2015-04-16T16:55:57.626+02:0000\" }"; - bsonReader = new JsonReader(json); - bsonReader.readBsonType(); - } - - @Test(expected = JsonParseException.class) - public void testInvalidDateTimeISOString2() { - String json = "{ \"$date\" : \"2015-04-16T16:55:57.626Z invalid string\" }"; - bsonReader = new JsonReader(json); - bsonReader.readBsonType(); - } - - @Test(expected = JsonParseException.class) - public void testInvalidDateTimeValue() { - String json = "{ \"$date\" : {} }"; - bsonReader = new JsonReader(json); - bsonReader.readBsonType(); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(1429196157626L, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeTengen() { String json = "new Date(0)"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(0, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(0, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDocumentEmpty() { String json = "{ }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - bsonReader.readStartDocument(); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndDocument(); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + bsonReader.readStartDocument(); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndDocument(); + return null; + } + }); } @Test public void testDocumentNested() { String json = "{ \"a\" : { \"x\" : 1 }, \"y\" : 2 }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - bsonReader.readStartDocument(); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - assertEquals("a", bsonReader.readName()); - bsonReader.readStartDocument(); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals("x", bsonReader.readName()); - assertEquals(1, bsonReader.readInt32()); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndDocument(); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals("y", bsonReader.readName()); - assertEquals(2, bsonReader.readInt32()); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + bsonReader.readStartDocument(); + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + assertEquals("a", bsonReader.readName()); + bsonReader.readStartDocument(); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals("x", bsonReader.readName()); + assertEquals(1, bsonReader.readInt32()); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndDocument(); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals("y", bsonReader.readName()); + assertEquals(2, bsonReader.readInt32()); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDocumentOneElement() { String json = "{ \"x\" : 1 }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - bsonReader.readStartDocument(); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals("x", bsonReader.readName()); - assertEquals(1, bsonReader.readInt32()); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + bsonReader.readStartDocument(); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals("x", bsonReader.readName()); + assertEquals(1, bsonReader.readInt32()); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDocumentTwoElements() { String json = "{ \"x\" : 1, \"y\" : 2 }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - bsonReader.readStartDocument(); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals("x", bsonReader.readName()); - assertEquals(1, bsonReader.readInt32()); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals("y", bsonReader.readName()); - assertEquals(2, bsonReader.readInt32()); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + bsonReader.readStartDocument(); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals("x", bsonReader.readName()); + assertEquals(1, bsonReader.readInt32()); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals("y", bsonReader.readName()); + assertEquals(2, bsonReader.readInt32()); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDouble() { String json = "1.5"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOUBLE, bsonReader.readBsonType()); - assertEquals(1.5, bsonReader.readDouble(), 0); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOUBLE, bsonReader.readBsonType()); + assertEquals(1.5, bsonReader.readDouble(), 0); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testHexData() { - byte[] expectedBytes = new byte[]{0x01, 0x23}; + final byte[] expectedBytes = new byte[]{0x01, 0x23}; String json = "HexData(0, \"0123\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.BINARY, bsonReader.readBsonType()); - BsonBinary binary = bsonReader.readBinaryData(); - byte[] bytes = binary.getData(); - assertArrayEquals(expectedBytes, binary.getData()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertArrayEquals(expectedBytes, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testHexDataWithNew() { - byte[] expectedBytes = new byte[]{0x01, 0x23}; + final byte[] expectedBytes = new byte[]{0x01, 0x23}; String json = "new HexData(0, \"0123\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.BINARY, bsonReader.readBsonType()); - BsonBinary binary = bsonReader.readBinaryData(); - byte[] bytes = binary.getData(); - assertArrayEquals(expectedBytes, binary.getData()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertArrayEquals(expectedBytes, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testInt32() { String json = "123"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals(123, bsonReader.readInt32()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals(123, bsonReader.readInt32()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testInt64() { String json = String.valueOf(Long.MAX_VALUE); - bsonReader = new JsonReader(json); - assertEquals(BsonType.INT64, bsonReader.readBsonType()); - assertEquals(Long.MAX_VALUE, bsonReader.readInt64()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - } - - @Test - public void testNumberLong() { - String json = "NumberLong(123)"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.INT64, bsonReader.readBsonType()); - assertEquals(123, bsonReader.readInt64()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.INT64, bsonReader.readBsonType()); + assertEquals(Long.MAX_VALUE, bsonReader.readInt64()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testNumberLongExtendedJson() { String json = "{\"$numberLong\":\"123\"}"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.INT64, bsonReader.readBsonType()); - assertEquals(123, bsonReader.readInt64()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.INT64, bsonReader.readBsonType()); + assertEquals(123, bsonReader.readInt64()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test - public void testNumberLongWithNew() { - String json = "new NumberLong(123)"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.INT64, bsonReader.readBsonType()); - assertEquals(123, bsonReader.readInt64()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + public void testNumberLong() { + List jsonTexts = asList( + "NumberLong(123)", + "NumberLong(\"123\")", + "new NumberLong(123)", + "new NumberLong(\"123\")"); + for (String json : jsonTexts) { + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.INT64, bsonReader.readBsonType()); + assertEquals(123, bsonReader.readInt64()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + } + + @Test + public void testNumberInt() { + List jsonTexts = asList( + "NumberInt(123)", + "NumberInt(\"123\")", + "new NumberInt(123)", + "new NumberInt(\"123\")"); + for (String json : jsonTexts) { + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals(123, bsonReader.readInt32()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + } + + @Test + public void testDecimal128StringConstructor() { + String json = "NumberDecimal(\"314E-2\")"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DECIMAL128, bsonReader.readBsonType()); + assertEquals(Decimal128.parse("314E-2"), bsonReader.readDecimal128()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testDecimal128Int32Constructor() { + String json = "NumberDecimal(" + Integer.MAX_VALUE + ")"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DECIMAL128, bsonReader.readBsonType()); + assertEquals(new Decimal128(Integer.MAX_VALUE), bsonReader.readDecimal128()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testDecimal128Int64Constructor() { + String json = "NumberDecimal(" + Long.MAX_VALUE + ")"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DECIMAL128, bsonReader.readBsonType()); + assertEquals(new Decimal128(Long.MAX_VALUE), bsonReader.readDecimal128()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testDecimal128DoubleConstructor() { + String json = "NumberDecimal(" + 1.0 + ")"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DECIMAL128, bsonReader.readBsonType()); + assertEquals(Decimal128.parse("1"), bsonReader.readDecimal128()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testDecimal128BooleanConstructor() { + String json = "NumberDecimal(true)"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + try { + bsonReader.readBsonType(); + fail("Should fail to parse NumberDecimal constructor with a string"); + } catch (JsonParseException e) { + // all good + } + return null; + } + }); + } + + @Test + public void testDecimal128WithNew() { + String json = "new NumberDecimal(\"314E-2\")"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DECIMAL128, bsonReader.readBsonType()); + assertEquals(Decimal128.parse("314E-2"), bsonReader.readDecimal128()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testDecimal128ExtendedJson() { + String json = "{\"$numberDecimal\":\"314E-2\"}"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DECIMAL128, bsonReader.readBsonType()); + assertEquals(Decimal128.parse("314E-2"), bsonReader.readDecimal128()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testJavaScript() { String json = "{ \"$code\" : \"function f() { return 1; }\" }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.JAVASCRIPT, bsonReader.readBsonType()); - assertEquals("function f() { return 1; }", bsonReader.readJavaScript()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.JAVASCRIPT, bsonReader.readBsonType()); + assertEquals("function f() { return 1; }", bsonReader.readJavaScript()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testJavaScriptWithScope() { String json = "{\"codeWithScope\": { \"$code\" : \"function f() { return n; }\", \"$scope\" : { \"n\" : 1 } } }"; - bsonReader = new JsonReader(json); - bsonReader.readStartDocument(); - assertEquals(BsonType.JAVASCRIPT_WITH_SCOPE, bsonReader.readBsonType()); - assertEquals("codeWithScope", bsonReader.readName()); - assertEquals("function f() { return n; }", bsonReader.readJavaScriptWithScope()); - bsonReader.readStartDocument(); - assertEquals(BsonType.INT32, bsonReader.readBsonType()); - assertEquals("n", bsonReader.readName()); - assertEquals(1, bsonReader.readInt32()); - bsonReader.readEndDocument(); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals(BsonType.JAVASCRIPT_WITH_SCOPE, bsonReader.readBsonType()); + assertEquals("codeWithScope", bsonReader.readName()); + assertEquals("function f() { return n; }", bsonReader.readJavaScriptWithScope()); + bsonReader.readStartDocument(); + assertEquals(BsonType.INT32, bsonReader.readBsonType()); + assertEquals("n", bsonReader.readName()); + assertEquals(1, bsonReader.readInt32()); + bsonReader.readEndDocument(); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testMaxKey() { - String json = "{ \"$maxKey\" : 1 }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.MAX_KEY, bsonReader.readBsonType()); - bsonReader.readMaxKey(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - + for (String maxKeyJson : asList("{ \"$maxKey\" : 1 }", "MaxKey", "MaxKey()", "new MaxKey", "new MaxKey()")) { + String json = "{ maxKey : " + maxKeyJson + " }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals("maxKey", bsonReader.readName()); + assertEquals(BsonType.MAX_KEY, bsonReader.getCurrentBsonType()); + bsonReader.readMaxKey(); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } } @Test public void testMinKey() { - String json = "{ \"$minKey\" : 1 }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.MIN_KEY, bsonReader.readBsonType()); - bsonReader.readMinKey(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - + for (String minKeyJson : asList("{ \"$minKey\" : 1 }", "MinKey", "MinKey()", "new MinKey", "new MinKey()")) { + String json = "{ minKey : " + minKeyJson + " }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals("minKey", bsonReader.readName()); + assertEquals(BsonType.MIN_KEY, bsonReader.getCurrentBsonType()); + bsonReader.readMinKey(); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } } @Test public void testNestedArray() { String json = "{ \"a\" : [1, 2] }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - bsonReader.readStartDocument(); - assertEquals(BsonType.ARRAY, bsonReader.readBsonType()); - assertEquals("a", bsonReader.readName()); - bsonReader.readStartArray(); - assertEquals(1, bsonReader.readInt32()); - assertEquals(2, bsonReader.readInt32()); - bsonReader.readEndArray(); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + bsonReader.readStartDocument(); + assertEquals(BsonType.ARRAY, bsonReader.readBsonType()); + assertEquals("a", bsonReader.readName()); + bsonReader.readStartArray(); + assertEquals(1, bsonReader.readInt32()); + assertEquals(2, bsonReader.readInt32()); + bsonReader.readEndArray(); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testNestedDocument() { String json = "{ \"a\" : { \"b\" : 1, \"c\" : 2 } }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - bsonReader.readStartDocument(); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - assertEquals("a", bsonReader.readName()); - bsonReader.readStartDocument(); - assertEquals("b", bsonReader.readName()); - assertEquals(1, bsonReader.readInt32()); - assertEquals("c", bsonReader.readName()); - assertEquals(2, bsonReader.readInt32()); - bsonReader.readEndDocument(); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + bsonReader.readStartDocument(); + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + assertEquals("a", bsonReader.readName()); + bsonReader.readStartDocument(); + assertEquals("b", bsonReader.readName()); + assertEquals(1, bsonReader.readInt32()); + assertEquals("c", bsonReader.readName()); + assertEquals(2, bsonReader.readInt32()); + bsonReader.readEndDocument(); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testNull() { String json = "null"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.NULL, bsonReader.readBsonType()); - bsonReader.readNull(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.NULL, bsonReader.readBsonType()); + bsonReader.readNull(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testObjectIdShell() { String json = "ObjectId(\"4d0ce088e447ad08b4721a37\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.OBJECT_ID, bsonReader.readBsonType()); - ObjectId objectId = bsonReader.readObjectId(); - assertEquals("4d0ce088e447ad08b4721a37", objectId.toString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.OBJECT_ID, bsonReader.readBsonType()); + ObjectId objectId = bsonReader.readObjectId(); + assertEquals("4d0ce088e447ad08b4721a37", objectId.toString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testObjectIdWithNew() { String json = "new ObjectId(\"4d0ce088e447ad08b4721a37\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.OBJECT_ID, bsonReader.readBsonType()); - ObjectId objectId = bsonReader.readObjectId(); - assertEquals("4d0ce088e447ad08b4721a37", objectId.toString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.OBJECT_ID, bsonReader.readBsonType()); + ObjectId objectId = bsonReader.readObjectId(); + assertEquals("4d0ce088e447ad08b4721a37", objectId.toString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testObjectIdStrict() { String json = "{ \"$oid\" : \"4d0ce088e447ad08b4721a37\" }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.OBJECT_ID, bsonReader.readBsonType()); - ObjectId objectId = bsonReader.readObjectId(); - assertEquals("4d0ce088e447ad08b4721a37", objectId.toString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.OBJECT_ID, bsonReader.readBsonType()); + ObjectId objectId = bsonReader.readObjectId(); + assertEquals("4d0ce088e447ad08b4721a37", objectId.toString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testObjectIdTenGen() { String json = "ObjectId(\"4d0ce088e447ad08b4721a37\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.OBJECT_ID, bsonReader.readBsonType()); - ObjectId objectId = bsonReader.readObjectId(); - assertEquals("4d0ce088e447ad08b4721a37", objectId.toString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.OBJECT_ID, bsonReader.readBsonType()); + ObjectId objectId = bsonReader.readObjectId(); + assertEquals("4d0ce088e447ad08b4721a37", objectId.toString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testRegularExpressionShell() { String json = "/pattern/imxs"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); - BsonRegularExpression regex = bsonReader.readRegularExpression(); - assertEquals("pattern", regex.getPattern()); - assertEquals("imxs", regex.getOptions()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); + BsonRegularExpression regex = bsonReader.readRegularExpression(); + assertEquals("pattern", regex.getPattern()); + assertEquals("imsx", regex.getOptions()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testRegularExpressionStrict() { String json = "{ \"$regex\" : \"pattern\", \"$options\" : \"imxs\" }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); - BsonRegularExpression regex = bsonReader.readRegularExpression(); - assertEquals("pattern", regex.getPattern()); - assertEquals("imxs", regex.getOptions()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - JsonWriterSettings settings = new JsonWriterSettings(JsonMode.STRICT); - + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); + BsonRegularExpression regex = bsonReader.readRegularExpression(); + assertEquals("pattern", regex.getPattern()); + assertEquals("imsx", regex.getOptions()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testRegularExpressionCanonical() { + String json = "{ \"$regularExpression\" : { \"pattern\" : \"pattern\", \"options\" : \"imxs\" }}"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); + BsonRegularExpression regex = bsonReader.readRegularExpression(); + assertEquals("pattern", regex.getPattern()); + assertEquals("imsx", regex.getOptions()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testRegularExpressionQuery() { + String json = "{ \"$regex\" : { \"$regularExpression\" : { \"pattern\" : \"pattern\", \"options\" : \"imxs\" }}}"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + BsonRegularExpression regex = bsonReader.readRegularExpression("$regex"); + assertEquals("pattern", regex.getPattern()); + assertEquals("imsx", regex.getOptions()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testRegularExpressionQueryShell() { + String json = "{ \"$regex\" : /pattern/imxs}"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + BsonRegularExpression regex = bsonReader.readRegularExpression("$regex"); + assertEquals("pattern", regex.getPattern()); + assertEquals("imsx", regex.getOptions()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testString() { - String str = "abc"; - String json = '"' + str + '"'; - bsonReader = new JsonReader(json); - assertEquals(BsonType.STRING, bsonReader.readBsonType()); - assertEquals(str, bsonReader.readString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - - str = "\ud806\udc5c"; - json = '"' + str + '"'; - bsonReader = new JsonReader(json); - assertEquals(BsonType.STRING, bsonReader.readBsonType()); - assertEquals(str, bsonReader.readString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - - str = "\\ud806\\udc5c"; - json = '"' + str + '"'; - bsonReader = new JsonReader(json); - assertEquals(BsonType.STRING, bsonReader.readBsonType()); - assertEquals("\ud806\udc5c", bsonReader.readString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - - str = "꼢𑡜ᳫ鉠鮻罖᧭䆔瘉"; - json = '"' + str + '"'; - bsonReader = new JsonReader(json); - assertEquals(BsonType.STRING, bsonReader.readBsonType()); - assertEquals(str, bsonReader.readString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + final String str = "abc"; + final String json = '"' + str + '"'; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.STRING, bsonReader.readBsonType()); + assertEquals(str, bsonReader.readString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + + final String str2 = "\ud806\udc5c"; + final String json2 = '"' + str2 + '"'; + testStringAndStream(json2, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.STRING, bsonReader.readBsonType()); + assertEquals(str2, bsonReader.readString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + + final String str3 = "\\ud806\\udc5c"; + final String json3 = '"' + str3 + '"'; + testStringAndStream(json3, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.STRING, bsonReader.readBsonType()); + assertEquals("\ud806\udc5c", bsonReader.readString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + + final String str4 = "꼢𑡜ᳫ鉠鮻罖᧭䆔瘉"; + final String json4 = '"' + str4 + '"'; + testStringAndStream(json4, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.STRING, bsonReader.readBsonType()); + assertEquals(str4, bsonReader.readString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testStringEmpty() { String json = "\"\""; - bsonReader = new JsonReader(json); - assertEquals(BsonType.STRING, bsonReader.readBsonType()); - assertEquals("", bsonReader.readString()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.STRING, bsonReader.readBsonType()); + assertEquals("", bsonReader.readString()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testSymbol() { String json = "{ \"$symbol\" : \"symbol\" }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.SYMBOL, bsonReader.readBsonType()); - assertEquals("symbol", bsonReader.readSymbol()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.SYMBOL, bsonReader.readBsonType()); + assertEquals("symbol", bsonReader.readSymbol()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testTimestampStrict() { String json = "{ \"$timestamp\" : { \"t\" : 1234, \"i\" : 1 } }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.TIMESTAMP, bsonReader.readBsonType()); - assertEquals(new BsonTimestamp(1234, 1), bsonReader.readTimestamp()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.TIMESTAMP, bsonReader.readBsonType()); + assertEquals(new BsonTimestamp(1234, 1), bsonReader.readTimestamp()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testTimestampStrictWithOutOfOrderFields() { + String json = "{ \"$timestamp\" : { \"i\" : 1, \"t\" : 1234 } }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.TIMESTAMP, bsonReader.readBsonType()); + assertEquals(new BsonTimestamp(1234, 1), bsonReader.readTimestamp()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testTimestampShell() { String json = "Timestamp(1234, 1)"; - bsonReader = new JsonReader(json); - - assertEquals(BsonType.TIMESTAMP, bsonReader.readBsonType()); - assertEquals(new BsonTimestamp(1234, 1), bsonReader.readTimestamp()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.TIMESTAMP, bsonReader.readBsonType()); + assertEquals(new BsonTimestamp(1234, 1), bsonReader.readTimestamp()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testUndefined() { String json = "undefined"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.UNDEFINED, bsonReader.readBsonType()); - bsonReader.readUndefined(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.UNDEFINED, bsonReader.readBsonType()); + bsonReader.readUndefined(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testUndefinedExtended() { String json = "{ \"$undefined\" : true }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.UNDEFINED, bsonReader.readBsonType()); - bsonReader.readUndefined(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - } - - @Test(expected = JsonParseException.class) - public void testUndefinedExtendedInvalid() { - String json = "{ \"$undefined\" : false }"; - bsonReader = new JsonReader(json); - bsonReader.readUndefined(); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.UNDEFINED, bsonReader.readBsonType()); + bsonReader.readUndefined(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test(expected = IllegalStateException.class) public void testClosedState() { - bsonReader = new JsonReader(""); + final AbstractBsonReader bsonReader = new JsonReader(""); bsonReader.close(); bsonReader.readBinaryData(); } - @Test(expected = JsonParseException.class) + @Test public void testEndOfFile0() { String json = "{"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - bsonReader.readStartDocument(); - bsonReader.readBsonType(); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + bsonReader.readStartDocument(); + bsonReader.readBsonType(); + return null; + } + }, JsonParseException.class); } - @Test(expected = JsonParseException.class) + @Test public void testEndOfFile1() { String json = "{ test : "; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); - bsonReader.readStartDocument(); - bsonReader.readBsonType(); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DOCUMENT, bsonReader.readBsonType()); + bsonReader.readStartDocument(); + bsonReader.readBsonType(); + return null; + } + }, JsonParseException.class); } @Test - public void testBinary() { + public void testLegacyBinary() { String json = "{ \"$binary\" : \"AQID\", \"$type\" : \"0\" }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.BINARY, bsonReader.readBsonType()); - BsonBinary binary = bsonReader.readBinaryData(); - assertEquals(0, binary.getType()); - assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); - } - - @Test - public void testUserDefinedBinary() { + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(BsonBinarySubType.BINARY.getValue(), binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testLegacyBinaryWithNumericType() { + String json = "{ \"$binary\" : \"AQID\", \"$type\" : 0 }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(BsonBinarySubType.BINARY.getValue(), binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testLegacyUserDefinedBinary() { String json = "{ \"$binary\" : \"AQID\", \"$type\" : \"80\" }"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.BINARY, bsonReader.readBsonType()); - BsonBinary binary = bsonReader.readBinaryData(); - assertEquals(BsonBinarySubType.USER_DEFINED.getValue(), binary.getType()); - assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(BsonBinarySubType.USER_DEFINED.getValue(), binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testLegacyUserDefinedBinaryWithKeyOrderReversed() { + String json = "{ \"$type\" : \"80\", \"$binary\" : \"AQID\" }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(BsonBinarySubType.USER_DEFINED.getValue(), binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testLegacyUserDefinedBinaryWithNumericType() { + String json = "{ \"$binary\" : \"AQID\", \"$type\" : 128 }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(BsonBinarySubType.USER_DEFINED.getValue(), binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testCanonicalExtendedJsonBinary() { + String json = "{ \"$binary\" : { \"base64\" : \"AQID\", \"subType\" : \"80\" } }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(BsonBinarySubType.USER_DEFINED.getValue(), binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testCanonicalExtendedJsonBinaryWithKeysReversed() { + String json = "{ \"$binary\" : { \"subType\" : \"80\", \"base64\" : \"AQID\" } }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(BsonBinarySubType.USER_DEFINED.getValue(), binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testCanonicalExtendedJsonBinaryWithIncorrectFirstKey() { + String json = "{ \"$binary\" : { \"badKey\" : \"80\", \"base64\" : \"AQID\" } }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + return null; + } + }, JsonParseException.class); } @Test public void testInfinity() { String json = "{ \"value\" : Infinity }"; - bsonReader = new JsonReader(json); - bsonReader.readStartDocument(); - assertEquals(BsonType.DOUBLE, bsonReader.readBsonType()); - bsonReader.readName(); - assertEquals(Double.POSITIVE_INFINITY, bsonReader.readDouble(), 0.0001); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals(BsonType.DOUBLE, bsonReader.readBsonType()); + bsonReader.readName(); + assertEquals(Double.POSITIVE_INFINITY, bsonReader.readDouble(), 0.0001); + return null; + } + }); } @Test public void testNaN() { String json = "{ \"value\" : NaN }"; - bsonReader = new JsonReader(json); - bsonReader.readStartDocument(); - assertEquals(BsonType.DOUBLE, bsonReader.readBsonType()); - bsonReader.readName(); - assertEquals(Double.NaN, bsonReader.readDouble(), 0.0001); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals(BsonType.DOUBLE, bsonReader.readBsonType()); + bsonReader.readName(); + assertEquals(Double.NaN, bsonReader.readDouble(), 0.0001); + return null; + } + }); } @Test public void testBinData() { String json = "{ \"a\" : BinData(3, AQID) }"; - bsonReader = new JsonReader(json); - bsonReader.readStartDocument(); - assertEquals(BsonType.BINARY, bsonReader.readBsonType()); - BsonBinary binary = bsonReader.readBinaryData(); - assertEquals(3, binary.getType()); - assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(3, binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testBinDataUserDefined() { String json = "{ \"a\" : BinData(128, AQID) }"; - bsonReader = new JsonReader(json); - bsonReader.readStartDocument(); - assertEquals(BsonType.BINARY, bsonReader.readBsonType()); - BsonBinary binary = bsonReader.readBinaryData(); - assertEquals(BsonBinarySubType.USER_DEFINED.getValue(), binary.getType()); - assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(BsonBinarySubType.USER_DEFINED.getValue(), binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testBinDataWithNew() { String json = "{ \"a\" : new BinData(3, AQID) }"; - bsonReader = new JsonReader(json); - bsonReader.readStartDocument(); - assertEquals(BsonType.BINARY, bsonReader.readBsonType()); - BsonBinary binary = bsonReader.readBinaryData(); - assertEquals(3, binary.getType()); - assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(3, binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3}, binary.getData()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); + } + + @Test + public void testBinDataQuoted() { + String json = "{ \"a\" : BinData(3, \"AQIDBA==\") }"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + assertEquals(BsonType.BINARY, bsonReader.readBsonType()); + BsonBinary binary = bsonReader.readBinaryData(); + assertEquals(3, binary.getType()); + assertArrayEquals(new byte[]{1, 2, 3, 4}, binary.getData()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateWithNumbers() { String json = "new Date(1988, 06, 13 , 22 , 1)"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(584834460000L, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(584834460000L, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeConstructorWithNew() { String json = "new Date(\"Sat Jul 13 2013 11:10:05 UTC\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertEquals(1373713805000L, bsonReader.readDateTime()); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertEquals(1373713805000L, bsonReader.readDateTime()); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testEmptyDateTimeConstructorWithNew() { - long currentTime = new Date().getTime(); + final long currentTime = new Date().getTime(); String json = "new Date()"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertTrue(bsonReader.readDateTime() >= currentTime); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertTrue(bsonReader.readDateTime() >= currentTime); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeWithOutNew() { - long currentTime = currentTimeWithoutMillis(); + final long currentTime = currentTimeWithoutMillis(); String json = "Date()"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.STRING, bsonReader.readBsonType()); - assertTrue(dateStringToTime(bsonReader.readString()) >= currentTime); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.STRING, bsonReader.readBsonType()); + assertTrue(dateStringToTime(bsonReader.readString()) >= currentTime); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDateTimeWithOutNewContainingJunk() { - long currentTime = currentTimeWithoutMillis(); + final long currentTime = currentTimeWithoutMillis(); String json = "Date({ok: 1}, 1234)"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.STRING, bsonReader.readBsonType()); - assertTrue(dateStringToTime(bsonReader.readString()) >= currentTime); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.STRING, bsonReader.readBsonType()); + assertTrue(dateStringToTime(bsonReader.readString()) >= currentTime); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testEmptyISODateTimeConstructorWithNew() { - long currentTime = new Date().getTime(); + final long currentTime = new Date().getTime(); String json = "new ISODate()"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertTrue(bsonReader.readDateTime() >= currentTime); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertTrue(bsonReader.readDateTime() >= currentTime); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testEmptyISODateTimeConstructor() { - long currentTime = new Date().getTime(); + final long currentTime = new Date().getTime(); String json = "ISODate()"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); - assertTrue(bsonReader.readDateTime() >= currentTime); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DATE_TIME, bsonReader.readBsonType()); + assertTrue(bsonReader.readDateTime() >= currentTime); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testRegExp() { String json = "RegExp(\"abc\",\"im\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); - BsonRegularExpression regularExpression = bsonReader.readRegularExpression(); - assertEquals("abc", regularExpression.getPattern()); - assertEquals("im", regularExpression.getOptions()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); + BsonRegularExpression regularExpression = bsonReader.readRegularExpression(); + assertEquals("abc", regularExpression.getPattern()); + assertEquals("im", regularExpression.getOptions()); + return null; + } + }); } @Test public void testRegExpWithNew() { String json = "new RegExp(\"abc\",\"im\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); - BsonRegularExpression regularExpression = bsonReader.readRegularExpression(); - assertEquals("abc", regularExpression.getPattern()); - assertEquals("im", regularExpression.getOptions()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.REGULAR_EXPRESSION, bsonReader.readBsonType()); + BsonRegularExpression regularExpression = bsonReader.readRegularExpression(); + assertEquals("abc", regularExpression.getPattern()); + assertEquals("im", regularExpression.getOptions()); + return null; + } + }); } @Test public void testSkip() { String json = "{ \"a\" : 2 }"; - bsonReader = new JsonReader(json); - bsonReader.readStartDocument(); - bsonReader.readBsonType(); - bsonReader.skipName(); - bsonReader.skipValue(); - assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); - bsonReader.readEndDocument(); - assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + bsonReader.readBsonType(); + bsonReader.skipName(); + bsonReader.skipValue(); + assertEquals(BsonType.END_OF_DOCUMENT, bsonReader.readBsonType()); + bsonReader.readEndDocument(); + assertEquals(AbstractBsonReader.State.DONE, bsonReader.getState()); + return null; + } + }); } @Test public void testDBPointer() { String json = "DBPointer(\"b\",\"5209296cd6c4e38cf96fffdc\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DB_POINTER, bsonReader.readBsonType()); - BsonDbPointer dbPointer = bsonReader.readDBPointer(); - assertEquals("b", dbPointer.getNamespace()); - assertEquals(new ObjectId("5209296cd6c4e38cf96fffdc"), dbPointer.getId()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DB_POINTER, bsonReader.readBsonType()); + BsonDbPointer dbPointer = bsonReader.readDBPointer(); + assertEquals("b", dbPointer.getNamespace()); + assertEquals(new ObjectId("5209296cd6c4e38cf96fffdc"), dbPointer.getId()); + return null; + } + }); } @Test public void testDBPointerWithNew() { String json = "new DBPointer(\"b\",\"5209296cd6c4e38cf96fffdc\")"; - bsonReader = new JsonReader(json); - assertEquals(BsonType.DB_POINTER, bsonReader.readBsonType()); - BsonDbPointer dbPointer = bsonReader.readDBPointer(); - assertEquals("b", dbPointer.getNamespace()); - assertEquals(new ObjectId("5209296cd6c4e38cf96fffdc"), dbPointer.getId()); + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + assertEquals(BsonType.DB_POINTER, bsonReader.readBsonType()); + BsonDbPointer dbPointer = bsonReader.readDBPointer(); + assertEquals("b", dbPointer.getNamespace()); + assertEquals(new ObjectId("5209296cd6c4e38cf96fffdc"), dbPointer.getId()); + return null; + } + }); + } + + @Test + public void testMultipleMarks() { + String json = "{a : { b : 1 }}"; + testStringAndStream(json, new Function() { + @Override + public Void apply(final AbstractBsonReader bsonReader) { + bsonReader.readStartDocument(); + BsonReaderMark markOne = bsonReader.getMark(); + bsonReader.readName("a"); + bsonReader.readStartDocument(); + BsonReaderMark markTwo = bsonReader.getMark(); + bsonReader.readName("b"); + bsonReader.readInt32(); + bsonReader.readEndDocument(); + markTwo.reset(); + bsonReader.readName("b"); + markOne.reset(); + bsonReader.readName("a"); + return null; + } + }); + } + + @Test + public void testTwoDocuments() { + Reader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream("{a : 1}{b : 1}".getBytes()))); + + JsonReader jsonReader = new JsonReader(reader); + jsonReader.readStartDocument(); + jsonReader.readName("a"); + jsonReader.readInt32(); + jsonReader.readEndDocument(); + + jsonReader = new JsonReader(reader); + jsonReader.readStartDocument(); + jsonReader.readName("b"); + jsonReader.readInt32(); + jsonReader.readEndDocument(); + } + + private void testStringAndStream(final String json, final Function testFunc, + final Class exClass) { + try { + testFunc.apply(new JsonReader(json)); + } catch (final RuntimeException e) { + if (exClass == null) { + throw e; + } + assertEquals(exClass, e.getClass()); + } + try { + testFunc.apply(new JsonReader(new InputStreamReader(new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8"))), + Charset.forName("UTF-8")))); + } catch (final RuntimeException e) { + if (exClass == null) { + throw e; + } + assertEquals(exClass, e.getClass()); + } + } + + private void testStringAndStream(final String json, final Function testFunc) { + testStringAndStream(json, testFunc, null); } private long dateStringToTime(final String date) { diff --git a/bson/src/test/unit/org/bson/json/JsonScannerTest.java b/bson/src/test/unit/org/bson/json/JsonScannerTest.java index 5955c8a0775..290adab453e 100644 --- a/bson/src/test/unit/org/bson/json/JsonScannerTest.java +++ b/bson/src/test/unit/org/bson/json/JsonScannerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ public class JsonScannerTest { @Test public void testEndOfFile() { String json = "\t "; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.END_OF_FILE, token.getType()); @@ -36,7 +36,7 @@ public void testEndOfFile() { @Test public void testBeginObject() { String json = "\t {x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.BEGIN_OBJECT, token.getType()); @@ -47,7 +47,7 @@ public void testBeginObject() { @Test public void testEndObject() { String json = "\t }x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.END_OBJECT, token.getType()); @@ -58,7 +58,7 @@ public void testEndObject() { @Test public void testBeginArray() { String json = "\t [x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.BEGIN_ARRAY, token.getType()); @@ -69,7 +69,7 @@ public void testBeginArray() { @Test public void testEndArray() { String json = "\t ]x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.END_ARRAY, token.getType()); @@ -80,7 +80,7 @@ public void testEndArray() { @Test public void testParentheses() { String json = "\t (jj)x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.LEFT_PAREN, token.getType()); @@ -95,7 +95,7 @@ public void testParentheses() { @Test public void testNameSeparator() { String json = "\t :x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.COLON, token.getType()); @@ -106,7 +106,7 @@ public void testNameSeparator() { @Test public void testValueSeparator() { String json = "\t ,x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.COMMA, token.getType()); @@ -117,7 +117,7 @@ public void testValueSeparator() { @Test public void testEmptyString() { String json = "\t \"\"x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.STRING, token.getType()); @@ -128,7 +128,7 @@ public void testEmptyString() { @Test public void test1CharacterString() { String json = "\t \"1\"x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.STRING, token.getType()); @@ -139,7 +139,7 @@ public void test1CharacterString() { @Test public void test2CharacterString() { String json = "\t \"12\"x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.STRING, token.getType()); @@ -150,7 +150,7 @@ public void test2CharacterString() { @Test public void test3CharacterString() { String json = "\t \"123\"x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.STRING, token.getType()); @@ -161,7 +161,7 @@ public void test3CharacterString() { @Test public void testEscapeSequences() { String json = "\t \"x\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0030y\"x"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.STRING, token.getType()); @@ -173,7 +173,7 @@ public void testEscapeSequences() { @Test public void testTrue() { String json = "\t true,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.UNQUOTED_STRING, token.getType()); @@ -184,7 +184,7 @@ public void testTrue() { @Test public void testMinusInfinity() { String json = "\t -Infinity]"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -195,7 +195,7 @@ public void testMinusInfinity() { @Test public void testFalse() { String json = "\t false,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.UNQUOTED_STRING, token.getType()); @@ -206,7 +206,7 @@ public void testFalse() { @Test public void testNull() { String json = "\t null,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.UNQUOTED_STRING, token.getType()); @@ -217,7 +217,7 @@ public void testNull() { @Test public void testUndefined() { String json = "\t undefined,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.UNQUOTED_STRING, token.getType()); @@ -228,7 +228,7 @@ public void testUndefined() { @Test public void testUnquotedStringWithSeparator() { String json = "\t name123:1"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.UNQUOTED_STRING, token.getType()); @@ -239,7 +239,7 @@ public void testUnquotedStringWithSeparator() { @Test public void testUnquotedString() { String json = "name123"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.UNQUOTED_STRING, token.getType()); @@ -250,7 +250,7 @@ public void testUnquotedString() { @Test public void testZero() { String json = "\t 0,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.INT32, token.getType()); @@ -261,7 +261,7 @@ public void testZero() { @Test public void testMinusZero() { String json = "\t -0,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.INT32, token.getType()); @@ -272,7 +272,7 @@ public void testMinusZero() { @Test public void testOne() { String json = "\t 1,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.INT32, token.getType()); @@ -283,7 +283,7 @@ public void testOne() { @Test public void testMinusOne() { String json = "\t -1,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.INT32, token.getType()); @@ -294,7 +294,7 @@ public void testMinusOne() { @Test public void testTwelve() { String json = "\t 12,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.INT32, token.getType()); @@ -305,7 +305,7 @@ public void testTwelve() { @Test public void testMinusTwelve() { String json = "\t -12,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.INT32, token.getType()); @@ -316,7 +316,7 @@ public void testMinusTwelve() { @Test public void testZeroPointZero() { String json = "\t 0.0,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -327,7 +327,7 @@ public void testZeroPointZero() { @Test public void testMinusZeroPointZero() { String json = "\t -0.0,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -338,7 +338,7 @@ public void testMinusZeroPointZero() { @Test public void testZeroExponentOne() { String json = "\t 0e1,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -349,7 +349,7 @@ public void testZeroExponentOne() { @Test public void testMinusZeroExponentOne() { String json = "\t -0e1,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -360,7 +360,7 @@ public void testMinusZeroExponentOne() { @Test public void testZeroExponentMinusOne() { String json = "\t 0e-1,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -371,7 +371,7 @@ public void testZeroExponentMinusOne() { @Test public void testMinusZeroExponentMinusOne() { String json = "\t -0e-1,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -382,7 +382,7 @@ public void testMinusZeroExponentMinusOne() { @Test public void testOnePointTwo() { String json = "\t 1.2,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -393,7 +393,7 @@ public void testOnePointTwo() { @Test public void testMinusOnePointTwo() { String json = "\t -1.2,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -404,7 +404,7 @@ public void testMinusOnePointTwo() { @Test public void testOneExponentTwelve() { String json = "\t 1e12,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -415,7 +415,7 @@ public void testOneExponentTwelve() { @Test public void testMinusZeroExponentTwelve() { String json = "\t -1e12,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -426,7 +426,7 @@ public void testMinusZeroExponentTwelve() { @Test public void testOneExponentMinuesTwelve() { String json = "\t 1e-12,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -437,7 +437,7 @@ public void testOneExponentMinuesTwelve() { @Test public void testMinusZeroExponentMinusTwelve() { String json = "\t -1e-12,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.DOUBLE, token.getType()); @@ -448,7 +448,7 @@ public void testMinusZeroExponentMinusTwelve() { @Test public void testRegularExpressionEmpty() { String json = "\t //,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.REGULAR_EXPRESSION, token.getType()); @@ -464,7 +464,7 @@ public void testRegularExpressionEmpty() { public void testRegularExpressionPattern() { String json = "\t /pattern/,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.REGULAR_EXPRESSION, token.getType()); @@ -475,7 +475,7 @@ public void testRegularExpressionPattern() { @Test public void testRegularExpressionPatternAndOptions() { String json = "\t /pattern/im,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.REGULAR_EXPRESSION, token.getType()); @@ -489,7 +489,7 @@ public void testRegularExpressionPatternAndOptions() { @Test public void testRegularExpressionPatternAndEscapeSequence() { String json = "\t /patte\\.n/,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); JsonToken token = scanner.nextToken(); assertEquals(JsonTokenType.REGULAR_EXPRESSION, token.getType()); @@ -500,7 +500,7 @@ public void testRegularExpressionPatternAndEscapeSequence() { @Test(expected = JsonParseException.class) public void testInvalidRegularExpression() { String json = "\t /pattern/nsk,"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); scanner.nextToken(); } @@ -508,7 +508,7 @@ public void testInvalidRegularExpression() { @Test(expected = JsonParseException.class) public void testInvalidRegularExpressionNoEnd() { String json = "/b"; - JsonBuffer buffer = new JsonBuffer(json); + JsonBuffer buffer = new JsonStringBuffer(json); JsonScanner scanner = new JsonScanner(buffer); scanner.nextToken(); } diff --git a/bson/src/test/unit/org/bson/json/JsonStreamBufferTest.java b/bson/src/test/unit/org/bson/json/JsonStreamBufferTest.java new file mode 100644 index 00000000000..faa607755d3 --- /dev/null +++ b/bson/src/test/unit/org/bson/json/JsonStreamBufferTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; + +import static org.junit.Assert.assertEquals; + +public class JsonStreamBufferTest { + + @Test + public void testRead() { + JsonStreamBuffer buffer = new JsonStreamBuffer(new InputStreamReader(new ByteArrayInputStream("ABC".getBytes()))); + assertEquals('A', buffer.read()); + assertEquals('B', buffer.read()); + assertEquals('C', buffer.read()); + assertEquals(-1, buffer.read()); + } + + @Test + public void testUnRead() { + JsonStreamBuffer buffer = new JsonStreamBuffer(new InputStreamReader(new ByteArrayInputStream("A".getBytes()))); + buffer.unread(buffer.read()); + assertEquals('A', buffer.read()); + assertEquals(-1, buffer.read()); + } + + @Test + public void testPosition() { + JsonStreamBuffer buffer = new JsonStreamBuffer(new InputStreamReader(new ByteArrayInputStream("ABC".getBytes()))); + + buffer.read(); + buffer.read(); + assertEquals(2, buffer.getPosition()); + } + + @Test(expected = JsonParseException.class) + public void testEOFCheck() { + JsonStreamBuffer buffer = new JsonStreamBuffer(new InputStreamReader(new ByteArrayInputStream("".getBytes()))); + + buffer.read(); + buffer.read(); + } + + @Test + public void testMarkAndReset() { + JsonStreamBuffer buffer = + new JsonStreamBuffer(new InputStreamReader(new ByteArrayInputStream("ABCDEFGHIJKLMNOPQRSTUPWXYZ".getBytes())), 4); + + int pos = buffer.mark(); + assertEquals(0, pos); + assertEquals('A', buffer.read()); + + buffer.reset(pos); + assertEquals('A', buffer.read()); + + pos = buffer.mark(); + buffer.discard(pos); + assertEquals('B', buffer.read()); + + pos = buffer.mark(); + assertEquals(2, pos); + + buffer.read(); + buffer.mark(); + + buffer.read(); + buffer.mark(); + + buffer.reset(pos + 1); + assertEquals(pos + 1, buffer.getPosition()); + assertEquals('D', buffer.read()); + + pos = buffer.mark(); + buffer.read(); + buffer.read(); + buffer.read(); + buffer.read(); + buffer.read(); + + buffer.reset(pos); + assertEquals('E', buffer.read()); + assertEquals('F', buffer.read()); + assertEquals('G', buffer.read()); + assertEquals('H', buffer.read()); + assertEquals('I', buffer.read()); + assertEquals('J', buffer.read()); + } +} diff --git a/bson/src/test/unit/org/bson/json/JsonStringBufferTest.java b/bson/src/test/unit/org/bson/json/JsonStringBufferTest.java new file mode 100644 index 00000000000..8fdab60b12f --- /dev/null +++ b/bson/src/test/unit/org/bson/json/JsonStringBufferTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class JsonStringBufferTest { + + @Test + public void testRead() { + JsonBuffer buffer = new JsonStringBuffer("ABC"); + assertEquals('A', buffer.read()); + assertEquals('B', buffer.read()); + assertEquals('C', buffer.read()); + assertEquals(-1, buffer.read()); + } + + @Test + public void testUnRead() { + JsonStringBuffer buffer = new JsonStringBuffer("A"); + buffer.unread(buffer.read()); + assertEquals('A', buffer.read()); + assertEquals(-1, buffer.read()); + } + + @Test + public void testPosition() { + JsonStringBuffer buffer = new JsonStringBuffer("ABC"); + + buffer.read(); + buffer.read(); + assertEquals(2, buffer.getPosition()); + } + + @Test(expected = JsonParseException.class) + public void testEOFCheck() { + JsonStringBuffer buffer = new JsonStringBuffer(""); + + buffer.read(); + buffer.read(); + } +} diff --git a/bson/src/test/unit/org/bson/json/JsonWriterSettingsSpecification.groovy b/bson/src/test/unit/org/bson/json/JsonWriterSettingsSpecification.groovy new file mode 100644 index 00000000000..f09c134e902 --- /dev/null +++ b/bson/src/test/unit/org/bson/json/JsonWriterSettingsSpecification.groovy @@ -0,0 +1,256 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json + +import spock.lang.Specification + +class JsonWriterSettingsSpecification extends Specification { + + def 'test defaults'() { + when: + def settings = new JsonWriterSettings(); + + then: + !settings.isIndent() + settings.getOutputMode() == JsonMode.STRICT + + when: + settings = JsonWriterSettings.builder().build(); + + then: + !settings.isIndent() + settings.getOutputMode() == JsonMode.RELAXED + settings.getMaxLength() == 0 + } + + + def 'test output mode'() { + when: + def settings = JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build() + + then: + settings.getOutputMode() == JsonMode.SHELL + } + + def 'test indent defaults'() { + when: + def settings = JsonWriterSettings.builder().indent(true).build() + + then: + settings.isIndent() + settings.getIndentCharacters() == ' ' + settings.getNewLineCharacters() == System.getProperty('line.separator') + } + + def 'test indent settings'() { + when: + def settings = JsonWriterSettings.builder() + .indent(true).indentCharacters('\t').newLineCharacters('\r\n').build() + + then: + settings.getIndentCharacters() == '\t' + settings.getNewLineCharacters() == '\r\n' + } + + def 'test max length setting'() { + when: + def settings = JsonWriterSettings.builder() + .maxLength(100).build() + + then: + settings.getMaxLength() == 100 + } + + def 'test constructors'() { + when: + def settings = new JsonWriterSettings() + + then: + !settings.isIndent() + settings.getOutputMode() == JsonMode.STRICT + settings.getMaxLength() == 0 + + when: + settings = new JsonWriterSettings(JsonMode.SHELL) + + then: + settings.getOutputMode() == JsonMode.SHELL + + when: + settings = new JsonWriterSettings(true) + + then: + settings.isIndent() + + when: + settings = new JsonWriterSettings(JsonMode.SHELL, true) + + then: + settings.getOutputMode() == JsonMode.SHELL + settings.isIndent() + + when: + settings = new JsonWriterSettings(JsonMode.SHELL, '\t') + + then: + settings.getOutputMode() == JsonMode.SHELL + settings.isIndent() + settings.getIndentCharacters() == '\t' + + when: + settings = new JsonWriterSettings(JsonMode.SHELL, '\t', '\r') + + then: + settings.getOutputMode() == JsonMode.SHELL + settings.isIndent() + settings.getIndentCharacters() == '\t' + settings.getNewLineCharacters() == '\r' + } + + def 'should use legacy extended json converters for strict mode'() { + when: + def settings = JsonWriterSettings.builder().outputMode(JsonMode.STRICT).build() + + then: + settings.binaryConverter.class == LegacyExtendedJsonBinaryConverter + settings.booleanConverter.class == JsonBooleanConverter + settings.dateTimeConverter.class == LegacyExtendedJsonDateTimeConverter + settings.decimal128Converter.class == ExtendedJsonDecimal128Converter + settings.doubleConverter.class == JsonDoubleConverter + settings.int32Converter.class == JsonInt32Converter + settings.int64Converter.class == ExtendedJsonInt64Converter + settings.javaScriptConverter.class == JsonJavaScriptConverter + settings.maxKeyConverter.class == ExtendedJsonMaxKeyConverter + settings.minKeyConverter.class == ExtendedJsonMinKeyConverter + settings.nullConverter.class == JsonNullConverter + settings.objectIdConverter.class == ExtendedJsonObjectIdConverter + settings.regularExpressionConverter.class == LegacyExtendedJsonRegularExpressionConverter + settings.stringConverter.class == JsonStringConverter + settings.symbolConverter.class == JsonSymbolConverter + settings.timestampConverter.class == ExtendedJsonTimestampConverter + settings.undefinedConverter.class == ExtendedJsonUndefinedConverter + } + + def 'should use extended json converters for extended json mode'() { + when: + def settings = JsonWriterSettings.builder().outputMode(JsonMode.EXTENDED).build() + + then: + settings.binaryConverter.class == ExtendedJsonBinaryConverter + settings.booleanConverter.class == JsonBooleanConverter + settings.dateTimeConverter.class == ExtendedJsonDateTimeConverter + settings.decimal128Converter.class == ExtendedJsonDecimal128Converter + settings.doubleConverter.class == ExtendedJsonDoubleConverter + settings.int32Converter.class == ExtendedJsonInt32Converter + settings.int64Converter.class == ExtendedJsonInt64Converter + settings.javaScriptConverter.class == JsonJavaScriptConverter + settings.maxKeyConverter.class == ExtendedJsonMaxKeyConverter + settings.minKeyConverter.class == ExtendedJsonMinKeyConverter + settings.nullConverter.class == JsonNullConverter + settings.objectIdConverter.class == ExtendedJsonObjectIdConverter + settings.regularExpressionConverter.class == ExtendedJsonRegularExpressionConverter + settings.stringConverter.class == JsonStringConverter + settings.symbolConverter.class == JsonSymbolConverter + settings.timestampConverter.class == ExtendedJsonTimestampConverter + settings.undefinedConverter.class == ExtendedJsonUndefinedConverter + } + + def 'should use shell converters for shell mode'() { + when: + def settings = JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build() + + then: + settings.binaryConverter.class == ShellBinaryConverter + settings.booleanConverter.class == JsonBooleanConverter + settings.dateTimeConverter.class == ShellDateTimeConverter + settings.decimal128Converter.class == ShellDecimal128Converter + settings.doubleConverter.class == JsonDoubleConverter + settings.int32Converter.class == JsonInt32Converter + settings.int64Converter.class == ShellInt64Converter + settings.javaScriptConverter.class == JsonJavaScriptConverter + settings.maxKeyConverter.class == ShellMaxKeyConverter + settings.minKeyConverter.class == ShellMinKeyConverter + settings.nullConverter.class == JsonNullConverter + settings.objectIdConverter.class == ShellObjectIdConverter + settings.regularExpressionConverter.class == ShellRegularExpressionConverter + settings.stringConverter.class == JsonStringConverter + settings.symbolConverter.class == JsonSymbolConverter + settings.timestampConverter.class == ShellTimestampConverter + settings.undefinedConverter.class == ShellUndefinedConverter + } + + def 'should set converters'() { + given: + def binaryConverter = new ShellBinaryConverter() + def booleanConverter = new JsonBooleanConverter() + def dateTimeConverter = new ShellDateTimeConverter() + def decimal128Converter = new ShellDecimal128Converter() + def doubleConverter = new JsonDoubleConverter() + def int32Converter = new JsonInt32Converter() + def int64Converter = new ShellInt64Converter() + def javaScriptConverter = new JsonJavaScriptConverter() + def maxKeyConverter = new ShellMaxKeyConverter() + def minKeyConverter = new ShellMinKeyConverter() + def nullConverter = new JsonNullConverter() + def objectIdConverter = new ShellObjectIdConverter() + def regularExpressionConverter = new ShellRegularExpressionConverter() + def stringConverter = new JsonStringConverter() + def symbolConverter = new JsonSymbolConverter() + def timestampConverter = new ShellTimestampConverter() + def undefinedConverter = new ShellUndefinedConverter() + + when: + def settings = JsonWriterSettings.builder() + .binaryConverter(binaryConverter) + .booleanConverter(booleanConverter) + .dateTimeConverter(dateTimeConverter) + .decimal128Converter(decimal128Converter) + .doubleConverter(doubleConverter) + .int32Converter(int32Converter) + .int64Converter(int64Converter) + .javaScriptConverter(javaScriptConverter) + .maxKeyConverter(maxKeyConverter) + .minKeyConverter(minKeyConverter) + .nullConverter(nullConverter) + .objectIdConverter(objectIdConverter) + .regularExpressionConverter(regularExpressionConverter) + .stringConverter(stringConverter) + .symbolConverter(symbolConverter) + .timestampConverter(timestampConverter) + .undefinedConverter(undefinedConverter) + .build() + + then: + settings.binaryConverter == binaryConverter + settings.booleanConverter == booleanConverter + settings.dateTimeConverter == dateTimeConverter + settings.decimal128Converter == decimal128Converter + settings.doubleConverter == doubleConverter + settings.int32Converter == int32Converter + settings.int64Converter == int64Converter + settings.javaScriptConverter == javaScriptConverter + settings.maxKeyConverter == maxKeyConverter + settings.minKeyConverter == minKeyConverter + settings.nullConverter == nullConverter + settings.objectIdConverter == objectIdConverter + settings.regularExpressionConverter == regularExpressionConverter + settings.stringConverter == stringConverter + settings.symbolConverter == symbolConverter + settings.timestampConverter == timestampConverter + settings.undefinedConverter == undefinedConverter + } +} diff --git a/bson/src/test/unit/org/bson/json/JsonWriterSpecification.groovy b/bson/src/test/unit/org/bson/json/JsonWriterSpecification.groovy new file mode 100644 index 00000000000..c1dafb2306a --- /dev/null +++ b/bson/src/test/unit/org/bson/json/JsonWriterSpecification.groovy @@ -0,0 +1,336 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json + +import org.bson.BsonBinary +import org.bson.BsonDbPointer +import org.bson.BsonDocumentReader +import org.bson.BsonRegularExpression +import org.bson.BsonTimestamp +import org.bson.types.Decimal128 +import org.bson.types.ObjectId +import spock.lang.Specification + +import static org.bson.BsonHelper.documentWithValuesOfEveryType + +class JsonWriterSpecification extends Specification { + + def stringWriter = new StringWriter(); + def writer = new JsonWriter(stringWriter) + def jsonWithValuesOfEveryType = documentWithValuesOfEveryType().toJson(JsonWriterSettings.builder().build()) + + def 'should pipe all types'() { + given: + def reader = new BsonDocumentReader(documentWithValuesOfEveryType()) + + when: + writer.pipe(reader) + + then: + stringWriter.toString() == documentWithValuesOfEveryType().toJson() + } + + def 'should pipe all types with capped length'() { + given: + def reader = new BsonDocumentReader(documentWithValuesOfEveryType()) + def writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().maxLength(maxLength).build()) + + when: + writer.pipe(reader) + + then: + stringWriter.toString() == ((maxLength == 0) ? + jsonWithValuesOfEveryType : jsonWithValuesOfEveryType[0.. { private final T value; private final String expected; - public TestData(final T value, final String expected) { + TestData(final T value, final String expected) { this.value = value; this.expected = expected; } } - @Test - public void testSettings() { - JsonWriterSettings settings = new JsonWriterSettings(); - assertNull(settings.getIndentCharacters()); - assertEquals(System.getProperty("line.separator"), settings.getNewLineCharacters()); - assertFalse(settings.isIndent()); - assertEquals(JsonMode.STRICT, settings.getOutputMode()); - - settings = new JsonWriterSettings(JsonMode.SHELL); - assertNull(settings.getIndentCharacters()); - assertEquals(System.getProperty("line.separator"), settings.getNewLineCharacters()); - assertFalse(settings.isIndent()); - assertEquals(JsonMode.SHELL, settings.getOutputMode()); - - settings = new JsonWriterSettings(true); - assertEquals(" ", settings.getIndentCharacters()); - assertEquals(System.getProperty("line.separator"), settings.getNewLineCharacters()); - assertTrue(settings.isIndent()); - assertEquals(JsonMode.STRICT, settings.getOutputMode()); - - settings = new JsonWriterSettings(JsonMode.SHELL, true); - assertEquals(" ", settings.getIndentCharacters()); - assertEquals(System.getProperty("line.separator"), settings.getNewLineCharacters()); - assertTrue(settings.isIndent()); - assertEquals(JsonMode.SHELL, settings.getOutputMode()); - - settings = new JsonWriterSettings(JsonMode.SHELL, "\t", "\r\n"); - assertEquals("\t", settings.getIndentCharacters()); - assertEquals("\r\n", settings.getNewLineCharacters()); - assertTrue(settings.isIndent()); - assertEquals(JsonMode.SHELL, settings.getOutputMode()); - } - @Test(expected = BsonInvalidOperationException.class) public void shouldThrowExceptionForBooleanWhenWritingBeforeStartingDocument() { writer.writeBoolean("b1", true); @@ -212,77 +177,95 @@ public void shouldThrowAnErrorIfTryingToWriteEndDocumentIntoAJavascriptScope() { public void testEmptyDocument() { writer.writeStartDocument(); writer.writeEndDocument(); - String expected = "{ }"; + String expected = "{}"; assertEquals(expected, stringWriter.toString()); } @Test - public void testSingleString() { + public void testSingleElementDocument() { writer.writeStartDocument(); - writer.writeString("abc", "xyz"); + writer.writeName("s"); + writer.writeString("str"); writer.writeEndDocument(); - String expected = "{ \"abc\" : \"xyz\" }"; + String expected = "{\"s\": \"str\"}"; assertEquals(expected, stringWriter.toString()); } @Test - public void testIndentedEmptyDocument() { - writer = new JsonWriter(stringWriter, new JsonWriterSettings(true)); + public void testTwoElementDocument() { + writer.writeStartDocument(); + writer.writeName("s"); + writer.writeString("str"); + writer.writeName("d"); + writer.writeString("str2"); + writer.writeEndDocument(); + String expected = "{\"s\": \"str\", \"d\": \"str2\"}"; + assertEquals(expected, stringWriter.toString()); + } + + @Test + public void testNestedDocument() { + writer.writeStartDocument(); + writer.writeName("doc"); + writer.writeStartDocument(); + writer.writeName("doc"); writer.writeStartDocument(); + writer.writeName("s"); + writer.writeString("str"); writer.writeEndDocument(); - String expected = "{ }"; + writer.writeEndDocument(); + writer.writeEndDocument(); + String expected = "{\"doc\": {\"doc\": {\"s\": \"str\"}}}"; assertEquals(expected, stringWriter.toString()); } @Test - public void testIndentedOneElement() { - writer = new JsonWriter(stringWriter, new JsonWriterSettings(true)); + public void testSingleString() { writer.writeStartDocument(); - writer.writeString("name", "value"); + writer.writeString("abc", "xyz"); writer.writeEndDocument(); - String expected = String.format("{%n \"name\" : \"value\"%n}"); + String expected = "{\"abc\": \"xyz\"}"; assertEquals(expected, stringWriter.toString()); } + @Test - public void testIndentedTwoElements() { - writer = new JsonWriter(stringWriter, new JsonWriterSettings(true)); + public void testBoolean() { writer.writeStartDocument(); - writer.writeString("a", "x"); - writer.writeString("b", "y"); + writer.writeBoolean("abc", true); writer.writeEndDocument(); - String expected = String.format("{%n \"a\" : \"x\",%n \"b\" : \"y\"%n}"); + String expected = "{\"abc\": true}"; assertEquals(expected, stringWriter.toString()); } @Test public void testDouble() { List> tests = asList(new TestData(0.0, "0.0"), new TestData(0.0005, "5.0E-4"), - new TestData(0.5, "0.5"), new TestData(1.0, "1.0"), - new TestData(1.5, "1.5"), new TestData(1.5E+40, "1.5E40"), - new TestData(1.5E-40, "1.5E-40"), - new TestData(1234567890.1234568E+123, "1.2345678901234568E132"), - new TestData(Double.MAX_VALUE, "1.7976931348623157E308"), - new TestData(Double.MIN_VALUE, "4.9E-324"), - - new TestData(-0.0005, "-5.0E-4"), - new TestData(-0.5, "-0.5"), - new TestData(-1.0, "-1.0"), - new TestData(-1.5, "-1.5"), - new TestData(-1.5E+40, "-1.5E40"), - new TestData(-1.5E-40, "-1.5E-40"), - new TestData(-1234567890.1234568E+123, "-1.2345678901234568E132"), - - new TestData(Double.NaN, "NaN"), - new TestData(Double.NEGATIVE_INFINITY, "-Infinity"), - new TestData(Double.POSITIVE_INFINITY, "Infinity")); + new TestData(0.5, "0.5"), new TestData(1.0, "1.0"), + new TestData(1.5, "1.5"), new TestData(1.5E+40, "1.5E40"), + new TestData(1.5E-40, "1.5E-40"), + new TestData(1234567890.1234568E+123, "1.2345678901234568E132"), + new TestData(Double.MAX_VALUE, "1.7976931348623157E308"), + new TestData(Double.MIN_VALUE, "4.9E-324"), + + new TestData(-0.0005, "-5.0E-4"), + new TestData(-0.5, "-0.5"), + new TestData(-1.0, "-1.0"), + new TestData(-1.5, "-1.5"), + new TestData(-1.5E+40, "-1.5E40"), + new TestData(-1.5E-40, "-1.5E-40"), + new TestData(-1234567890.1234568E+123, "-1.2345678901234568E132"), + + new TestData(Double.NaN, "NaN"), + new TestData(Double.NEGATIVE_INFINITY, "-Infinity"), + new TestData(Double.POSITIVE_INFINITY, "Infinity")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings()); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.EXTENDED).build()); writer.writeStartDocument(); writer.writeDouble("d", cur.value); writer.writeEndDocument(); - String expected = "{ \"d\" : " + cur.expected + " }"; + String expected = "{\"d\": {\"$numberDouble\": \"" + cur.expected + "\"}}"; assertEquals(expected, stringWriter.toString()); } } @@ -290,69 +273,80 @@ public void testDouble() { @Test public void testInt64Shell() { List> tests = asList(new TestData(Long.MIN_VALUE, "NumberLong(\"-9223372036854775808\")"), - new TestData(Integer.MIN_VALUE - 1L, "NumberLong(\"-2147483649\")"), - new TestData(Integer.MIN_VALUE + 0L, "NumberLong(-2147483648)") - , - new TestData(0L, "NumberLong(0)"), - new TestData(Integer.MAX_VALUE + 0L, "NumberLong(2147483647)"), - new TestData(Integer.MAX_VALUE + 1L, "NumberLong(\"2147483648\")"), - new TestData(Long.MAX_VALUE, "NumberLong(\"9223372036854775807\")")); + new TestData(Integer.MIN_VALUE - 1L, "NumberLong(\"-2147483649\")"), + new TestData(Integer.MIN_VALUE + 0L, "NumberLong(-2147483648)"), + new TestData(0L, "NumberLong(0)"), + new TestData(Integer.MAX_VALUE + 0L, "NumberLong(2147483647)"), + new TestData(Integer.MAX_VALUE + 1L, "NumberLong(\"2147483648\")"), + new TestData(Long.MAX_VALUE, "NumberLong(\"9223372036854775807\")")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.SHELL)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); writer.writeStartDocument(); writer.writeInt64("l", cur.value); writer.writeEndDocument(); - String expected = "{ \"l\" : " + cur.expected + " }"; + String expected = "{\"l\": " + cur.expected + "}"; assertEquals(expected, stringWriter.toString()); } } @Test + @SuppressWarnings("deprecation") public void testInt64Strict() { List> tests = asList(new TestData(Long.MIN_VALUE, "-9223372036854775808"), - new TestData(Integer.MIN_VALUE - 1L, "-2147483649"), - new TestData(Integer.MIN_VALUE - 0L, "-2147483648"), - new TestData(0L, "0"), - new TestData(Integer.MAX_VALUE + 0L, "2147483647"), - new TestData(Integer.MAX_VALUE + 1L, "2147483648"), - new TestData(Long.MAX_VALUE, "9223372036854775807")); + new TestData(Integer.MIN_VALUE - 1L, "-2147483649"), + new TestData(Integer.MIN_VALUE - 0L, "-2147483648"), + new TestData(0L, "0"), + new TestData(Integer.MAX_VALUE + 0L, "2147483647"), + new TestData(Integer.MAX_VALUE + 1L, "2147483648"), + new TestData(Long.MAX_VALUE, "9223372036854775807")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.STRICT)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.STRICT).build()); writer.writeStartDocument(); writer.writeInt64("l", cur.value); writer.writeEndDocument(); - String expected = "{ \"l\" : { \"$numberLong\" : \"" + cur.expected + "\" } }"; + String expected = "{\"l\": {\"$numberLong\": \"" + cur.expected + "\"}}"; assertEquals(expected, stringWriter.toString()); } } @Test - public void testEmbeddedDocument() { - writer.writeStartDocument(); - writer.writeStartDocument("doc"); - writer.writeInt32("a", 1); - writer.writeInt32("b", 2); - writer.writeEndDocument(); - writer.writeEndDocument(); - String expected = "{ \"doc\" : { \"a\" : 1, \"b\" : 2 } }"; - assertEquals(expected, stringWriter.toString()); - } + public void testDecimal128SShell() { + List> tests = asList( + new TestData(Decimal128.parse("1.0"), "1.0"), + new TestData(Decimal128.POSITIVE_INFINITY, Decimal128.POSITIVE_INFINITY.toString())); + + for (final TestData cur : tests) { + stringWriter = new StringWriter(); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); + writer.writeStartDocument(); + writer.writeDecimal128("d", cur.value); + writer.writeEndDocument(); + String expected = "{\"d\": NumberDecimal(\"" + cur.expected + "\")}"; + assertEquals(expected, stringWriter.toString()); + } + } @Test - public void testIndentedEmbeddedDocument() { - writer = new JsonWriter(stringWriter, new JsonWriterSettings(true)); - writer.writeStartDocument(); - writer.writeStartDocument("doc"); - writer.writeInt32("a", 1); - writer.writeInt32("b", 2); - writer.writeEndDocument(); - writer.writeEndDocument(); - String expected = String.format("{%n \"doc\" : {%n \"a\" : 1,%n \"b\" : 2%n }%n}"); - assertEquals(expected, stringWriter.toString()); + @SuppressWarnings("deprecation") + public void testDecimal128Strict() { + List> tests = asList( + new TestData(Decimal128.parse("1.0"), "1.0"), + new TestData(Decimal128.POSITIVE_INFINITY, Decimal128.POSITIVE_INFINITY.toString())); + + + for (final TestData cur : tests) { + stringWriter = new StringWriter(); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.STRICT).build()); + writer.writeStartDocument(); + writer.writeDecimal128("d", cur.value); + writer.writeEndDocument(); + String expected = "{\"d\": {\"$numberDecimal\": \"" + cur.expected + "\"}}"; + assertEquals(expected, stringWriter.toString()); + } } @Test @@ -364,34 +358,35 @@ public void testArray() { writer.writeInt32(3); writer.writeEndArray(); writer.writeEndDocument(); - String expected = "{ \"array\" : [1, 2, 3] }"; + String expected = "{\"array\": [1, 2, 3]}"; assertEquals(expected, stringWriter.toString()); } @Test + @SuppressWarnings("deprecation") public void testBinaryStrict() { List> tests = asList(new TestData(new BsonBinary(new byte[0]), - "{ \"$binary\" : \"\", " - + "\"$type\" : \"0\" }"), - new TestData(new BsonBinary(new byte[]{1}), - "{ \"$binary\" : \"AQ==\", " - + "\"$type\" : \"0\" }"), - new TestData(new BsonBinary(new byte[]{1, 2}), - "{ \"$binary\" : \"AQI=\", " - + "\"$type\" : \"0\" }"), - new TestData(new BsonBinary(new byte[]{1, 2, 3}), - "{ \"$binary\" : \"AQID\", " - + "\"$type\" : \"0\" }"), - new TestData(new BsonBinary((byte) 0x80, new byte[]{1, 2, 3}), - "{ \"$binary\" : \"AQID\", " - + "\"$type\" : \"80\" }")); + "{\"$binary\": \"\", " + + "\"$type\": \"00\"}"), + new TestData(new BsonBinary(new byte[]{1}), + "{\"$binary\": \"AQ==\", " + + "\"$type\": \"00\"}"), + new TestData(new BsonBinary(new byte[]{1, 2}), + "{\"$binary\": \"AQI=\", " + + "\"$type\": \"00\"}"), + new TestData(new BsonBinary(new byte[]{1, 2, 3}), + "{\"$binary\": \"AQID\", " + + "\"$type\": \"00\"}"), + new TestData(new BsonBinary((byte) 0x80, new byte[]{1, 2, 3}), + "{\"$binary\": \"AQID\", " + + "\"$type\": \"80\"}")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.STRICT)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.STRICT).build()); writer.writeStartDocument(); writer.writeBinaryData("binary", cur.value); writer.writeEndDocument(); - String expected = "{ \"binary\" : " + cur.expected + " }"; + String expected = "{\"binary\": " + cur.expected + "}"; assertEquals(expected, stringWriter.toString()); } } @@ -399,34 +394,35 @@ public void testBinaryStrict() { @Test public void testBinaryShell() { List> tests = asList(new TestData(new BsonBinary(new byte[0]), "new BinData(0, \"\")"), - new TestData(new BsonBinary(new byte[]{1}), "new BinData(0, \"AQ==\")"), - new TestData(new BsonBinary(new byte[]{1, 2}), "new BinData(0, \"AQI=\")"), - new TestData(new BsonBinary(new byte[]{1, 2, 3}), "new BinData(0, \"AQID\")"), - new TestData(new BsonBinary((byte) 0x80, new byte[]{1, 2, 3}), - "new BinData(128, \"AQID\")")); + new TestData(new BsonBinary(new byte[]{1}), "new BinData(0, \"AQ==\")"), + new TestData(new BsonBinary(new byte[]{1, 2}), "new BinData(0, \"AQI=\")"), + new TestData(new BsonBinary(new byte[]{1, 2, 3}), "new BinData(0, \"AQID\")"), + new TestData(new BsonBinary((byte) 0x80, new byte[]{1, 2, 3}), + "new BinData(128, \"AQID\")")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.SHELL)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); writer.writeStartDocument(); writer.writeBinaryData("binary", cur.value); writer.writeEndDocument(); - String expected = "{ \"binary\" : " + cur.expected + " }"; + String expected = "{\"binary\": " + cur.expected + "}"; assertEquals(expected, stringWriter.toString()); } } @Test + @SuppressWarnings("deprecation") public void testDateTimeStrict() { - List> tests = asList(new TestData(new Date(0), "{ \"$date\" : 0 }"), - new TestData(new Date(Long.MAX_VALUE), "{ \"$date\" : 9223372036854775807 }"), - new TestData(new Date(Long.MIN_VALUE), "{ \"$date\" : -9223372036854775808 }")); + List> tests = asList(new TestData(new Date(0), "{\"$date\": 0}"), + new TestData(new Date(Long.MAX_VALUE), "{\"$date\": 9223372036854775807}"), + new TestData(new Date(Long.MIN_VALUE), "{\"$date\": -9223372036854775808}")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.STRICT)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.STRICT).build()); writer.writeStartDocument(); writer.writeDateTime("date", cur.value.getTime()); writer.writeEndDocument(); - String expected = "{ \"date\" : " + cur.expected + " }"; + String expected = "{\"date\": " + cur.expected + "}"; assertEquals(expected, stringWriter.toString()); } } @@ -434,17 +430,17 @@ public void testDateTimeStrict() { @Test public void testDateTimeShell() { List> tests = asList(new TestData(new Date(0), "ISODate(\"1970-01-01T00:00:00.000Z\")"), - new TestData(new Date(1), "ISODate(\"1970-01-01T00:00:00.001Z\")"), - new TestData(new Date(-1), "ISODate(\"1969-12-31T23:59:59.999Z\")"), - new TestData(new Date(Long.MAX_VALUE), "new Date(9223372036854775807)"), - new TestData(new Date(Long.MIN_VALUE), "new Date(-9223372036854775808)")); + new TestData(new Date(1), "ISODate(\"1970-01-01T00:00:00.001Z\")"), + new TestData(new Date(-1), "ISODate(\"1969-12-31T23:59:59.999Z\")"), + new TestData(new Date(Long.MAX_VALUE), "new Date(9223372036854775807)"), + new TestData(new Date(Long.MIN_VALUE), "new Date(-9223372036854775808)")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.SHELL)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); writer.writeStartDocument(); writer.writeDateTime("date", cur.value.getTime()); writer.writeEndDocument(); - String expected = "{ \"date\" : " + cur.expected + " }"; + String expected = "{\"date\": " + cur.expected + "}"; assertEquals(expected, stringWriter.toString()); } } @@ -454,7 +450,7 @@ public void testJavaScript() { writer.writeStartDocument(); writer.writeJavaScript("f", "function f() { return 1; }"); writer.writeEndDocument(); - String expected = "{ \"f\" : { \"$code\" : \"function f() { return 1; }\" } }"; + String expected = "{\"f\": {\"$code\": \"function f() { return 1; }\"}}"; assertEquals(expected, stringWriter.toString()); } @@ -467,25 +463,46 @@ public void testJavaScriptWithScope() { writer.writeEndDocument(); writer.writeEndDocument(); String expected = - "{ \"f\" : { \"$code\" : \"function f() { return n; }\", " + "\"$scope\" : { \"n\" : 1 } } }"; + "{\"f\": {\"$code\": \"function f() { return n; }\", " + "\"$scope\": {\"n\": 1}}}"; + assertEquals(expected, stringWriter.toString()); + } + + @Test + public void testMaxKeyStrict() { + writer.writeStartDocument(); + writer.writeMaxKey("maxkey"); + writer.writeEndDocument(); + String expected = "{\"maxkey\": {\"$maxKey\": 1}}"; assertEquals(expected, stringWriter.toString()); } @Test - public void testMaxKey() { + public void testMinKeyStrict() { + writer.writeStartDocument(); + writer.writeMinKey("minkey"); + writer.writeEndDocument(); + String expected = "{\"minkey\": {\"$minKey\": 1}}"; + assertEquals(expected, stringWriter.toString()); + } + + + @Test + public void testMaxKeyShell() { + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); writer.writeStartDocument(); writer.writeMaxKey("maxkey"); writer.writeEndDocument(); - String expected = "{ \"maxkey\" : { \"$maxKey\" : 1 } }"; + String expected = "{\"maxkey\": MaxKey}"; assertEquals(expected, stringWriter.toString()); } @Test - public void testMinKey() { + public void testMinKeyShell() { + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); writer.writeStartDocument(); writer.writeMinKey("minkey"); writer.writeEndDocument(); - String expected = "{ \"minkey\" : { \"$minKey\" : 1 } }"; + String expected = "{\"minkey\": MinKey}"; assertEquals(expected, stringWriter.toString()); } @@ -494,20 +511,20 @@ public void testNull() { writer.writeStartDocument(); writer.writeNull("null"); writer.writeEndDocument(); - String expected = "{ \"null\" : null }"; + String expected = "{\"null\": null}"; assertEquals(expected, stringWriter.toString()); } @Test public void testObjectIdShell() { - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.SHELL)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); ObjectId objectId = new ObjectId("4d0ce088e447ad08b4721a37"); writer.writeStartDocument(); writer.writeObjectId("_id", objectId); writer.writeEndDocument(); - String expected = "{ \"_id\" : ObjectId(\"4d0ce088e447ad08b4721a37\") }"; + String expected = "{\"_id\": ObjectId(\"4d0ce088e447ad08b4721a37\")}"; assertEquals(expected, stringWriter.toString()); } @@ -519,7 +536,7 @@ public void testObjectIdStrict() { writer.writeObjectId("_id", objectId); writer.writeEndDocument(); - String expected = "{ \"_id\" : { \"$oid\" : \"4d0ce088e447ad08b4721a37\" } }"; + String expected = "{\"_id\": {\"$oid\": \"4d0ce088e447ad08b4721a37\"}}"; assertEquals(expected, stringWriter.toString()); } @@ -527,88 +544,64 @@ public void testObjectIdStrict() { public void testRegularExpressionShell() { List> tests; tests = asList(new TestData(new BsonRegularExpression(""), "/(?:)/"), - new TestData(new BsonRegularExpression("a"), "/a/"), - new TestData(new BsonRegularExpression("a/b"), "/a\\/b/"), - new TestData(new BsonRegularExpression("a\\b"), "/a\\b/"), - new TestData(new BsonRegularExpression("a", "i"), "/a/i"), - new TestData(new BsonRegularExpression("a", "m"), "/a/m"), - new TestData(new BsonRegularExpression("a", "x"), "/a/x"), - new TestData(new BsonRegularExpression("a", "s"), "/a/s"), - new TestData(new BsonRegularExpression("a", "imxs"), "/a/imxs")); + new TestData(new BsonRegularExpression("a"), "/a/"), + new TestData(new BsonRegularExpression("a/b"), "/a\\/b/"), + new TestData(new BsonRegularExpression("a\\b"), "/a\\b/"), + new TestData(new BsonRegularExpression("a", "i"), "/a/i"), + new TestData(new BsonRegularExpression("a", "m"), "/a/m"), + new TestData(new BsonRegularExpression("a", "x"), "/a/x"), + new TestData(new BsonRegularExpression("a", "s"), "/a/s"), + new TestData(new BsonRegularExpression("a", "imxs"), "/a/imsx")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.SHELL)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); writer.writeStartDocument(); writer.writeRegularExpression("regex", cur.value); writer.writeEndDocument(); - String expected = "{ \"regex\" : " + cur.expected + " }"; + String expected = "{\"regex\": " + cur.expected + "}"; assertEquals(expected, stringWriter.toString()); } } @Test + @SuppressWarnings("deprecation") public void testRegularExpressionStrict() { List> tests; - tests = asList(new TestData(new BsonRegularExpression(""), "{ \"$regex\" : \"\", " - + "\"$options\" : \"\" " - + "}"), - new TestData(new BsonRegularExpression("a"), "{ \"$regex\" : \"a\"," - + " \"$options\" : \"\" " - + "}"), - new TestData(new BsonRegularExpression("a/b"), "{ \"$regex\" : " - + "\"a/b\", " - + "\"$options\" : \"\" " - + "}"), - new TestData(new BsonRegularExpression("a\\b"), "{ \"$regex\" : " - + "\"a\\\\b\", " - + "\"$options\" : \"\" " - + "}"), - new TestData(new BsonRegularExpression("a", "i"), "{ \"$regex\" : \"a\"," - + " \"$options\" : \"i\"" - + " }"), - new TestData(new BsonRegularExpression("a", "m"), "{ \"$regex\" : \"a\"," - + " \"$options\" : \"m\"" - + " }"), - new TestData(new BsonRegularExpression("a", "x"), "{ \"$regex\" : \"a\"," - + " \"$options\" : \"x\"" - + " }"), - new TestData(new BsonRegularExpression("a", "s"), "{ \"$regex\" : \"a\"," - + " \"$options\" : \"s\"" - + " }"), - new TestData(new BsonRegularExpression("a", "imxs"), - "{ \"$regex\" : \"a\"," + " \"$options\" : \"imxs\" }")); + tests = asList(new TestData(new BsonRegularExpression(""), "{\"$regex\": \"\", " + + "\"$options\": \"\"" + + "}"), + new TestData(new BsonRegularExpression("a"), "{\"$regex\": \"a\", " + + "\"$options\": \"\"" + + "}"), + new TestData(new BsonRegularExpression("a/b"), "{\"$regex\": " + + "\"a/b\", " + + "\"$options\": \"\"" + + "}"), + new TestData(new BsonRegularExpression("a\\b"), "{\"$regex\": " + + "\"a\\\\b\", " + + "\"$options\": \"\"" + + "}"), + new TestData(new BsonRegularExpression("a", "i"), "{\"$regex\": \"a\", " + + "\"$options\": \"i\"" + + "}"), + new TestData(new BsonRegularExpression("a", "m"), "{\"$regex\": \"a\", " + + "\"$options\": \"m\"" + + "}"), + new TestData(new BsonRegularExpression("a", "x"), "{\"$regex\": \"a\", " + + "\"$options\": \"x\"" + + "}"), + new TestData(new BsonRegularExpression("a", "s"), "{\"$regex\": \"a\", " + + "\"$options\": \"s\"" + + "}"), + new TestData(new BsonRegularExpression("a", "imxs"), + "{\"$regex\": \"a\", " + "\"$options\": \"imsx\"}")); for (final TestData cur : tests) { stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.STRICT)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.STRICT).build()); writer.writeStartDocument(); writer.writeRegularExpression("regex", cur.value); writer.writeEndDocument(); - String expected = "{ \"regex\" : " + cur.expected + " }"; - assertEquals(expected, stringWriter.toString()); - } - } - - @Test - public void testString() { - List> tests; - tests = asList(new TestData("", "\"\""), new TestData(" ", "\" \""), - new TestData("a", "\"a\""), new TestData("ab", "\"ab\""), - new TestData("abc", "\"abc\""), - new TestData("abc\u0000def", "\"abc\\u0000def\""), - new TestData("\\", "\"\\\\\""), new TestData("\'", "\"'\""), - new TestData("\"", "\"\\\"\""), new TestData("\0", "\"\\u0000\""), - new TestData("\b", "\"\\b\""), new TestData("\f", "\"\\f\""), - new TestData("\n", "\"\\n\""), new TestData("\r", "\"\\r\""), - new TestData("\t", "\"\\t\""), new TestData("\u0080", "\"\\u0080\""), - new TestData("\u0080\u0081", "\"\\u0080\\u0081\""), - new TestData("\u0080\u0081\u0082", "\"\\u0080\\u0081\\u0082\"")); - for (final TestData cur : tests) { - stringWriter = new StringWriter(); - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.STRICT)); - writer.writeStartDocument(); - writer.writeString("str", cur.value); - writer.writeEndDocument(); - String expected = "{ \"str\" : " + cur.expected + " }"; + String expected = "{\"regex\": " + cur.expected + "}"; assertEquals(expected, stringWriter.toString()); } } @@ -618,7 +611,7 @@ public void testSymbol() { writer.writeStartDocument(); writer.writeSymbol("symbol", "name"); writer.writeEndDocument(); - String expected = "{ \"symbol\" : { \"$symbol\" : \"name\" } }"; + String expected = "{\"symbol\": {\"$symbol\": \"name\"}}"; assertEquals(expected, stringWriter.toString()); } @@ -628,37 +621,38 @@ public void testTimestampStrict() { writer.writeStartDocument(); writer.writeTimestamp("timestamp", new BsonTimestamp(1000, 1)); writer.writeEndDocument(); - String expected = "{ \"timestamp\" : { \"$timestamp\" : { \"t\" : 1000, \"i\" : 1 } } }"; + String expected = "{\"timestamp\": {\"$timestamp\": {\"t\": 1000, \"i\": 1}}}"; assertEquals(expected, stringWriter.toString()); } @Test public void testTimestampShell() { - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.SHELL)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); writer.writeStartDocument(); writer.writeTimestamp("timestamp", new BsonTimestamp(1000, 1)); writer.writeEndDocument(); - String expected = "{ \"timestamp\" : Timestamp(1000, 1) }"; + String expected = "{\"timestamp\": Timestamp(1000, 1)}"; assertEquals(expected, stringWriter.toString()); } @Test + @SuppressWarnings("deprecation") public void testUndefinedStrict() { - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.STRICT)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.STRICT).build()); writer.writeStartDocument(); writer.writeUndefined("undefined"); writer.writeEndDocument(); - String expected = "{ \"undefined\" : { \"$undefined\" : true } }"; + String expected = "{\"undefined\": {\"$undefined\": true}}"; assertEquals(expected, stringWriter.toString()); } @Test public void testUndefinedShell() { - writer = new JsonWriter(stringWriter, new JsonWriterSettings(JsonMode.SHELL)); + writer = new JsonWriter(stringWriter, JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()); writer.writeStartDocument(); writer.writeUndefined("undefined"); writer.writeEndDocument(); - String expected = "{ \"undefined\" : undefined }"; + String expected = "{\"undefined\": undefined}"; assertEquals(expected, stringWriter.toString()); } @@ -667,7 +661,7 @@ public void testDBPointer() { writer.writeStartDocument(); writer.writeDBPointer("dbPointer", new BsonDbPointer("my.test", new ObjectId("4d0ce088e447ad08b4721a37"))); writer.writeEndDocument(); - String expected = "{ \"dbPointer\" : { \"$ref\" : \"my.test\", \"$id\" : { \"$oid\" : \"4d0ce088e447ad08b4721a37\" } } }"; + String expected = "{\"dbPointer\": {\"$ref\": \"my.test\", \"$id\": {\"$oid\": \"4d0ce088e447ad08b4721a37\"}}}"; assertEquals(expected, stringWriter.toString()); } } diff --git a/bson/src/test/unit/org/bson/json/StrictCharacterStreamJsonWriterSpecification.groovy b/bson/src/test/unit/org/bson/json/StrictCharacterStreamJsonWriterSpecification.groovy new file mode 100644 index 00000000000..8a3d16036f3 --- /dev/null +++ b/bson/src/test/unit/org/bson/json/StrictCharacterStreamJsonWriterSpecification.groovy @@ -0,0 +1,569 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bson.json + +import org.bson.BsonInvalidOperationException +import spock.lang.Specification + +import static java.lang.String.format + +class StrictCharacterStreamJsonWriterSpecification extends Specification { + + private StringWriter stringWriter + private StrictCharacterStreamJsonWriter writer + + def setup() { + stringWriter = new StringWriter() + writer = new StrictCharacterStreamJsonWriter(stringWriter, StrictCharacterStreamJsonWriterSettings.builder().build()) + } + + def 'should write empty document'() { + when: + writer.writeStartObject() + writer.writeEndObject() + + then: + stringWriter.toString() == '{}' + } + + def 'should write empty array'() { + when: + writer.writeStartArray() + writer.writeEndArray() + + then: + stringWriter.toString() == '[]' + } + + def 'should write null'() { + when: + writer.writeStartObject() + writer.writeNull('n') + writer.writeEndObject() + + then: + stringWriter.toString() == '{"n": null}' + } + + def 'should write boolean'() { + when: + writer.writeStartObject() + writer.writeBoolean('b1', true) + writer.writeEndObject() + + then: + stringWriter.toString() == '{"b1": true}' + } + + def 'should write number'() { + when: + writer.writeStartObject() + writer.writeNumber('n', '42') + writer.writeEndObject() + + then: + stringWriter.toString() == '{"n": 42}' + } + + def 'should write string'() { + when: + writer.writeStartObject() + writer.writeString('n', '42') + writer.writeEndObject() + + then: + stringWriter.toString() == '{"n": "42"}' + } + + def 'should write unquoted string'() { + when: + writer.writeStartObject() + writer.writeRaw('s', 'NumberDecimal("42.0")') + writer.writeEndObject() + + then: + stringWriter.toString() == '{"s": NumberDecimal("42.0")}' + } + + def 'should write document'() { + when: + writer.writeStartObject() + writer.writeStartObject('d') + writer.writeEndObject() + writer.writeEndObject() + + then: + stringWriter.toString() == '{"d": {}}' + } + + def 'should write array'() { + when: + writer.writeStartObject() + writer.writeStartArray('a') + writer.writeEndArray() + writer.writeEndObject() + + then: + stringWriter.toString() == '{"a": []}' + } + + def 'should write array of values'() { + when: + writer.writeStartObject() + writer.writeStartArray('a') + writer.writeNumber('1') + writer.writeNull() + writer.writeString('str') + writer.writeEndArray() + writer.writeEndObject() + + then: + stringWriter.toString() == '{"a": [1, null, "str"]}' + } + + def 'should write strings'() { + when: + writer.writeStartObject() + writer.writeString('str', value) + writer.writeEndObject() + + then: + stringWriter.toString() == '{"str": ' + expected + '}' + + where: + value | expected + '' | '""' + ' ' | '" "' + 'a' | '"a"' + 'ab' | '"ab"' + 'abc' | '"abc"' + 'abc\u0000def' | '"abc\\u0000def"' + '\\' | '"\\\\"' + '\'' | '"\'"' + '"' | '"\\""' + '\0' | '"\\u0000"' + '\b' | '"\\b"' + '\f' | '"\\f"' + '\n' | '"\\n"' + '\r' | '"\\r"' + '\t' | '"\\t"' + '\u0080' | '"\\u0080"' + '\u0080\u0081' | '"\\u0080\\u0081"' + '\u0080\u0081\u0082' | '"\\u0080\\u0081\\u0082"' + } + + def 'should write two object elements'() { + when: + writer.writeStartObject() + writer.writeBoolean('b1', true) + writer.writeBoolean('b2', false) + writer.writeEndObject() + + then: + stringWriter.toString() == '{"b1": true, "b2": false}' + } + + def 'should indent one element'() { + given: + writer = new StrictCharacterStreamJsonWriter(stringWriter, StrictCharacterStreamJsonWriterSettings.builder().indent(true).build()) + + when: + writer.writeStartObject() + writer.writeString('name', 'value') + writer.writeEndObject() + + then: + stringWriter.toString() == format('{%n "name": "value"%n}') + } + + def 'should indent one element with indent and newline characters'() { + given: + writer = new StrictCharacterStreamJsonWriter(stringWriter, StrictCharacterStreamJsonWriterSettings.builder() + .indent(true) + .indentCharacters('\t') + .newLineCharacters('\r') + .build()) + + when: + writer.writeStartObject() + writer.writeString('name', 'value') + writer.writeEndObject() + + then: + stringWriter.toString() == format('{\r\t"name": "value"\r}') + } + + def 'should indent two elements'() { + given: + writer = new StrictCharacterStreamJsonWriter(stringWriter, StrictCharacterStreamJsonWriterSettings.builder().indent(true).build()) + + when: + writer.writeStartObject() + writer.writeString('a', 'x') + writer.writeString('b', 'y') + writer.writeEndObject() + + then: + stringWriter.toString() == format('{%n "a": "x",%n "b": "y"%n}') + } + + def 'should indent two array elements'() { + given: + writer = new StrictCharacterStreamJsonWriter(stringWriter, StrictCharacterStreamJsonWriterSettings.builder().indent(true).build()) + + when: + writer.writeStartObject() + writer.writeStartArray('a') + writer.writeNull() + writer.writeNumber('4') + writer.writeEndArray() + writer.writeEndObject() + + then: + stringWriter.toString() == format('{%n "a": [%n null,%n 4%n ]%n}') + } + + def 'should indent two document elements'() { + given: + writer = new StrictCharacterStreamJsonWriter(stringWriter, StrictCharacterStreamJsonWriterSettings.builder().indent(true).build()) + + when: + writer.writeStartObject() + writer.writeStartArray('a') + writer.writeStartObject() + writer.writeNull('a') + writer.writeEndObject() + writer.writeStartObject() + writer.writeNull('a') + writer.writeEndObject() + writer.writeEndArray() + writer.writeEndObject() + + then: + stringWriter.toString() == format('{%n "a": [%n {%n "a": null%n },%n {%n "a": null%n }%n ]%n}') + } + + def 'should indent embedded document'() { + given: + writer = new StrictCharacterStreamJsonWriter(stringWriter, StrictCharacterStreamJsonWriterSettings.builder().indent(true).build()) + + when: + writer.writeStartObject() + writer.writeStartObject('doc') + writer.writeNumber('a', '1') + writer.writeNumber('b', '2') + writer.writeEndObject() + writer.writeEndObject() + + then: + stringWriter.toString() == format('{%n "doc": {%n "a": 1,%n "b": 2%n }%n}') + } + + def shouldThrowExceptionForBooleanWhenWritingBeforeStartingDocument() { + when: + writer.writeBoolean('b1', true) + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowExceptionForNameWhenWritingBeforeStartingDocument() { + when: + writer.writeName('name') + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowExceptionForStringWhenStateIsValue() { + given: + writer.writeStartObject() + + when: + writer.writeString('SomeString') + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowExceptionWhenEndingAnArrayWhenStateIsValue() { + given: + writer.writeStartObject() + + when: + writer.writeEndArray() + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowExceptionWhenWritingASecondName() { + given: + writer.writeStartObject() + writer.writeName('f1') + + when: + writer.writeName('i2') + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowExceptionWhenEndingADocumentBeforeValueIsWritten() { + given: + writer.writeStartObject() + writer.writeName('f1') + + when: + writer.writeEndObject() + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenTryingToWriteAValue() { + when: + writer.writeString('i2') + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenWritingANameInAnArray() { + given: + writer.writeStartObject() + writer.writeStartArray('f2') + + when: + writer.writeName('i3') + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenEndingDocumentInTheMiddleOfWritingAnArray() { + given: + writer.writeStartObject() + writer.writeStartArray('f2') + + when: + writer.writeEndObject() + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenEndingAnArrayInASubDocument() { + given: + writer.writeStartObject() + writer.writeStartArray('f2') + writer.writeStartObject() + + when: + writer.writeEndArray() + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenEndingAnArrayWhenValueIsExpected() { + given: + writer.writeStartObject() + writer.writeName('a') + + when: + writer.writeEndArray() + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenWritingANameInAnArrayEvenWhenSubDocumentExistsInArray() { + given: + writer.writeStartObject() + writer.writeStartArray('f2') + writer.writeStartObject() + writer.writeEndObject() + + when: + writer.writeName('i3') + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenStartingAnObjectWhenDone() { + given: + writer.writeStartObject() + writer.writeEndObject() + + when: + writer.writeStartObject() + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenStartingAnObjectWhenNameIsExpected() { + given: + writer.writeStartObject() + + when: + writer.writeStartObject() + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenAttemptingToEndAnArrayThatWasNotStarted() { + given: + writer.writeStartObject() + writer.writeStartArray('f2') + writer.writeEndArray() + + when: + writer.writeEndArray() + + then: + thrown(BsonInvalidOperationException) + } + + def shouldThrowAnExceptionWhenWritingNullName() { + given: + writer.writeStartObject() + + when: + writer.writeName(null) + + then: + thrown(IllegalArgumentException) + } + + def shouldThrowAnExceptionWhenWritingNullValue() { + given: + writer.writeStartObject() + writer.writeName('v') + + when: + writer.writeNumber(null) + + then: + thrown(IllegalArgumentException) + + when: + writer.writeString(null) + + then: + thrown(IllegalArgumentException) + + when: + writer.writeRaw(null) + + then: + thrown(IllegalArgumentException) + } + + def shouldThrowAnExceptionWhenWritingNullMemberValue() { + given: + writer.writeStartObject() + + when: + writer.writeNumber('v', null) + + then: + thrown(IllegalArgumentException) + + when: + writer.writeString('v', null) + + then: + thrown(IllegalArgumentException) + + when: + writer.writeRaw('v', null) + + then: + thrown(IllegalArgumentException) + } + + def shouldThrowAnExceptionWhenWritingNullMemberName() { + given: + writer.writeStartObject() + + when: + writer.writeNumber(null, '1') + + then: + thrown(IllegalArgumentException) + + when: + writer.writeString(null, 'str') + + then: + thrown(IllegalArgumentException) + + when: + writer.writeRaw(null, 'raw') + + then: + thrown(IllegalArgumentException) + + when: + writer.writeBoolean(null, true) + + then: + thrown(IllegalArgumentException) + + when: + writer.writeNull(null) + + then: + thrown(IllegalArgumentException) + + when: + writer.writeStartObject(null) + + then: + thrown(IllegalArgumentException) + + when: + writer.writeStartArray(null) + + then: + thrown(IllegalArgumentException) + } + + def shouldStopAtMaxLength() { + given: + def fullJsonText = '{"n": null}' + writer = new StrictCharacterStreamJsonWriter(stringWriter, + StrictCharacterStreamJsonWriterSettings.builder().maxLength(maxLength).build()) + + when: + writer.writeStartObject() + writer.writeNull('n') + writer.writeEndObject() + + then: + stringWriter.toString() == fullJsonText[0.. defaultList = ['a', 'b', 'c'] + + then: + doc.getList('y', String).get(0) == 'two' + doc.getList('y', String).get(1) == 'three' + doc.getList('z', Document).get(0).getString('a') == 'one' + doc.getList('z', Document).get(1).getInteger('b') == 2 + doc.get('w', Document).getList('a', String).get(0) == 'One' + doc.get('w', Document).getList('a', String).get(1) == 'Two' + doc.getList('invalidKey', Document, defaultList).get(0) == 'a' + doc.getList('invalidKey', Document, defaultList).get(1) == 'b' + doc.getList('invalidKey', Document, defaultList).get(2) == 'c' + doc.getList('numberList', Number).get(0) == 10 + doc.getList('numberList', Number).get(1) == 20.5d + doc.getList('numberList', Number).get(2) == 30L + } + + def 'should return null list when key is not found'() { + when: + Document doc = Document.parse('{x: 1}') + + then: + doc.getList('a', String) == null + } + + def 'should return specified default value when key is not found'() { + when: + Document doc = Document.parse('{x: 1}') + List defaultList = ['a', 'b', 'c'] + + then: + doc.getList('a', String, defaultList) == defaultList + } + + def 'should throw an exception when the list elements are not objects of the specified class'() { + given: + Document doc = Document.parse('{x: 1, y: [{a: 1}, {b: 2}], z: [1, 2]}') + + when: + doc.getList('x', String) + + then: + thrown(ClassCastException) + + when: + doc.getList('y', String) + + then: + thrown(ClassCastException) + + when: + doc.getList('z', String) + + then: + thrown(ClassCastException) + } + + def 'should return null when getting embedded value'() { + when: + Document document = Document.parse("{a: 1, b: {x: [2, 3, 4], y: {m: 'one', len: 3}}, 'a.b': 'two'}") + + then: + document.getEmbedded(['notAKey'], String) == null + document.getEmbedded(['b', 'y', 'notAKey'], String) == null + document.getEmbedded(['b', 'b', 'm'], String) == null + Document.parse('{}').getEmbedded(['a', 'b'], Integer) == null + Document.parse('{b: 1}').getEmbedded(['a'], Integer) == null + Document.parse('{b: 1}').getEmbedded(['a', 'b'], Integer) == null + Document.parse('{a: {c: 1}}').getEmbedded(['a', 'b'], Integer) == null + Document.parse('{a: {c: 1}}').getEmbedded(['a', 'b', 'c'], Integer) == null + } + + def 'should return embedded value'() { + given: + Date date = new Date(); + ObjectId objectId = new ObjectId(); + + when: + Document document = Document.parse("{a: 1, b: {x: [2, 3, 4], y: {m: 'one', len: 3}}, 'a.b': 'two'}") + .append('l', new Document('long', 2L)) + .append('d', new Document('double', 3.0 as double)) + .append('t', new Document('boolean', true)) + .append('o', new Document('objectId', objectId)) + .append('n', new Document('date', date)) + + then: + document.getEmbedded(['a'], Integer) == 1 + document.getEmbedded(['b', 'x'], List).get(0) == 2 + document.getEmbedded(['b', 'x'], List).get(1) == 3 + document.getEmbedded(['b', 'x'], List).get(2) == 4 + document.getEmbedded(['b', 'y', 'm'], String) == 'one' + document.getEmbedded(['b', 'y', 'len'], Integer) == 3 + document.getEmbedded(['a.b'], String) == 'two' + document.getEmbedded(['b', 'y'], Document).getString('m') == 'one' + document.getEmbedded(['b', 'y'], Document).getInteger('len') == 3 + + document.getEmbedded(['l', 'long'], Long) == 2L + document.getEmbedded(['d', 'double'], Double) == 3.0d + document.getEmbedded(['l', 'long'], Number) == 2L + document.getEmbedded(['d', 'double'], Number) == 3.0d + document.getEmbedded(['t', 'boolean'], Boolean) == true + document.getEmbedded(['t', 'x'], false) == false + document.getEmbedded(['o', 'objectId'], ObjectId) == objectId + document.getEmbedded(['n', 'date'], Date) == date + } + + def 'should throw an exception getting an embedded value'() { + given: + Document document = Document.parse("{a: 1, b: {x: [2, 3, 4], y: {m: 'one', len: 3}}, 'a.b': 'two'}") + + when: + document.getEmbedded(null, String) == null + + then: + thrown(IllegalArgumentException) + + when: + document.getEmbedded([], String) == null + + then: + thrown(IllegalStateException) + + when: + document.getEmbedded(['a', 'b'], Integer) + + then: + thrown(ClassCastException) + + when: + document.getEmbedded(['b', 'y', 'm'], Integer) + + then: + thrown(ClassCastException) + + when: + document.getEmbedded(['b', 'x'], Document) + + then: + thrown(ClassCastException) + + when: + document.getEmbedded(['b', 'x', 'm'], String) + + then: + thrown(ClassCastException) + + when: + document.getEmbedded(['b', 'x', 'm'], 'invalid') + + then: + thrown(ClassCastException) } def 'should parse a valid JSON string to a Document'() { diff --git a/bson/src/test/unit/org/bson/types/ObjectIdTest.java b/bson/src/test/unit/org/bson/types/ObjectIdTest.java index 2aeb39ad342..bc95e8acd59 100644 --- a/bson/src/test/unit/org/bson/types/ObjectIdTest.java +++ b/bson/src/test/unit/org/bson/types/ObjectIdTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,31 +18,72 @@ import org.junit.Test; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; +import java.nio.ByteBuffer; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +@SuppressWarnings("deprecation") public class ObjectIdTest { @Test - public void testToByteArray() { + public void testToBytes() { ObjectId objectId = new ObjectId(0x5106FC9A, 0x00BC8237, (short) 0x5581, 0x0036D289); - assertArrayEquals(new byte[]{81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119}, objectId.toByteArray()); + byte[] expectedBytes = new byte[]{81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119}; + + assertArrayEquals(expectedBytes, objectId.toByteArray()); + + ByteBuffer buffer = ByteBuffer.allocate(12); + objectId.putToByteBuffer(buffer); + assertArrayEquals(expectedBytes, buffer.array()); } @Test - public void testFromByteArray() { - ObjectId objectId = new ObjectId(new byte[]{81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119}); - assertEquals(0x5106FC9A, objectId.getTimestamp()); - assertEquals(0x00BC8237, objectId.getMachineIdentifier()); - assertEquals((short) 0x5581, objectId.getProcessIdentifier()); - assertEquals(0x0036D289, objectId.getCounter()); + public void testFromBytes() { + + try { + new ObjectId((byte[]) null); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("bytes can not be null", e.getMessage()); + } + + try { + new ObjectId(new byte[11]); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("state should be: bytes has length of 12", e.getMessage()); + } + + try { + new ObjectId(new byte[13]); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertEquals("state should be: bytes has length of 12", e.getMessage()); + } + + byte[] bytes = new byte[]{81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119}; + + ObjectId objectId1 = new ObjectId(bytes); + assertEquals(0x5106FC9A, objectId1.getTimestamp()); + assertEquals(0x00BC8237, objectId1.getMachineIdentifier()); + assertEquals((short) 0x5581, objectId1.getProcessIdentifier()); + assertEquals(0x0036D289, objectId1.getCounter()); + + ObjectId objectId2 = new ObjectId(ByteBuffer.wrap(bytes)); + assertEquals(0x5106FC9A, objectId2.getTimestamp()); + assertEquals(0x00BC8237, objectId2.getMachineIdentifier()); + assertEquals((short) 0x5581, objectId2.getProcessIdentifier()); + assertEquals(0x0036D289, objectId2.getCounter()); } @Test - public void testBytes() { + public void testBytesRoundtrip() { ObjectId expected = new ObjectId(); ObjectId actual = new ObjectId(expected.toByteArray()); assertEquals(expected, actual); @@ -57,6 +98,26 @@ public void testBytes() { assertEquals("41d91c58988b09375cc1fe9f", expected.toString()); } + @Test + public void testGetTimeZero() { + assertEquals(0L, new ObjectId(0, 0).getTime()); + } + + @Test + public void testGetTimeMaxSignedInt() { + assertEquals(0x7FFFFFFFL * 1000, new ObjectId(0x7FFFFFFF, 0).getTime()); + } + + @Test + public void testGetTimeMaxSignedIntPlusOne() { + assertEquals(0x80000000L * 1000, new ObjectId(0x80000000, 0).getTime()); + } + + @Test + public void testGetTimeMaxInt() { + assertEquals(0xFFFFFFFFL * 1000, new ObjectId(0xFFFFFFFF, 0).getTime()); + } + @Test public void testTime() { long a = System.currentTimeMillis(); @@ -66,9 +127,7 @@ public void testTime() { @Test public void testDateCons() { - Date d = new Date(); - ObjectId a = new ObjectId(d); - assertEquals(d.getTime() / 1000, a.getDate().getTime() / 1000); + assertEquals(new Date().getTime() / 1000, new ObjectId(new Date()).getDate().getTime() / 1000); } @Test @@ -127,7 +186,31 @@ public void testCompareTo() { public void testToHexString() { assertEquals("000000000000000000000000", new ObjectId(0, 0, (short) 0, 0).toHexString()); assertEquals("7fffffff007fff7fff007fff", - new ObjectId(Integer.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE).toHexString()); + new ObjectId(Integer.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE).toHexString()); + } + + private Date getDate(final String s) throws ParseException { + return new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z").parse(s); + } + + @Test + public void testTimeZero() throws ParseException { + assertEquals(getDate("01-Jan-1970 00:00:00 -0000"), new ObjectId(0, 0).getDate()); + } + + @Test + public void testTimeMaxSignedInt() throws ParseException { + assertEquals(getDate("19-Jan-2038 03:14:07 -0000"), new ObjectId(0x7FFFFFFF, 0).getDate()); + } + + @Test + public void testTimeMaxSignedIntPlusOne() throws ParseException { + assertEquals(getDate("19-Jan-2038 03:14:08 -0000"), new ObjectId(0x80000000, 0).getDate()); + } + + @Test + public void testTimeMaxInt() throws ParseException { + assertEquals(getDate("07-Feb-2106 06:28:15 -0000"), new ObjectId(0xFFFFFFFF, 0).getDate()); } @SuppressWarnings("deprecation") @@ -139,14 +222,13 @@ public void testDeprecatedMethods() { assertEquals(id.getDate().getTime(), id.getTime()); assertEquals(id.toHexString(), id.toStringMongod()); assertArrayEquals(new byte[]{0x12, 0x34, 0x56, 0x78, 0x43, 0x21, 0xffffff87, 0x65, 0x74, 0xffffff92, 0xffffff87, 0x56}, - new ObjectId(0x12345678, 0x43218765, 0x74928756).toByteArray()); + new ObjectId(0x12345678, 0x43218765, 0x74928756).toByteArray()); } // Got these values from 2.12.0 driver. This test is ensuring that we properly round-trip old and new format ObjectIds. @Test public void testCreateFromLegacy() { assertArrayEquals(new byte[]{82, 23, -82, -78, -80, -58, -95, -92, -75, -38, 118, -16}, - ObjectId.createFromLegacyFormat(1377283762, -1329159772, -1243973904).toByteArray()); + ObjectId.createFromLegacyFormat(1377283762, -1329159772, -1243973904).toByteArray()); } } - diff --git a/driver/src/test/unit/org/bson/types/StringRangeSetSpecification.groovy b/bson/src/test/unit/org/bson/types/StringRangeSetSpecification.groovy similarity index 93% rename from driver/src/test/unit/org/bson/types/StringRangeSetSpecification.groovy rename to bson/src/test/unit/org/bson/types/StringRangeSetSpecification.groovy index e6cc8a88267..191057b4cb4 100644 --- a/driver/src/test/unit/org/bson/types/StringRangeSetSpecification.groovy +++ b/bson/src/test/unit/org/bson/types/StringRangeSetSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2008-2016 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,15 @@ class StringRangeSetSpecification extends Specification { !stringSet.containsAll([0, 1, 2]) } + def 'should not contain strings that do not parse as integers'() { + when: + def stringSet = new StringRangeSet(5) + + then: + !stringSet.contains('foo') + !stringSet.containsAll(['foo', 'bar', 'baz']) + } + def 'set should be ordered string representations of the range'() { given: def size = 2000; diff --git a/driver/src/test/unit/org/bson/util/ClassMapSpecification.groovy b/bson/src/test/unit/org/bson/util/ClassMapSpecification.groovy similarity index 98% rename from driver/src/test/unit/org/bson/util/ClassMapSpecification.groovy rename to bson/src/test/unit/org/bson/util/ClassMapSpecification.groovy index b9edb6576e3..a87f96b237e 100644 --- a/driver/src/test/unit/org/bson/util/ClassMapSpecification.groovy +++ b/bson/src/test/unit/org/bson/util/ClassMapSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2014 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/util/GroovyHelpers.java b/bson/src/test/unit/util/GroovyHelpers.java index 0fb52cc557b..8319f7f9e16 100644 --- a/bson/src/test/unit/util/GroovyHelpers.java +++ b/bson/src/test/unit/util/GroovyHelpers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/bson/src/test/unit/util/Hex.java b/bson/src/test/unit/util/Hex.java new file mode 100644 index 00000000000..3ea40ab39db --- /dev/null +++ b/bson/src/test/unit/util/Hex.java @@ -0,0 +1,53 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util; + +public final class Hex { + public static byte[] decode(final String hex) { + if (hex.length() % 2 != 0) { + throw new IllegalArgumentException("A hex string must contain an even number of characters: " + hex); + } + + byte[] out = new byte[hex.length() / 2]; + + for (int i = 0; i < hex.length(); i += 2) { + int high = Character.digit(hex.charAt(i), 16); + int low = Character.digit(hex.charAt(i + 1), 16); + if (high == -1 || low == -1) { + throw new IllegalArgumentException("A hex string can only contain the characters 0-9, A-F, a-f: " + hex); + } + + out[i / 2] = (byte) (high * 16 + low); + } + + return out; + } + + private static final char[] UPPER_HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', }; + + public static String encode(final byte[] bytes) { + StringBuilder stringBuilder = new StringBuilder(bytes.length * 2); + for (byte cur : bytes) { + stringBuilder.append(UPPER_HEX_DIGITS[(cur >> 4) & 0xF]); + stringBuilder.append(UPPER_HEX_DIGITS[(cur & 0xF)]); + } + return stringBuilder.toString(); + } + + private Hex() { + } +} diff --git a/bson/src/test/unit/util/JsonPoweredTestHelper.java b/bson/src/test/unit/util/JsonPoweredTestHelper.java index a375c3a63dc..09a547dbfbe 100644 --- a/bson/src/test/unit/util/JsonPoweredTestHelper.java +++ b/bson/src/test/unit/util/JsonPoweredTestHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,8 +12,6 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * - * */ package util; @@ -39,6 +37,10 @@ public static BsonDocument getTestDocument(final File file) throws IOException { return new BsonDocumentCodec().decode(new JsonReader(getFileAsString(file)), DecoderContext.builder().build()); } + public static BsonDocument getTestDocument(final String resourcePath) throws IOException, URISyntaxException { + return getTestDocument(new File(JsonPoweredTestHelper.class.getResource(resourcePath).toURI())); + } + public static List getTestFiles(final String resourcePath) throws URISyntaxException { List files = new ArrayList(); addFilesFromDirectory(new File(JsonPoweredTestHelper.class.getResource(resourcePath).toURI()), files); diff --git a/build.gradle b/build.gradle index 487327e882c..176b4422ea4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 MongoDB, Inc. + * Copyright 2008-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,20 +17,17 @@ apply plugin: 'eclipse' apply plugin: 'idea' -def configDir = new File(rootDir, 'config') -ext.nettyVersion = '4.0.26.Final' - buildscript { repositories { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } - jcenter() - mavenCentral() mavenLocal() + mavenCentral() + jcenter() + maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.2' - classpath 'com.netflix.nebula:gradle-extra-configurations-plugin:1.12.+' - classpath 'com.bmuschko:gradle-nexus-plugin:2.2' + classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.5' + classpath 'com.netflix.nebula:gradle-extra-configurations-plugin:3.0.3' + classpath 'gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:1.6.4' } } @@ -38,25 +35,57 @@ buildscript { // Common behavior // ////////////////////////////////////////// -configure(subprojects.findAll { it.name != 'util' }) { - apply plugin: 'java' - apply plugin: 'optional-base' +ext { + configDir = new File(rootDir, 'config') + jnrUnixsocketVersion = '0.18' + nettyVersion = '4.1.17.Final' + snappyVersion = '1.1.4' + zstdVersion = '1.3.8-3' + mongoCryptVersion = '1.0.0' + gitVersion = getGitVersion() +} - evaluationDependsOn(':util') +def configDir = ext.configDir +def coreProjects = subprojects.findAll { !['util'].contains(it.name) } +def javaProjects = subprojects +def javaMainProjects = subprojects.findAll { !['util'].contains(it.name) } +def javaCodeCheckedProjects = subprojects.findAll { !['util', 'mongo-java-driver', 'driver-benchmarks'].contains(it.name) } +configure(coreProjects) { + evaluationDependsOn(':util') group = 'org.mongodb' - version = '3.3.0' - sourceCompatibility = JavaVersion.VERSION_1_6 - targetCompatibility = JavaVersion.VERSION_1_6 + version = '3.12.0' repositories { - mavenCentral() - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } mavenLocal() + google() + mavenCentral() + jcenter() } +} + +configure(javaProjects) { + apply plugin: 'java-library' + + sourceCompatibility = JavaVersion.VERSION_1_6 + targetCompatibility = JavaVersion.VERSION_1_6 + + sourceSets { + main { + java.srcDirs = ['src/main'] + } + } +} + +configure(javaMainProjects) { + apply plugin: 'idea' + apply plugin: 'osgi' + apply plugin: 'nebula.optional-base' dependencies { + compileOnly 'com.google.code.findbugs:jsr305:1.3.9' compile 'org.slf4j:slf4j-api:1.7.6', optional + testCompile 'com.google.code.findbugs:jsr305:1.3.9' } /* Compiling */ @@ -64,63 +93,21 @@ configure(subprojects.findAll { it.name != 'util' }) { options.encoding = 'ISO-8859-1' options.fork = true options.debug = true - options.compilerArgs = ['-Xlint:all', '-Xlint:-options'] - - onlyIf { JavaVersion.current().isJava7Compatible() } - } - - project.ext.buildingWith = { propertyName -> - project.hasProperty(propertyName) && project.property(propertyName).toBoolean() - } - - javadoc { - exclude "**/com/mongodb/**/internal/**" - - dependsOn project(':util').compileJava //We need taglets to be compiled - options.author = true - options.version = true - options.links 'http://docs.oracle.com/javase/7/docs/api/' - options.tagletPath project(':util').sourceSets.main.output.classesDir - options.taglets 'ManualTaglet' - options.taglets 'ServerReleaseTaglet' - options.encoding = 'UTF-8' - options.charSet 'UTF-8' - options.docEncoding 'UTF-8' - options.header = ''' - - ''' + options.compilerArgs = ['-Xlint:all', '-Xlint:-options', '-Xlint:-deprecation'] } } -configure(subprojects.findAll { it.name != 'util' && it.name != 'mongo-java-driver' }) { +configure(javaCodeCheckedProjects) { apply plugin: 'checkstyle' - apply plugin: 'findbugs' + apply plugin: "com.github.spotbugs" + apply plugin: 'org.kordamp.gradle.clirr' apply plugin: 'jacoco' apply plugin: 'groovy' apply plugin: 'codenarc' dependencies { - testCompile 'org.codehaus.groovy:groovy-all:2.3.9' - testCompile 'org.spockframework:spock-core:1.1-groovy-2.3-SNAPSHOT' + testCompile 'org.codehaus.groovy:groovy-all:2.4.15' + testCompile 'org.spockframework:spock-core:1.1-groovy-2.4' testCompile 'cglib:cglib-nodep:2.2.2' testCompile 'org.objenesis:objenesis:1.3' testCompile 'org.hamcrest:hamcrest-all:1.3' @@ -129,12 +116,8 @@ configure(subprojects.findAll { it.name != 'util' && it.name != 'mongo-java-driv } sourceSets { - main { - java.srcDirs = ['src/main'] - } test { groovy.srcDirs = ['src/test/functional', 'src/test/unit'] - java.srcDirs = ['src/test/functional', 'src/test/unit'] } } @@ -144,26 +127,36 @@ configure(subprojects.findAll { it.name != 'util' && it.name != 'mongo-java-driv maxParallelForks = 1 systemProperties( - 'org.mongodb.test.uri': System.getProperty('org.mongodb.test.uri', null), + 'org.mongodb.test.uri': System.getProperty('org.mongodb.test.uri'), + 'org.mongodb.test.transaction.uri': System.getProperty('org.mongodb.test.transaction.uri'), + 'org.mongodb.test.embedded.path': System.getProperty('org.mongodb.test.embedded.path'), 'org.mongodb.useSocket': System.getProperty('org.mongodb.useSocket', 'false'), 'org.mongodb.disableAsync': System.getProperty('org.mongodb.disableAsync', 'false'), 'org.mongodb.async.type': System.getProperty('org.mongodb.async.type', 'nio2'), - - 'javax.net.ssl.trustStore': System.getProperty('javax.net.ssl.trustStore', "${System.getProperty('user.home')}/.keystore"), - 'javax.net.ssl.keyStore': System.getProperty('javax.net.ssl.keyStore', "${System.getProperty('user.home')}/.keystore"), - 'javax.net.ssl.keyStorePassword': System.getProperty('javax.net.ssl.keyStorePassword', 'changeit'), - 'javax.net.ssl.trustStorePassword': System.getProperty('javax.net.ssl.trustStorePassword', 'changeit') + 'org.mongodb.test.awsAccessKeyId': System.getProperty('org.mongodb.test.awsAccessKeyId'), + 'org.mongodb.test.awsSecretAccessKey': System.getProperty('org.mongodb.test.awsSecretAccessKey'), + 'jna.library.path': System.getProperty('jna.library.path') ) + project.ext.buildingWith = { propertyName -> + project.hasProperty(propertyName) && project.property(propertyName).toBoolean() + } + if (project.buildingWith('ssl.enabled')) { - systemProperties( - 'javax.net.ssl.keyStoreType': project.property('ssl.keyStoreType'), - 'javax.net.ssl.keyStore': project.property('ssl.keyStore'), - 'javax.net.ssl.keyStorePassword': project.property('ssl.keyStorePassword'), - 'javax.net.ssl.trustStoreType': project.property('ssl.trustStoreType'), - 'javax.net.ssl.trustStore': project.property('ssl.trustStore'), - 'javax.net.ssl.trustStorePassword': project.property('ssl.trustStorePassword') - ) + if (project.hasProperty('ssl.keyStoreType')) { + systemProperties( + 'javax.net.ssl.keyStoreType': project.property('ssl.keyStoreType'), + 'javax.net.ssl.keyStore': project.property('ssl.keyStore'), + 'javax.net.ssl.keyStorePassword': project.property('ssl.keyStorePassword'), + ) + } + if (project.hasProperty('ssl.trustStoreType')) { + systemProperties( + 'javax.net.ssl.trustStoreType': project.property('ssl.trustStoreType'), + 'javax.net.ssl.trustStore': project.property('ssl.trustStore'), + 'javax.net.ssl.trustStorePassword': project.property('ssl.trustStorePassword') + ) + } } if (project.buildingWith('gssapi.enabled')) { @@ -177,36 +170,17 @@ configure(subprojects.findAll { it.name != 'util' && it.name != 'mongo-java-driv } useJUnit { - if (!project.buildingWith('rs.enabled')) { - excludeCategories 'category.ReplicaSet' - } - if (project.buildingWith('quicktest')) { - excludeCategories 'category.SlowUnit' - } - if (project.buildingWith('travistest')) { - excludeCategories 'category.SlowUnit', 'category.Slow' - } + excludeCategories 'category.Slow' } jacoco { enabled = false } - beforeTest { descr -> - logger.info("[Test ${descr.className} > ${descr.name}]") - } - - afterTest { descr, result -> - def failureStartTime = result.getResultType().toString() == "FAILURE" ? " Started at: ${result.getStartTime()}" : "" - logger.info("[Test ${descr.className} > ${descr.name}] ${result.getResultType()} " - + "(${result.getEndTime() - result.getStartTime()} ms) ${failureStartTime}") - - } - testLogging { exceptionFormat = 'full' } } - task testSlowUnit(type: Test) { + task testSlowOnly(type: Test) { useJUnit { - includeCategories 'category.SlowUnit' + includeCategories 'category.Slow' } } @@ -219,54 +193,77 @@ configure(subprojects.findAll { it.name != 'util' && it.name != 'mongo-java-driv task testCoverage(dependsOn: test) /* Code quality */ + tasks.withType(Checkstyle) { + reports { + xml.enabled true + html.enabled true + } + } checkstyle { - toolVersion = "6.2" + toolVersion = "7.4" configFile = new File(configDir, 'checkstyle.xml') configProperties.checkstyleConfigDir = configDir } - findbugs { + spotbugs { excludeFilter = new File(configDir, 'findbugs-exclude.xml') sourceSets = [sourceSets.main] - toolVersion = '3.0.1' + toolVersion = '3.1.7' } codenarc { - toolVersion = '0.24' + toolVersion = '1.1' reportFormat = project.buildingWith('xmlReports.enabled') ? 'xml' : 'html' } - tasks.withType(FindBugs) { + tasks.withType(com.github.spotbugs.SpotBugsTask) { reports { xml.enabled = project.buildingWith('xmlReports.enabled') html.enabled = !project.buildingWith('xmlReports.enabled') } } + + tasks.withType(Test) { + def jdkHome = findProperty("jdkHome") + if (jdkHome) { + def javaExecutablesPath = new File(jdkHome, 'bin') + def javaExecutables = [:].withDefault { execName -> + def executable = new File(javaExecutablesPath, execName) + assert executable.exists() : "There is no ${execName} executable in ${javaExecutablesPath}" + executable + } + executable = javaExecutables.java + } + } } -task docs(type: Javadoc) { - source subprojects.grep({ it.name != 'util' }).collect {project -> project.sourceSets.main.allJava } - options = subprojects.first().javadoc.options - dependsOn = subprojects.first().javadoc.dependsOn - excludes = subprojects.first().javadoc.excludes - classpath = files(subprojects.collect {project -> project.sourceSets.main.compileClasspath}) - destinationDir = new File(projectDir, 'build/docs') +def getGitVersion() { + def describeStdOut = new ByteArrayOutputStream() + exec { + commandLine 'git', 'describe', '--tags', '--always', '--dirty' + standardOutput = describeStdOut + } + describeStdOut.toString().substring(1).trim() } +apply from: 'gradle/publish.gradle' +apply from: 'gradle/deploy.gradle' +apply from: 'gradle/javadoc.gradle' +apply from: 'gradle/testColorOutput.gradle' + + ////////////////////////////////////////// // Root project configuration // ////////////////////////////////////////// task wrapper(type: Wrapper) { - gradleVersion = '2.4' + gradleVersion = '4.10.2' } -gradle.buildFinished { BuildResult result -> - if (result.failure && !JavaVersion.current().isJava7Compatible()) { - gradle.rootProject.logger.error("\n* Warning:\nJDK ${JavaVersion.VERSION_1_7} is minimal requirement for building the driver. " + - "You have ${JavaVersion.current()}.") - } +if (!JavaVersion.current().isJava9Compatible()) { + throw new GradleException(""" + | ERROR: + | JDK ${JavaVersion.VERSION_1_9.getMajorVersion()} is required to build the driver: You are using JDK ${JavaVersion.current().getMajorVersion()}. + |""".stripMargin() + ) } - -apply from: 'gradle/deploy.gradle' - diff --git a/config/checkstyle-exclude.xml b/config/checkstyle-exclude.xml index 183e378ccb4..19c29707230 100644 --- a/config/checkstyle-exclude.xml +++ b/config/checkstyle-exclude.xml @@ -1,7 +1,7 @@ @@ -32,6 +47,13 @@ + + + + + + + @@ -41,9 +63,13 @@ + + + + @@ -61,6 +87,7 @@ + @@ -90,11 +117,26 @@ - + + + + + + + + + + + + + + + + diff --git a/config/checkstyle.xml b/config/checkstyle.xml index 36b767522a5..6ba0a356009 100644 --- a/config/checkstyle.xml +++ b/config/checkstyle.xml @@ -1,6 +1,6 @@ + value="^(//| \*) Copyright 2008-present MongoDB, Inc\.$" /> @@ -47,7 +47,9 @@ - + + + @@ -121,7 +123,7 @@ - + @@ -218,6 +220,12 @@ + + + + + + diff --git a/config/clirr-exclude.yml b/config/clirr-exclude.yml index 789844841d4..7cc05ec93d8 100644 --- a/config/clirr-exclude.yml +++ b/config/clirr-exclude.yml @@ -4,3 +4,29 @@ differenceTypes: [] packages: - io.netty.* - org.slf4j.* + +classes: + - org.bson.codecs.configuration.Optional$Some + +members: + org.bson.AbstractBsonReader: + - doReadDecimal128() + - doPeekBinarySize() + org.bson.AbstractBsonWriter: + - doWriteDecimal128(org.bson.types.Decimal128) + org.bson.BsonNumber: + - decimal128Value() + org.bson.BsonReader: + - readDecimal128() + - readDecimal128(java.lang.String) + - peekBinarySize() + - getMark() + - close() + org.bson.io.BsonInput: + - getMark(int) + org.bson.BsonWriter: + - writeDecimal128(org.bson.types.Decimal128) + - writeDecimal128(java.lang.String,org.bson.types.Decimal128) + - pipe(org.bson.BsonReader,java.util.List) + org.bson.diagnostics.Loggers: + - getLogger(java.lang.String) diff --git a/config/codenarc/codenarc.xml b/config/codenarc/codenarc.xml index 6662b18bbb0..2e6aeae2eb5 100644 --- a/config/codenarc/codenarc.xml +++ b/config/codenarc/codenarc.xml @@ -1,3 +1,19 @@ + + - + + + + + + + + + + + + + + + + + @@ -40,10 +72,15 @@ - + + + + + + @@ -53,9 +90,11 @@ + + @@ -76,6 +115,9 @@ + + + @@ -86,6 +128,8 @@ + + diff --git a/config/findbugs-exclude.xml b/config/findbugs-exclude.xml index d27a2f4af29..eb6c479d606 100644 --- a/config/findbugs-exclude.xml +++ b/config/findbugs-exclude.xml @@ -1,3 +1,19 @@ + + @@ -17,7 +33,7 @@ - + @@ -72,6 +88,17 @@ + + + + + + + + + + + @@ -100,8 +127,35 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/landing/README.md b/docs/landing/README.md index 1efb1858835..5f1702b3a37 100644 --- a/docs/landing/README.md +++ b/docs/landing/README.md @@ -3,7 +3,7 @@ The static front page site for the Java documentation portal. ## Requirements -Hugo version 0.13 [download here](https://github.com/spf13/hugo/releases/tag/v0.13) +Hugo version 0.25 [download here](https://github.com/spf13/hugo/releases/tag/v0.25)
    Check out the hugo [quickstart guide](http://gohugo.io/overview/quickstart/). diff --git a/docs/landing/config.toml b/docs/landing/config.toml index f4b0ac5aaee..013e39ec8ba 100644 --- a/docs/landing/config.toml +++ b/docs/landing/config.toml @@ -2,5 +2,7 @@ baseurl = "/mongo-java-driver/" languageCode = "en-us" title = "MongoDB Java Driver" canonifyurls = false +disableHugoGeneratorInject = true +disableKinds = ["section", "taxonomy", "taxonomyTerm", "404"] githubRepo = "mongo-java-driver" diff --git a/docs/landing/data/releases.toml b/docs/landing/data/releases.toml index 2476ee9c005..4d4c1edd0ca 100644 --- a/docs/landing/data/releases.toml +++ b/docs/landing/data/releases.toml @@ -1,8 +1,53 @@ -current = "3.3.0" +current = "3.12.0" [[versions]] - version = "3.3.0" + version = "3.12.0" status = "current" + docs = "./3.12" + api = "./3.12/javadoc" + +[[versions]] + version = "3.11.2" + docs = "./3.11" + api = "./3.11/javadoc" + +[[versions]] + version = "3.10.2" + docs = "./3.10" + api = "./3.10/javadoc" + +[[versions]] + version = "3.9.0" + docs = "./3.9" + api = "./3.9/javadoc" + +[[versions]] + version = "3.8.2" + docs = "./3.8" + api = "./3.8/javadoc" + +[[versions]] + version = "3.7.1" + docs = "./3.7" + api = "./3.7/javadoc" + +[[versions]] + version = "3.6.4" + docs = "./3.6" + api = "./3.6/javadoc" + +[[versions]] + version = "3.5.0" + docs = "./3.5" + api = "./3.5/javadoc" + +[[versions]] + version = "3.4.3" + docs = "./3.4" + api = "./3.4/javadoc" + +[[versions]] + version = "3.3.0" docs = "./3.3" api = "http://api.mongodb.com/java/3.3" @@ -32,21 +77,17 @@ current = "3.3.0" api = "http://api.mongodb.com/java/2.13" [[drivers]] - name = "mongodb-driver" - description = "The synchronous driver, new in 3.0.
    For older versions of the driver or for OSGi-based applications please use the `mongo-java-driver`." - versions = "3.3.0,3.2.2,3.1.1,3.0.4" + name = "mongodb-driver-sync" + description = "The synchronous driver, new in 3.7." + versions = "3.12.0,3.11.2,3.10.2,3.9.0,3.8.2,3.7.1" [[drivers]] - name = "mongo-java-driver" - description = "An uber jar containing the bson library, the core library and the mongodb-driver.
    This artifact is a valid OSGi bundle." - versions = "3.3.0,3.2.2,3.1.1,3.0.4,2.14.2,2.13.3" + name = "mongodb-driver-legacy" + description = "The legacy API for the synchronous driver.
    Prefer mongodb-driver-sync for new applications" + versions = "3.12.0,3.11.2,3.10.2,3.9.0" [[drivers]] name = "mongodb-driver-async" - description = "The new asynchronous driver, new in 3.0" - versions = "3.3.0,3.2.2,3.1.1,3.0.4" + description = "The callback-based asynchronous driver.
    Prefer the Reactive Streams driver for new applications." + versions = "3.12.0,3.11.2,3.10.2,3.9.0,3.8.2,3.7.1,3.6.4,3.5.0,3.4.3,3.3.0,3.2.2,3.1.1,3.0.4" -[[drivers]] - name = "mongodb-driver-core" - description = "The core library, new in 3.0" - versions = "3.3.0,3.2.2,3.1.1,3.0.4" diff --git a/docs/landing/layouts/404.html b/docs/landing/layouts/404.html index 32eba427510..634869dcb9b 100644 --- a/docs/landing/layouts/404.html +++ b/docs/landing/layouts/404.html @@ -2,7 +2,7 @@ {{ partial "meta.html"}} - + {{.Title}} diff --git a/docs/landing/layouts/index.html b/docs/landing/layouts/index.html index b47a66a7c20..1164cfe59ad 100644 --- a/docs/landing/layouts/index.html +++ b/docs/landing/layouts/index.html @@ -1,34 +1,34 @@ - {{ partial "meta.html"}} - + {{- partial "meta.html" -}} + {{.Title}} - {{ partial "assets/css.html" . }} + {{- partial "assets/css.html" . -}} - {{ partial "header/main.html" . }} + {{- partial "header/main.html" . -}} - {{ partial "hero.html" . }} + {{- partial "hero.html" . -}}
    - {{ partial "introduction.html" . }} + {{- partial "introduction.html" . -}} - {{ partial "features.html" . }} + {{- partial "features.html" . -}} - {{ partial "quickStart.html" . }} + {{- partial "quickStart.html" . -}}
    - {{ partial "releases.html" . }} - {{ partial "mongodbUniversity.html" . }} + {{- partial "releases.html" . -}} + {{- partial "mongodbUniversity.html" . -}}
    @@ -38,7 +38,7 @@
    - {{ partial "footer.html" .}} + {{- partial "footer.html" . -}}
    @@ -46,7 +46,7 @@ -{{ partial "assets/javascripts.html" . }} -{{ partial "assets/analytics.html" . }} +{{- partial "assets/javascripts.html" . -}} +{{- partial "assets/analytics.html" . -}} diff --git a/docs/landing/layouts/partials/assets/css.html b/docs/landing/layouts/partials/assets/css.html index 7f49b2a0220..e053bda9e00 100644 --- a/docs/landing/layouts/partials/assets/css.html +++ b/docs/landing/layouts/partials/assets/css.html @@ -1,6 +1,6 @@ - - - - - - + + + + + + diff --git a/docs/landing/layouts/partials/assets/javascripts.html b/docs/landing/layouts/partials/assets/javascripts.html index 535b1b3db7e..6ef23295901 100644 --- a/docs/landing/layouts/partials/assets/javascripts.html +++ b/docs/landing/layouts/partials/assets/javascripts.html @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/docs/landing/layouts/partials/features.html b/docs/landing/layouts/partials/features.html index 76dc1ad72d8..0b062a181c9 100644 --- a/docs/landing/layouts/partials/features.html +++ b/docs/landing/layouts/partials/features.html @@ -8,7 +8,7 @@

    Features

    MongoDB Driver
    An updated Java driver that includes the legacy API as well as a new generic MongoCollection interface that complies with a new cross-driver CRUD specification.
    MongoDB Async Driver
    -
    A new asynchronous API that can leverage either Netty or Java 7's AsynchronousSocketChannel for fast and non-blocking IO.
    -
    Core driver
    -
    The MongoDB Driver and Async Driver are both built on top of a new core library, which anyone can use to build alternative or experimental high-level APIs.
    +
    A callback-based asynchronous driver. Note that this driver is now deprecated in favor of the + Reactive Streams Java Driver +
    diff --git a/docs/landing/layouts/partials/footer.html b/docs/landing/layouts/partials/footer.html index 39df5b0403f..bf9321d6f96 100644 --- a/docs/landing/layouts/partials/footer.html +++ b/docs/landing/layouts/partials/footer.html @@ -1,6 +1,6 @@ @@ -32,7 +32,7 @@ -{{ partial "assets/javascripts.html" . }} -{{ partial "assets/analytics.html" . }} +{{- partial "assets/javascripts.html" . -}} +{{- partial "assets/analytics.html" . -}} diff --git a/docs/reference/themes/mongodb/layouts/partials/header.html b/docs/reference/themes/mongodb/layouts/partials/header.html index f1edfbd0f2e..e29afe1b4af 100644 --- a/docs/reference/themes/mongodb/layouts/partials/header.html +++ b/docs/reference/themes/mongodb/layouts/partials/header.html @@ -1,22 +1,22 @@ - {{ partial "meta.html"}} - + {{- partial "meta.html" -}} + {{.Title}} - {{ partial "assets/css.html" . }} + {{- partial "assets/css.html" . -}}
    - {{ partial "header/main.html" . }} + {{- partial "header/main.html" . -}} - {{ partial "menu.html" . }} + {{- partial "menu.html" . -}}
    diff --git a/docs/reference/themes/mongodb/layouts/partials/header/contentHeader.html b/docs/reference/themes/mongodb/layouts/partials/header/contentHeader.html index b3a57f7b93a..f9e8246616d 100644 --- a/docs/reference/themes/mongodb/layouts/partials/header/contentHeader.html +++ b/docs/reference/themes/mongodb/layouts/partials/header/contentHeader.html @@ -1,25 +1,25 @@ -{{ if .IsPage }} -{{ $File := .File }} {{with $File.LogicalName }} {{ $srcref := add (add "docs/reference/content/" $File.Dir) $File.LogicalName }} +{{- if .IsPage -}} +{{- $File := .File -}}{{ with $File.LogicalName }} {{ $srcref := add (add "docs/reference/content/" $File.Dir) $File.LogicalName }} -{{end}} -{{end}} -{{ $menuItemL6 := $.Scratch.Get "menu.Item.L6"}} -{{ $menuItemL5 := $.Scratch.Get "menu.Item.L5"}} -{{ $menuItemL4 := $.Scratch.Get "menu.Item.L4"}} -{{ $menuItemL3 := $.Scratch.Get "menu.Item.L3"}} -{{ $menuItemL2 := $.Scratch.Get "menu.Item.L2"}} -{{ $menuItemL1 := $.Scratch.Get "menu.Item.L1"}} -{{ $menuItemL0 := $.Scratch.Get "menu.Item.L0"}} -{{if $menuItemL0}} +{{- end -}} +{{- end -}} +{{- $menuItemL6 := $.Scratch.Get "menu.Item.L6" -}} +{{- $menuItemL5 := $.Scratch.Get "menu.Item.L5" -}} +{{- $menuItemL4 := $.Scratch.Get "menu.Item.L4" -}} +{{- $menuItemL3 := $.Scratch.Get "menu.Item.L3" -}} +{{- $menuItemL2 := $.Scratch.Get "menu.Item.L2" -}} +{{- $menuItemL1 := $.Scratch.Get "menu.Item.L1" -}} +{{- $menuItemL0 := $.Scratch.Get "menu.Item.L0" -}} +{{- if $menuItemL0 -}}
      - {{with $menuItemL6}}
    • {{.Name}}
    • {{end}} - {{with $menuItemL5}}
    • {{.Name}}
    • {{end}} - {{with $menuItemL4}}
    • {{.Name}}
    • {{end}} - {{with $menuItemL3}}
    • {{.Name}}
    • {{end}} - {{with $menuItemL2}}
    • {{.Name}}
    • {{end}} - {{with $menuItemL1}}
    • {{.Name}}
    • {{end}} -
    • {{$menuItemL0.Name}}
    • + {{- with $menuItemL6 -}}
    • {{.Name}}
    • {{- end -}} + {{- with $menuItemL5 -}}
    • {{.Name}}
    • {{- end -}} + {{- with $menuItemL4 -}}
    • {{.Name}}
    • {{- end -}} + {{- with $menuItemL3 -}}
    • {{.Name}}
    • {{- end -}} + {{- with $menuItemL2 -}}
    • {{.Name}}
    • {{- end -}} + {{- with $menuItemL1 -}}
    • {{.Name}}
    • {{- end -}} +
    • {{ $menuItemL0.Name }}
    -{{ end }} +{{- end -}} diff --git a/docs/reference/themes/mongodb/layouts/partials/header/main.html b/docs/reference/themes/mongodb/layouts/partials/header/main.html index 11c373e0ff4..e37d07bc5f9 100644 --- a/docs/reference/themes/mongodb/layouts/partials/header/main.html +++ b/docs/reference/themes/mongodb/layouts/partials/header/main.html @@ -6,8 +6,8 @@ diff --git a/docs/reference/themes/mongodb/layouts/partials/header/search.html b/docs/reference/themes/mongodb/layouts/partials/header/search.html index 2570e110f7b..ac41c4db1b2 100644 --- a/docs/reference/themes/mongodb/layouts/partials/header/search.html +++ b/docs/reference/themes/mongodb/layouts/partials/header/search.html @@ -1,7 +1,7 @@