From 42ff7c2504f5a54393d11df7d72f87ec7cf6b76f Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Wed, 18 Jan 2017 17:34:20 +0100 Subject: [PATCH 01/20] initial commit --- .gitignore | 162 +++++++++++++++++ build.gradle | 92 ++++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52928 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 169 ++++++++++++++++++ gradlew.bat | 84 +++++++++ settings.gradle | 2 + src/main/java/ch/dissem/msgpack/Reader.java | 44 +++++ .../java/ch/dissem/msgpack/types/MPArray.java | 101 +++++++++++ .../ch/dissem/msgpack/types/MPBinary.java | 74 ++++++++ .../ch/dissem/msgpack/types/MPBoolean.java | 64 +++++++ .../ch/dissem/msgpack/types/MPDouble.java | 59 ++++++ .../java/ch/dissem/msgpack/types/MPFloat.java | 59 ++++++ .../ch/dissem/msgpack/types/MPInteger.java | 124 +++++++++++++ .../java/ch/dissem/msgpack/types/MPMap.java | 105 +++++++++++ .../java/ch/dissem/msgpack/types/MPNil.java | 46 +++++ .../ch/dissem/msgpack/types/MPString.java | 78 ++++++++ .../java/ch/dissem/msgpack/types/MPType.java | 20 +++ .../java/ch/dissem/msgpack/types/Utils.java | 20 +++ .../java/ch/dissem/msgpack/ReaderTest.java | 74 ++++++++ src/test/resources/demo.json | 1 + src/test/resources/demo.mp | Bin 0 -> 18 bytes 22 files changed, 1384 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/ch/dissem/msgpack/Reader.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPArray.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPBinary.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPBoolean.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPDouble.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPFloat.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPInteger.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPMap.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPNil.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPString.java create mode 100644 src/main/java/ch/dissem/msgpack/types/MPType.java create mode 100644 src/main/java/ch/dissem/msgpack/types/Utils.java create mode 100644 src/test/java/ch/dissem/msgpack/ReaderTest.java create mode 100644 src/test/resources/demo.json create mode 100644 src/test/resources/demo.mp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98d31cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# Created by https://www.gitignore.io + +*.log + +### Gradle ### +.gradle +build/ +classes/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### Linux ### +*~ + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3dfcde7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,92 @@ +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'signing' +apply plugin: 'jacoco' +apply plugin: 'gitflow-version' + +sourceCompatibility = 1.7 +group = 'ch.dissem.jabit' + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' +} + +test { + testLogging { + exceptionFormat = 'full' + } +} + +task javadocJar(type: Jar) { + classifier = 'javadoc' + from javadoc +} + +task sourcesJar(type: Jar) { + classifier = 'sources' + from sourceSets.main.allSource +} + +artifacts { + archives javadocJar, sourcesJar +} + +signing { + required { isRelease && project.getProperties().get("signing.keyId")?.length() > 0 } + sign configurations.archives +} + +uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + + snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + + pom.project { + name 'msgpack' + packaging 'jar' + url 'https://github.com/Dissem/msgpack' + + scm { + connection 'scm:git:https://github.com/Dissem/msgpack.git' + developerConnection 'scm:git:git@github.com:Dissem/msgpack.git' + url 'https://github.com/Dissem/msgpack.git' + } + + licenses { + license { + name 'The Apache License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + + developers { + developer { + name 'Christian Basler' + email 'chrigu.meyer@gmail.com' + } + } + } + } + } +} + +jacocoTestReport { + reports { + xml.enabled = true + html.enabled = true + } +} + +check.dependsOn jacocoTestReport diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..6ffa237849ef3607e39c3b334a92a65367962071 GIT binary patch literal 52928 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an value, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..84c2605 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'msgpack' + diff --git a/src/main/java/ch/dissem/msgpack/Reader.java b/src/main/java/ch/dissem/msgpack/Reader.java new file mode 100644 index 0000000..51d1518 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/Reader.java @@ -0,0 +1,44 @@ +package ch.dissem.msgpack; + +import ch.dissem.msgpack.types.*; + +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; + +/** + * Reads MPType object from an {@link InputStream}. + */ +public class Reader { + private List> unpackers = new LinkedList>(); + + public Reader() { + unpackers.add(new MPNil.Unpacker()); + unpackers.add(new MPBoolean.Unpacker()); + unpackers.add(new MPInteger.Unpacker()); + unpackers.add(new MPFloat.Unpacker()); + unpackers.add(new MPDouble.Unpacker()); + unpackers.add(new MPString.Unpacker()); + unpackers.add(new MPBinary.Unpacker()); + unpackers.add(new MPMap.Unpacker(this)); + unpackers.add(new MPArray.Unpacker(this)); + } + + /** + * Register your own extensions + */ + public void register(MPType.Unpacker unpacker) { + unpackers.add(unpacker); + } + + public MPType read(InputStream in) throws IOException { + int firstByte = in.read(); + for (MPType.Unpacker unpacker : unpackers) { + if (unpacker.is(firstByte)) { + return unpacker.unpack(firstByte, in); + } + } + throw new IOException(String.format("Unsupported input, no reader for 0x%02x", firstByte)); + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPArray.java b/src/main/java/ch/dissem/msgpack/types/MPArray.java new file mode 100644 index 0000000..80bdad8 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPArray.java @@ -0,0 +1,101 @@ +package ch.dissem.msgpack.types; + +import ch.dissem.msgpack.Reader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.*; + +public class MPArray implements MPType> { + private List array; + + public MPArray(List array) { + this.array = array; + } + + public MPArray(T... objects) { + this.array = Arrays.asList(objects); + } + + public List getValue() { + return array; + } + + public void pack(OutputStream out) throws IOException { + int size = array.size(); + if (size < 16) { + out.write(0b10010000 + size); + } else if (size < 65536) { + out.write(0xDC); + out.write(ByteBuffer.allocate(2).putShort((short) size).array()); + } else { + out.write(0xDD); + out.write(ByteBuffer.allocate(4).putInt(size).array()); + } + for (MPType o : array) { + o.pack(out); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MPArray mpArray = (MPArray) o; + return Objects.equals(array, mpArray.array); + } + + @Override + public int hashCode() { + return Objects.hash(array); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append('['); + Iterator iterator = array.iterator(); + while (iterator.hasNext()) { + T item = iterator.next(); + result.append(item.toString()); + if (iterator.hasNext()) { + result.append(", "); + } + } + result.append(']'); + return result.toString(); + } + + public static class Unpacker implements MPType.Unpacker { + private final Reader reader; + + public Unpacker(Reader reader) { + this.reader = reader; + } + + public boolean is(int firstByte) { + return firstByte == 0xDC || firstByte == 0xDD || (firstByte & 0b11110000) == 0b10010000; + } + + public MPArray> unpack(int firstByte, InputStream in) throws IOException { + int size; + if ((firstByte & 0b11110000) == 0b10010000) { + size = firstByte & 0b00001111; + } else if (firstByte == 0xDC) { + size = in.read() << 8 | in.read(); + } else if (firstByte == 0xDD) { + size = in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); + } else { + throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); + } + List> list = new LinkedList<>(); + for (int i = 0; i < size; i++) { + MPType value = reader.read(in); + list.add(value); + } + return new MPArray<>(list); + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPBinary.java b/src/main/java/ch/dissem/msgpack/types/MPBinary.java new file mode 100644 index 0000000..2414c1e --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPBinary.java @@ -0,0 +1,74 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static ch.dissem.msgpack.types.Utils.bytes; + +public class MPBinary implements MPType { + private byte[] value; + + public MPBinary(byte[] value) { + this.value = value; + } + + public byte[] getValue() { + return value; + } + + public void pack(OutputStream out) throws IOException { + int size = value.length; + if (size < 256) { + out.write(0xC4); + out.write((byte) size); + } else if (size < 65536) { + out.write(0xC5); + out.write(ByteBuffer.allocate(2).putShort((short) size).array()); + } else { + out.write(0xC6); + out.write(ByteBuffer.allocate(4).putInt(size).array()); + } + out.write(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MPBinary mpBinary = (MPBinary) o; + return Arrays.equals(value, mpBinary.value); + } + + @Override + public int hashCode() { + return Arrays.hashCode(value); + } + + @Override + public String toString() { + return null; // TODO base64 + } + + public static class Unpacker implements MPType.Unpacker { + public boolean is(int firstByte) { + return firstByte == 0xC4 || firstByte == 0xC5 || firstByte == 0xC6; + } + + public MPBinary unpack(int firstByte, InputStream in) throws IOException { + int size; + if (firstByte == 0xC4) { + size = in.read() << 8 | in.read(); + } else if (firstByte == 0xC5) { + size = in.read() << 8 | in.read(); + } else if (firstByte == 0xC6) { + size = in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); + } else { + throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); + } + return new MPBinary(bytes(in, size).array()); + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java new file mode 100644 index 0000000..f54b4a8 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java @@ -0,0 +1,64 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Objects; + +public class MPBoolean implements MPType { + private final static int FALSE = 0xC2; + private final static int TRUE = 0xC3; + + private final boolean value; + + public MPBoolean(boolean value) { + this.value = value; + } + + public Boolean getValue() { + return value; + } + + public void pack(OutputStream out) throws IOException { + if (value) { + out.write(TRUE); + } else { + out.write(FALSE); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MPBoolean mpBoolean = (MPBoolean) o; + return value == mpBoolean.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static class Unpacker implements MPType.Unpacker { + + public boolean is(int firstByte) { + return firstByte == TRUE || firstByte == FALSE; + } + + public MPBoolean unpack(int firstByte, InputStream in) { + if (firstByte == TRUE) { + return new MPBoolean(true); + } else if (firstByte == FALSE) { + return new MPBoolean(false); + } else { + throw new IllegalArgumentException(String.format("0xC2 or 0xC3 expected but was 0x%02x", firstByte)); + } + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPDouble.java b/src/main/java/ch/dissem/msgpack/types/MPDouble.java new file mode 100644 index 0000000..89575cf --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPDouble.java @@ -0,0 +1,59 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +import static ch.dissem.msgpack.types.Utils.bytes; + +public class MPDouble implements MPType { + private double value; + + public MPDouble(double value) { + this.value = value; + } + + @Override + public Double getValue() { + return value; + } + + public void pack(OutputStream out) throws IOException { + out.write(0xCB); + out.write(ByteBuffer.allocate(8).putDouble(value).array()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MPDouble mpDouble = (MPDouble) o; + return Double.compare(mpDouble.value, value) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static class Unpacker implements MPType.Unpacker { + public boolean is(int firstByte) { + return firstByte == 0xCB; + } + + public MPDouble unpack(int firstByte, InputStream in) throws IOException { + if (firstByte == 0xCB) { + return new MPDouble(bytes(in, 8).getDouble()); + } else { + throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); + } + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPFloat.java b/src/main/java/ch/dissem/msgpack/types/MPFloat.java new file mode 100644 index 0000000..30a52a3 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPFloat.java @@ -0,0 +1,59 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +import static ch.dissem.msgpack.types.Utils.bytes; + +public class MPFloat implements MPType { + private float value; + + public MPFloat(float value) { + this.value = value; + } + + @Override + public Float getValue() { + return value; + } + + public void pack(OutputStream out) throws IOException { + out.write(0xCA); + out.write(ByteBuffer.allocate(4).putFloat(value).array()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MPFloat mpFloat = (MPFloat) o; + return Float.compare(mpFloat.value, value) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static class Unpacker implements MPType.Unpacker { + public boolean is(int firstByte) { + return firstByte == 0xCA; + } + + public MPFloat unpack(int firstByte, InputStream in) throws IOException { + if (firstByte == 0xCA) { + return new MPFloat(bytes(in, 4).getFloat()); + } else { + throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); + } + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPInteger.java b/src/main/java/ch/dissem/msgpack/types/MPInteger.java new file mode 100644 index 0000000..a58e8b7 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPInteger.java @@ -0,0 +1,124 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +import static ch.dissem.msgpack.types.Utils.bytes; + +public class MPInteger implements MPType { + private long value; + + public MPInteger(long value) { + this.value = value; + } + + @Override + public Long getValue() { + return value; + } + + public void pack(OutputStream out) throws IOException { + if ((value > ((byte) 0b11100000) && value < 0x80)) { + out.write((int) value); + } else if (value > 0) { + if (value <= 0xFF) { + out.write(0xCC); + out.write((int) value); + } else if (value <= 0xFFFF) { + out.write(0xCD); + out.write(ByteBuffer.allocate(2).putShort((short) value).array()); + } else if (value < 0xFFFFFFFFL) { + out.write(0xCE); + out.write(ByteBuffer.allocate(4).putInt((int) value).array()); + } else { + out.write(0xCF); + out.write(ByteBuffer.allocate(8).putLong(value).array()); + } + } else { + if (value >= Byte.MIN_VALUE) { + out.write(0xD0); + out.write(ByteBuffer.allocate(1).put((byte) value).array()); + } else if (value >= Short.MIN_VALUE) { + out.write(0xD1); + out.write(ByteBuffer.allocate(2).putShort((short) value).array()); + } else if (value >= Integer.MIN_VALUE) { + out.write(0xD2); + out.write(ByteBuffer.allocate(4).putInt((int) value).array()); + } else { + out.write(0xD3); + out.write(ByteBuffer.allocate(8).putLong(value).array()); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MPInteger mpInteger = (MPInteger) o; + return value == mpInteger.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return String.valueOf(value); + } + + public static class Unpacker implements MPType.Unpacker { + public boolean is(int firstByte) { + switch (firstByte) { + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + return true; + default: + return (firstByte & 0b10000000) == 0 || (firstByte & 0b11100000) == 0b11100000; + } + } + + public MPInteger unpack(int firstByte, InputStream in) throws IOException { + if ((firstByte & 0b10000000) == 0 || (firstByte & 0b11100000) == 0b11100000) { + return new MPInteger((byte) firstByte); + } else { + switch (firstByte) { + case 0xCC: + return new MPInteger(in.read()); + case 0xCD: + return new MPInteger(in.read() << 8 | in.read()); + case 0xCE: + return new MPInteger(in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read()); + case 0xCF: { + long value = 0; + for (int i = 0; i < 8; i++) { + value = value << 8 | in.read(); + } + return new MPInteger(value); + } + case 0xD0: + return new MPInteger(bytes(in, 1).get()); + case 0xD1: + return new MPInteger(bytes(in, 2).getShort()); + case 0xD2: + return new MPInteger(bytes(in, 4).getInt()); + case 0xD3: + return new MPInteger(bytes(in, 8).getLong()); + default: + throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); + } + } + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPMap.java b/src/main/java/ch/dissem/msgpack/types/MPMap.java new file mode 100644 index 0000000..a105149 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPMap.java @@ -0,0 +1,105 @@ +package ch.dissem.msgpack.types; + +import ch.dissem.msgpack.Reader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +public class MPMap implements MPType> { + private Map map; + + public MPMap(Map map) { + this.map = map; + } + + @Override + public Map getValue() { + return map; + } + + public void pack(OutputStream out) throws IOException { + int size = map.size(); + if (size < 16) { + out.write(0x80 + size); + } else if (size < 65536) { + out.write(0xDE); + out.write(ByteBuffer.allocate(2).putShort((short) size).array()); + } else { + out.write(0xDF); + out.write(ByteBuffer.allocate(4).putInt(size).array()); + } + for (Map.Entry e : map.entrySet()) { + e.getKey().pack(out); + e.getValue().pack(out); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MPMap mpMap = (MPMap) o; + return Objects.equals(map, mpMap.map); + } + + @Override + public int hashCode() { + return Objects.hash(map); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append('{'); + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry item = iterator.next(); + result.append(item.getKey().toString()); + result.append(": "); + result.append(item.getValue().toString()); + if (iterator.hasNext()) { + result.append(", "); + } + } + result.append('}'); + return result.toString(); + } + + public static class Unpacker implements MPType.Unpacker { + private final Reader reader; + + public Unpacker(Reader reader) { + this.reader = reader; + } + + public boolean is(int firstByte) { + return firstByte == 0xDE || firstByte == 0xDF || (firstByte & 0xF0) == 0x80; + } + + public MPMap, MPType> unpack(int firstByte, InputStream in) throws IOException { + int size; + if ((firstByte & 0xF0) == 0x80) { + size = firstByte & 0x0F; + } else if (firstByte == 0xDE) { + size = in.read() << 8 | in.read(); + } else if (firstByte == 0xDF) { + size = in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); + } else { + throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); + } + Map, MPType> map = new LinkedHashMap<>(); + for (int i = 0; i < size; i++) { + MPType key = reader.read(in); + MPType value = reader.read(in); + map.put(key, value); + } + return new MPMap<>(map); + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPNil.java b/src/main/java/ch/dissem/msgpack/types/MPNil.java new file mode 100644 index 0000000..27122e9 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPNil.java @@ -0,0 +1,46 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class MPNil implements MPType { + private final static int NIL = 0xC0; + + public Void getValue() { + return null; + } + + public void pack(OutputStream out) throws IOException { + out.write(NIL); + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object o) { + return o instanceof MPNil; + } + + @Override + public String toString() { + return "nil"; + } + + public static class Unpacker implements MPType.Unpacker { + + public boolean is(int firstByte) { + return firstByte == NIL; + } + + public MPNil unpack(int firstByte, InputStream in) { + if (firstByte != NIL) { + throw new IllegalArgumentException(String.format("0xC0 expected but was 0x%02x", firstByte)); + } + return new MPNil(); + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java new file mode 100644 index 0000000..bbee5cc --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -0,0 +1,78 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +import static ch.dissem.msgpack.types.Utils.bytes; + +public class MPString implements MPType { + private String value; + + public MPString(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void pack(OutputStream out) throws IOException { + int size = value.length(); + if (size < 16) { + out.write(0b10100000 + size); + } else if (size < 256) { + out.write(0xD9); + out.write((byte) size); + } else if (size < 65536) { + out.write(0xDA); + out.write(ByteBuffer.allocate(2).putShort((short) size).array()); + } else { + out.write(0xDB); + out.write(ByteBuffer.allocate(4).putInt(size).array()); + } + out.write(value.getBytes("UTF-8")); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MPString mpString = (MPString) o; + return Objects.equals(value, mpString.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return '"' + value + '"'; // FIXME: escape value + } + + public static class Unpacker implements MPType.Unpacker { + public boolean is(int firstByte) { + return firstByte == 0xD9 || firstByte == 0xDA || firstByte == 0xDB || (firstByte & 0b11100000) == 0b10100000; + } + + public MPString unpack(int firstByte, InputStream in) throws IOException { + int size; + if ((firstByte & 0b11100000) == 0b10100000) { + size = firstByte & 0b00011111; + } else if (firstByte == 0xD9) { + size = in.read() << 8 | in.read(); + } else if (firstByte == 0xDA) { + size = in.read() << 8 | in.read(); + } else if (firstByte == 0xDB) { + size = in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); + } else { + throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); + } + return new MPString(new String(bytes(in, size).array(), "UTF-8")); + } + } +} diff --git a/src/main/java/ch/dissem/msgpack/types/MPType.java b/src/main/java/ch/dissem/msgpack/types/MPType.java new file mode 100644 index 0000000..d85b379 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/MPType.java @@ -0,0 +1,20 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Representation for some msgpack encoded data. + */ +public interface MPType { + interface Unpacker { + boolean is(int firstByte); + + M unpack(int firstByte, InputStream in) throws IOException; + } + + T getValue(); + + void pack(OutputStream out) throws IOException; +} diff --git a/src/main/java/ch/dissem/msgpack/types/Utils.java b/src/main/java/ch/dissem/msgpack/types/Utils.java new file mode 100644 index 0000000..de7ff00 --- /dev/null +++ b/src/main/java/ch/dissem/msgpack/types/Utils.java @@ -0,0 +1,20 @@ +package ch.dissem.msgpack.types; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +class Utils { + static ByteBuffer bytes(InputStream in, int count) throws IOException { + byte[] result = new byte[count]; + int off = 0; + while (off < count) { + int read = in.read(result, off, count - off); + if (read < 0) { + throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off); + } + off += read; + } + return ByteBuffer.wrap(result); + } +} diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java new file mode 100644 index 0000000..cb7975d --- /dev/null +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -0,0 +1,74 @@ +package ch.dissem.msgpack; + +import ch.dissem.msgpack.types.*; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedHashMap; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class ReaderTest { + private Reader reader = new Reader(); + + @Test + public void ensureDemoJsonIsParsedCorrectly() throws Exception { + MPType read = reader.read(stream("demo.mp")); + assertThat(read, instanceOf(MPMap.class)); + assertThat(read.toString(), is(string("demo.json"))); + } + + @Test + public void ensureDemoJsonIsEncodedCorrectly() throws Exception { + MPMap> object = new MPMap<>(new LinkedHashMap>()); + object.getValue().put(new MPString("compact"), new MPBoolean(true)); + object.getValue().put(new MPString("schema"), new MPInteger(0)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + object.pack(out); + assertThat(out.toByteArray(), is(bytes("demo.mp"))); + } + + @Test + @SuppressWarnings("unchecked") + public void ensureMPArrayIsEncodedAndDecodedCorrectly() throws Exception { + MPArray> array = new MPArray<>( +// new MPBinary(new byte[]{1, 3, 3, 7}), + new MPBoolean(false), + new MPDouble(Math.PI), + new MPFloat(1.5f), + new MPInteger(42), + new MPMap<>(new HashMap()), + new MPNil(), + new MPString("yay!") // TODO: emoji + ); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + array.pack(out); + MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); + assertThat(read, instanceOf(MPArray.class)); + assertThat((MPArray>) read, is(array)); + } + + private InputStream stream(String resource) { + return getClass().getClassLoader().getResourceAsStream(resource); + } + + private byte[] bytes(String resource) throws IOException { + InputStream in = stream(resource); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[100]; + for (int size = in.read(buffer); size >= 0; size = in.read(buffer)) { + out.write(buffer, 0, size); + } + return out.toByteArray(); + } + + private String string(String resource) throws IOException { + return new String(bytes(resource), "UTF-8"); + } +} diff --git a/src/test/resources/demo.json b/src/test/resources/demo.json new file mode 100644 index 0000000..b091f84 --- /dev/null +++ b/src/test/resources/demo.json @@ -0,0 +1 @@ +{"compact": true, "schema": 0} \ No newline at end of file diff --git a/src/test/resources/demo.mp b/src/test/resources/demo.mp new file mode 100644 index 0000000000000000000000000000000000000000..87ce865628f6f6bd647efd3a9e0b675d503fb319 GIT binary patch literal 18 ZcmZo#o}8askeFO@cv*3BMrv*%0{~GV2kQU; literal 0 HcmV?d00001 From 4b79e232d091eaf09cc112069d2fd657506a98a0 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 20 Jan 2017 08:23:58 +0100 Subject: [PATCH 02/20] Added tests and fixed some bugs --- .../ch/dissem/msgpack/types/MPBinary.java | 2 +- .../ch/dissem/msgpack/types/MPString.java | 6 +- .../java/ch/dissem/msgpack/ReaderTest.java | 100 +++++++++++++++++- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/dissem/msgpack/types/MPBinary.java b/src/main/java/ch/dissem/msgpack/types/MPBinary.java index 2414c1e..3999741 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBinary.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBinary.java @@ -60,7 +60,7 @@ public class MPBinary implements MPType { public MPBinary unpack(int firstByte, InputStream in) throws IOException { int size; if (firstByte == 0xC4) { - size = in.read() << 8 | in.read(); + size = in.read(); } else if (firstByte == 0xC5) { size = in.read() << 8 | in.read(); } else if (firstByte == 0xC6) { diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java index bbee5cc..741a137 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPString.java +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -21,11 +21,11 @@ public class MPString implements MPType { public void pack(OutputStream out) throws IOException { int size = value.length(); - if (size < 16) { + if (size < 32) { out.write(0b10100000 + size); } else if (size < 256) { out.write(0xD9); - out.write((byte) size); + out.write(size); } else if (size < 65536) { out.write(0xDA); out.write(ByteBuffer.allocate(2).putShort((short) size).array()); @@ -64,7 +64,7 @@ public class MPString implements MPType { if ((firstByte & 0b11100000) == 0b10100000) { size = firstByte & 0b00011111; } else if (firstByte == 0xD9) { - size = in.read() << 8 | in.read(); + size = in.read(); } else if (firstByte == 0xDA) { size = in.read() << 8 | in.read(); } else if (firstByte == 0xDB) { diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index cb7975d..64bbd4d 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -7,14 +7,17 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.Random; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; public class ReaderTest { + private static final Random RANDOM = new Random(); private Reader reader = new Reader(); @Test @@ -38,7 +41,7 @@ public class ReaderTest { @SuppressWarnings("unchecked") public void ensureMPArrayIsEncodedAndDecodedCorrectly() throws Exception { MPArray> array = new MPArray<>( -// new MPBinary(new byte[]{1, 3, 3, 7}), + new MPBinary(new byte[]{1, 3, 3, 7}), new MPBoolean(false), new MPDouble(Math.PI), new MPFloat(1.5f), @@ -54,6 +57,101 @@ public class ReaderTest { assertThat((MPArray>) read, is(array)); } + @Test + public void ensureStringsAreEncodedAndDecodedCorrectly() throws Exception { + ensureStringIsEncodedAndDecodedCorrectly(0); + ensureStringIsEncodedAndDecodedCorrectly(31); + ensureStringIsEncodedAndDecodedCorrectly(32); + ensureStringIsEncodedAndDecodedCorrectly(255); + ensureStringIsEncodedAndDecodedCorrectly(256); + ensureStringIsEncodedAndDecodedCorrectly(65535); + ensureStringIsEncodedAndDecodedCorrectly(65536); + } + + private void ensureStringIsEncodedAndDecodedCorrectly(int length) throws Exception { + MPString value = new MPString(stringWithLength(length)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + value.pack(out); + MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); + assertThat(read, instanceOf(MPString.class)); + assertThat((MPString) read, is(value)); + } + + @Test + public void ensureBinariesAreEncodedAndDecodedCorrectly() throws Exception { + ensureBinaryIsEncodedAndDecodedCorrectly(0); + ensureBinaryIsEncodedAndDecodedCorrectly(255); + ensureBinaryIsEncodedAndDecodedCorrectly(256); + ensureBinaryIsEncodedAndDecodedCorrectly(65535); + ensureBinaryIsEncodedAndDecodedCorrectly(65536); + } + + private void ensureBinaryIsEncodedAndDecodedCorrectly(int length) throws Exception { + MPBinary value = new MPBinary(new byte[length]); + RANDOM.nextBytes(value.getValue()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + value.pack(out); + MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); + assertThat(read, instanceOf(MPBinary.class)); + assertThat((MPBinary) read, is(value)); + } + + @Test + public void ensureArraysAreEncodedAndDecodedCorrectly() throws Exception { + ensureArrayIsEncodedAndDecodedCorrectly(0); + ensureArrayIsEncodedAndDecodedCorrectly(15); + ensureArrayIsEncodedAndDecodedCorrectly(16); + ensureArrayIsEncodedAndDecodedCorrectly(65535); + ensureArrayIsEncodedAndDecodedCorrectly(65536); + } + + @SuppressWarnings("unchecked") + private void ensureArrayIsEncodedAndDecodedCorrectly(int length) throws Exception { + MPNil nil = new MPNil(); + ArrayList list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(nil); + } + MPArray value = new MPArray<>(list); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + value.pack(out); + MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); + assertThat(read, instanceOf(MPArray.class)); + assertThat((MPArray) read, is(value)); + } + + @Test + public void ensureMapsAreEncodedAndDecodedCorrectly() throws Exception { + ensureMapIsEncodedAndDecodedCorrectly(0); + ensureMapIsEncodedAndDecodedCorrectly(15); + ensureMapIsEncodedAndDecodedCorrectly(16); + ensureMapIsEncodedAndDecodedCorrectly(65535); + ensureMapIsEncodedAndDecodedCorrectly(65536); + } + + @SuppressWarnings("unchecked") + private void ensureMapIsEncodedAndDecodedCorrectly(int length) throws Exception { + MPNil nil = new MPNil(); + HashMap map = new HashMap<>(length); + for (int i = 0; i < length; i++) { + map.put(nil, nil); + } + MPMap value = new MPMap<>(map); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + value.pack(out); + MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); + assertThat(read, instanceOf(MPMap.class)); + assertThat((MPMap) read, is(value)); + } + + private String stringWithLength(int length) { + StringBuilder result = new StringBuilder(length); + for (int i = 0; i < length; i++) { + result.append('a'); + } + return result.toString(); + } + private InputStream stream(String resource) { return getClass().getClassLoader().getResourceAsStream(resource); } From 30da667fdb838c6e46961a48d6095b5f64bd15ac Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 20 Jan 2017 22:43:44 +0100 Subject: [PATCH 03/20] Added comments, implemented interfaces for some added convenience. --- .../java/ch/dissem/msgpack/types/MPArray.java | 127 +++++++++++++++++- .../ch/dissem/msgpack/types/MPBinary.java | 3 + .../ch/dissem/msgpack/types/MPBoolean.java | 3 + .../ch/dissem/msgpack/types/MPDouble.java | 3 + .../java/ch/dissem/msgpack/types/MPFloat.java | 3 + .../ch/dissem/msgpack/types/MPInteger.java | 6 + .../java/ch/dissem/msgpack/types/MPMap.java | 78 ++++++++++- .../java/ch/dissem/msgpack/types/MPNil.java | 5 +- .../ch/dissem/msgpack/types/MPString.java | 80 ++++++++--- .../java/ch/dissem/msgpack/types/MPType.java | 2 +- .../java/ch/dissem/msgpack/types/Utils.java | 3 + .../java/ch/dissem/msgpack/ReaderTest.java | 6 +- 12 files changed, 291 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/dissem/msgpack/types/MPArray.java b/src/main/java/ch/dissem/msgpack/types/MPArray.java index 80bdad8..6f8c82e 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPArray.java +++ b/src/main/java/ch/dissem/msgpack/types/MPArray.java @@ -8,9 +8,19 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.*; -public class MPArray implements MPType> { +/** + * Representation of a msgpack encoded array. Uses a list to represent data internally, and implements the {@link List} + * interface for your convenience. + * + * @param + */ +public class MPArray implements MPType>, List { private List array; + public MPArray() { + this.array = new LinkedList<>(); + } + public MPArray(List array) { this.array = array; } @@ -39,6 +49,76 @@ public class MPArray implements MPType> { } } + @Override + public int size() { + return array.size(); + } + + @Override + public boolean isEmpty() { + return array.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return array.contains(o); + } + + @Override + public Iterator iterator() { + return array.iterator(); + } + + @Override + public Object[] toArray() { + return array.toArray(); + } + + @Override + public T1[] toArray(T1[] t1s) { + return array.toArray(t1s); + } + + @Override + public boolean add(T t) { + return array.add(t); + } + + @Override + public boolean remove(Object o) { + return array.remove(o); + } + + @Override + public boolean containsAll(Collection collection) { + return array.containsAll(collection); + } + + @Override + public boolean addAll(Collection collection) { + return array.addAll(collection); + } + + @Override + public boolean addAll(int i, Collection collection) { + return array.addAll(i, collection); + } + + @Override + public boolean removeAll(Collection collection) { + return array.removeAll(collection); + } + + @Override + public boolean retainAll(Collection collection) { + return array.retainAll(collection); + } + + @Override + public void clear() { + array.clear(); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -52,6 +132,51 @@ public class MPArray implements MPType> { return Objects.hash(array); } + @Override + public T get(int i) { + return array.get(i); + } + + @Override + public T set(int i, T t) { + return array.set(i, t); + } + + @Override + public void add(int i, T t) { + array.add(i, t); + } + + @Override + public T remove(int i) { + return array.remove(i); + } + + @Override + public int indexOf(Object o) { + return array.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return array.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return array.listIterator(); + } + + @Override + public ListIterator listIterator(int i) { + return array.listIterator(i); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return array.subList(fromIndex, toIndex); + } + @Override public String toString() { StringBuilder result = new StringBuilder(); diff --git a/src/main/java/ch/dissem/msgpack/types/MPBinary.java b/src/main/java/ch/dissem/msgpack/types/MPBinary.java index 3999741..600c306 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBinary.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBinary.java @@ -8,6 +8,9 @@ import java.util.Arrays; import static ch.dissem.msgpack.types.Utils.bytes; +/** + * Representation of msgpack encoded binary data a.k.a. byte array. + */ public class MPBinary implements MPType { private byte[] value; diff --git a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java index f54b4a8..a9e4f53 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java @@ -5,6 +5,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Objects; +/** + * Representation of a msgpack encoded boolean. + */ public class MPBoolean implements MPType { private final static int FALSE = 0xC2; private final static int TRUE = 0xC3; diff --git a/src/main/java/ch/dissem/msgpack/types/MPDouble.java b/src/main/java/ch/dissem/msgpack/types/MPDouble.java index 89575cf..69fb041 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPDouble.java +++ b/src/main/java/ch/dissem/msgpack/types/MPDouble.java @@ -8,6 +8,9 @@ import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; +/** + * Representation of a msgpack encoded float64 number. + */ public class MPDouble implements MPType { private double value; diff --git a/src/main/java/ch/dissem/msgpack/types/MPFloat.java b/src/main/java/ch/dissem/msgpack/types/MPFloat.java index 30a52a3..965cb72 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPFloat.java +++ b/src/main/java/ch/dissem/msgpack/types/MPFloat.java @@ -8,6 +8,9 @@ import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; +/** + * Representation of a msgpack encoded float32 number. + */ public class MPFloat implements MPType { private float value; diff --git a/src/main/java/ch/dissem/msgpack/types/MPInteger.java b/src/main/java/ch/dissem/msgpack/types/MPInteger.java index a58e8b7..7cc17a1 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPInteger.java +++ b/src/main/java/ch/dissem/msgpack/types/MPInteger.java @@ -8,6 +8,12 @@ import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; +/** + * Representation of a msgpack encoded integer. The encoding is automatically selected according to the value's size. + * Uses long due to the fact that the msgpack integer implementation may contain up to 64 bit numbers, corresponding + * to Java long values. Also note that uint64 values may be too large for signed long (thanks Java for not supporting + * unsigned values) and end in a negative value. + */ public class MPInteger implements MPType { private long value; diff --git a/src/main/java/ch/dissem/msgpack/types/MPMap.java b/src/main/java/ch/dissem/msgpack/types/MPMap.java index a105149..7424063 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPMap.java +++ b/src/main/java/ch/dissem/msgpack/types/MPMap.java @@ -6,14 +6,22 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; +import java.util.*; -public class MPMap implements MPType> { +/** + * Representation of a msgpack encoded map. It is recommended to use a {@link LinkedHashMap} to ensure the order + * of entries. For convenience, it also implements the {@link Map} interface. + * + * @param + * @param + */ +public class MPMap implements MPType>, Map { private Map map; + public MPMap() { + this.map = new LinkedHashMap<>(); + } + public MPMap(Map map) { this.map = map; } @@ -40,6 +48,66 @@ public class MPMap implements MPType map) { + this.map.putAll(map); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/ch/dissem/msgpack/types/MPNil.java b/src/main/java/ch/dissem/msgpack/types/MPNil.java index 27122e9..d77af46 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPNil.java +++ b/src/main/java/ch/dissem/msgpack/types/MPNil.java @@ -4,6 +4,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +/** + * Representation of msgpack encoded nil / null. + */ public class MPNil implements MPType { private final static int NIL = 0xC0; @@ -27,7 +30,7 @@ public class MPNil implements MPType { @Override public String toString() { - return "nil"; + return "null"; } public static class Unpacker implements MPType.Unpacker { diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java index 741a137..3fc17c3 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPString.java +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -4,12 +4,42 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; -public class MPString implements MPType { - private String value; +/** + * Representation of a msgpack encoded string. The encoding is automatically selected according to the string's length. + *

+ * The default encoding is UTF-8. + *

+ */ +public class MPString implements MPType, CharSequence { + private static final int FIXSTR_PREFIX = 0b10100000; + private static final int FIXSTR_PREFIX_FILTER = 0b11100000; + private static final int STR8_PREFIX = 0xD9; + private static final int STR8_LIMIT = 256; + private static final int STR16_PREFIX = 0xDA; + private static final int STR16_LIMIT = 65536; + private static final int STR32_PREFIX = 0xDB; + private static final int FIXSTR_FILTER = 0b00011111; + + private static Charset encoding = Charset.forName("UTF-8"); + + /** + * Use this method if for some messed up reason you really need to use something else than UTF-8. + * Ask yourself: why should I? Is this really necessary? + *

+ * It will set the encoding for all {@link MPString}s, but if you have inconsistent encoding in your + * format you're lost anyway. + *

+ */ + public static void setEncoding(Charset encoding) { + MPString.encoding = encoding; + } + + private final String value; public MPString(String value) { this.value = value; @@ -22,18 +52,18 @@ public class MPString implements MPType { public void pack(OutputStream out) throws IOException { int size = value.length(); if (size < 32) { - out.write(0b10100000 + size); - } else if (size < 256) { - out.write(0xD9); + out.write(FIXSTR_PREFIX + size); + } else if (size < STR8_LIMIT) { + out.write(STR8_PREFIX); out.write(size); - } else if (size < 65536) { - out.write(0xDA); + } else if (size < STR16_LIMIT) { + out.write(STR16_PREFIX); out.write(ByteBuffer.allocate(2).putShort((short) size).array()); } else { - out.write(0xDB); + out.write(STR32_PREFIX); out.write(ByteBuffer.allocate(4).putInt(size).array()); } - out.write(value.getBytes("UTF-8")); + out.write(value.getBytes(encoding)); } @Override @@ -49,30 +79,46 @@ public class MPString implements MPType { return Objects.hash(value); } + @Override + public int length() { + return value.length(); + } + + @Override + public char charAt(int i) { + return value.charAt(i); + } + + @Override + public CharSequence subSequence(int beginIndex, int endIndex) { + return value.subSequence(beginIndex, endIndex); + } + @Override public String toString() { - return '"' + value + '"'; // FIXME: escape value + return value; } public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { - return firstByte == 0xD9 || firstByte == 0xDA || firstByte == 0xDB || (firstByte & 0b11100000) == 0b10100000; + return firstByte == STR8_PREFIX || firstByte == STR16_PREFIX || firstByte == STR32_PREFIX + || (firstByte & FIXSTR_PREFIX_FILTER) == FIXSTR_PREFIX; } public MPString unpack(int firstByte, InputStream in) throws IOException { int size; - if ((firstByte & 0b11100000) == 0b10100000) { - size = firstByte & 0b00011111; - } else if (firstByte == 0xD9) { + if ((firstByte & FIXSTR_PREFIX_FILTER) == FIXSTR_PREFIX) { + size = firstByte & FIXSTR_FILTER; + } else if (firstByte == STR8_PREFIX) { size = in.read(); - } else if (firstByte == 0xDA) { + } else if (firstByte == STR16_PREFIX) { size = in.read() << 8 | in.read(); - } else if (firstByte == 0xDB) { + } else if (firstByte == STR32_PREFIX) { size = in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); } else { throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); } - return new MPString(new String(bytes(in, size).array(), "UTF-8")); + return new MPString(new String(bytes(in, size).array(), encoding)); } } } diff --git a/src/main/java/ch/dissem/msgpack/types/MPType.java b/src/main/java/ch/dissem/msgpack/types/MPType.java index d85b379..9136223 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPType.java +++ b/src/main/java/ch/dissem/msgpack/types/MPType.java @@ -5,7 +5,7 @@ import java.io.InputStream; import java.io.OutputStream; /** - * Representation for some msgpack encoded data. + * Representation of some msgpack encoded data. */ public interface MPType { interface Unpacker { diff --git a/src/main/java/ch/dissem/msgpack/types/Utils.java b/src/main/java/ch/dissem/msgpack/types/Utils.java index de7ff00..641076b 100644 --- a/src/main/java/ch/dissem/msgpack/types/Utils.java +++ b/src/main/java/ch/dissem/msgpack/types/Utils.java @@ -5,6 +5,9 @@ import java.io.InputStream; import java.nio.ByteBuffer; class Utils { + /** + * Returns a {@link ByteBuffer} containing the next count bytes from the {@link InputStream}. + */ static ByteBuffer bytes(InputStream in, int count) throws IOException { byte[] result = new byte[count]; int off = 0; diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index 64bbd4d..90b36f6 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -29,9 +29,9 @@ public class ReaderTest { @Test public void ensureDemoJsonIsEncodedCorrectly() throws Exception { - MPMap> object = new MPMap<>(new LinkedHashMap>()); - object.getValue().put(new MPString("compact"), new MPBoolean(true)); - object.getValue().put(new MPString("schema"), new MPInteger(0)); + MPMap> object = new MPMap<>(); + object.put(new MPString("compact"), new MPBoolean(true)); + object.put(new MPString("schema"), new MPInteger(0)); ByteArrayOutputStream out = new ByteArrayOutputStream(); object.pack(out); assertThat(out.toByteArray(), is(bytes("demo.mp"))); From d1215c6e12147998b099f4828ccbda12b680c7e6 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 23 Jan 2017 12:49:27 +0100 Subject: [PATCH 04/20] Introduced toJson() method --- .../java/ch/dissem/msgpack/types/MPArray.java | 21 +++++++-- .../ch/dissem/msgpack/types/MPBinary.java | 7 ++- .../ch/dissem/msgpack/types/MPBoolean.java | 5 ++ .../ch/dissem/msgpack/types/MPDouble.java | 5 ++ .../java/ch/dissem/msgpack/types/MPFloat.java | 5 ++ .../ch/dissem/msgpack/types/MPInteger.java | 5 ++ .../java/ch/dissem/msgpack/types/MPMap.java | 22 +++++++-- .../java/ch/dissem/msgpack/types/MPNil.java | 5 ++ .../ch/dissem/msgpack/types/MPString.java | 44 +++++++++++++++++ .../java/ch/dissem/msgpack/types/MPType.java | 2 + .../java/ch/dissem/msgpack/types/Utils.java | 47 +++++++++++++++++++ src/test/resources/demo.json | 5 +- 12 files changed, 162 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/dissem/msgpack/types/MPArray.java b/src/main/java/ch/dissem/msgpack/types/MPArray.java index 6f8c82e..f83cc39 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPArray.java +++ b/src/main/java/ch/dissem/msgpack/types/MPArray.java @@ -25,6 +25,7 @@ public class MPArray implements MPType>, List { this.array = array; } + @SafeVarargs public MPArray(T... objects) { this.array = Arrays.asList(objects); } @@ -179,17 +180,29 @@ public class MPArray implements MPType>, List { @Override public String toString() { + return toJson(); + } + + @Override + public String toJson() { + return toJson(""); + } + + String toJson(String indent) { StringBuilder result = new StringBuilder(); - result.append('['); + result.append("[\n"); Iterator iterator = array.iterator(); + String indent2 = indent + " "; while (iterator.hasNext()) { T item = iterator.next(); - result.append(item.toString()); + result.append(indent2); + result.append(Utils.toJson(item, indent2)); if (iterator.hasNext()) { - result.append(", "); + result.append(','); } + result.append('\n'); } - result.append(']'); + result.append("]"); return result.toString(); } diff --git a/src/main/java/ch/dissem/msgpack/types/MPBinary.java b/src/main/java/ch/dissem/msgpack/types/MPBinary.java index 600c306..163d724 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBinary.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBinary.java @@ -52,7 +52,12 @@ public class MPBinary implements MPType { @Override public String toString() { - return null; // TODO base64 + return toJson(); + } + + @Override + public String toJson() { + return Utils.base64(value); } public static class Unpacker implements MPType.Unpacker { diff --git a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java index a9e4f53..fa95460 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java @@ -48,6 +48,11 @@ public class MPBoolean implements MPType { return String.valueOf(value); } + @Override + public String toJson() { + return String.valueOf(value); + } + public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { diff --git a/src/main/java/ch/dissem/msgpack/types/MPDouble.java b/src/main/java/ch/dissem/msgpack/types/MPDouble.java index 69fb041..7407a23 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPDouble.java +++ b/src/main/java/ch/dissem/msgpack/types/MPDouble.java @@ -46,6 +46,11 @@ public class MPDouble implements MPType { return String.valueOf(value); } + @Override + public String toJson() { + return String.valueOf(value); + } + public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { return firstByte == 0xCB; diff --git a/src/main/java/ch/dissem/msgpack/types/MPFloat.java b/src/main/java/ch/dissem/msgpack/types/MPFloat.java index 965cb72..8d31318 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPFloat.java +++ b/src/main/java/ch/dissem/msgpack/types/MPFloat.java @@ -46,6 +46,11 @@ public class MPFloat implements MPType { return String.valueOf(value); } + @Override + public String toJson() { + return String.valueOf(value); + } + public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { return firstByte == 0xCA; diff --git a/src/main/java/ch/dissem/msgpack/types/MPInteger.java b/src/main/java/ch/dissem/msgpack/types/MPInteger.java index 7cc17a1..c8bf051 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPInteger.java +++ b/src/main/java/ch/dissem/msgpack/types/MPInteger.java @@ -78,6 +78,11 @@ public class MPInteger implements MPType { return String.valueOf(value); } + @Override + public String toJson() { + return String.valueOf(value); + } + public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { switch (firstByte) { diff --git a/src/main/java/ch/dissem/msgpack/types/MPMap.java b/src/main/java/ch/dissem/msgpack/types/MPMap.java index 7424063..f8e8d3d 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPMap.java +++ b/src/main/java/ch/dissem/msgpack/types/MPMap.java @@ -123,22 +123,34 @@ public class MPMap implements MPType> iterator = map.entrySet().iterator(); + String indent2 = indent + " "; while (iterator.hasNext()) { Map.Entry item = iterator.next(); - result.append(item.getKey().toString()); + result.append(indent2); + result.append(Utils.toJson(item.getKey(), indent2)); result.append(": "); - result.append(item.getValue().toString()); + result.append(Utils.toJson(item.getValue(), indent2)); if (iterator.hasNext()) { - result.append(", "); + result.append(','); } + result.append('\n'); } - result.append('}'); + result.append(indent).append("}"); return result.toString(); } + @Override + public String toJson() { + return toJson(""); + } + public static class Unpacker implements MPType.Unpacker { private final Reader reader; diff --git a/src/main/java/ch/dissem/msgpack/types/MPNil.java b/src/main/java/ch/dissem/msgpack/types/MPNil.java index d77af46..5243115 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPNil.java +++ b/src/main/java/ch/dissem/msgpack/types/MPNil.java @@ -33,6 +33,11 @@ public class MPNil implements MPType { return "null"; } + @Override + public String toJson() { + return "null"; + } + public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java index 3fc17c3..0b0db09 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPString.java +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -99,6 +99,50 @@ public class MPString implements MPType, CharSequence { return value; } + @Override + public String toJson() { + StringBuilder result = new StringBuilder(value.length() + 4); + result.append('"'); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + switch (c) { + case '\\': + case '"': + case '/': + result.append('\\').append(c); + break; + case '\b': + result.append("\\b"); + break; + case '\t': + result.append("\\t"); + break; + case '\n': + result.append("\\n"); + break; + case '\f': + result.append("\\f"); + break; + case '\r': + result.append("\\r"); + break; + default: + if (c < ' ') { + result.append("\\u"); + String hex = Integer.toHexString(c); + for (int j = 0; j - hex.length() < 4; j++) { + result.append('0'); + } + result.append(hex); + } else { + result.append(c); + } + } + } + result.append('"'); + return result.toString(); + } + public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { return firstByte == STR8_PREFIX || firstByte == STR16_PREFIX || firstByte == STR32_PREFIX diff --git a/src/main/java/ch/dissem/msgpack/types/MPType.java b/src/main/java/ch/dissem/msgpack/types/MPType.java index 9136223..2e62fe5 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPType.java +++ b/src/main/java/ch/dissem/msgpack/types/MPType.java @@ -17,4 +17,6 @@ public interface MPType { T getValue(); void pack(OutputStream out) throws IOException; + + String toJson(); } diff --git a/src/main/java/ch/dissem/msgpack/types/Utils.java b/src/main/java/ch/dissem/msgpack/types/Utils.java index 641076b..b90b610 100644 --- a/src/main/java/ch/dissem/msgpack/types/Utils.java +++ b/src/main/java/ch/dissem/msgpack/types/Utils.java @@ -5,6 +5,8 @@ import java.io.InputStream; import java.nio.ByteBuffer; class Utils { + private static final char[] BASE64_CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(); + /** * Returns a {@link ByteBuffer} containing the next count bytes from the {@link InputStream}. */ @@ -20,4 +22,49 @@ class Utils { } return ByteBuffer.wrap(result); } + + /** + * Helper method to decide which types support extra indention (for pretty printing JSON) + */ + static String toJson(MPType type, String indent) { + if (type instanceof MPMap) { + return ((MPMap) type).toJson(indent); + } + if (type instanceof MPArray) { + return ((MPArray) type).toJson(indent); + } + return type.toJson(); + } + + /** + * Slightly improved code from https://en.wikipedia.org/wiki/Base64 + */ + static String base64(byte[] data) { + StringBuilder result = new StringBuilder((data.length * 4) / 3 + 3); + int b; + for (int i = 0; i < data.length; i += 3) { + b = (data[i] & 0xFC) >> 2; + result.append(BASE64_CODES[b]); + b = (data[i] & 0x03) << 4; + if (i + 1 < data.length) { + b |= (data[i + 1] & 0xF0) >> 4; + result.append(BASE64_CODES[b]); + b = (data[i + 1] & 0x0F) << 2; + if (i + 2 < data.length) { + b |= (data[i + 2] & 0xC0) >> 6; + result.append(BASE64_CODES[b]); + b = data[i + 2] & 0x3F; + result.append(BASE64_CODES[b]); + } else { + result.append(BASE64_CODES[b]); + result.append('='); + } + } else { + result.append(BASE64_CODES[b]); + result.append("=="); + } + } + + return result.toString(); + } } diff --git a/src/test/resources/demo.json b/src/test/resources/demo.json index b091f84..6118e1d 100644 --- a/src/test/resources/demo.json +++ b/src/test/resources/demo.json @@ -1 +1,4 @@ -{"compact": true, "schema": 0} \ No newline at end of file +{ + "compact": true, + "schema": 0 +} \ No newline at end of file From e6b35c5f5ba9ecb0ede638d552b6235b8afe8be6 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 23 Jan 2017 17:26:33 +0100 Subject: [PATCH 05/20] Added gitflow version plugin --- build.gradle | 4 +- .../ch/dissem/gradle/GitFlowVersion.groovy | 57 +++++++++++++++++++ .../gradle-plugins/gitflow-version.properties | 1 + 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 buildSrc/src/main/groovy/ch/dissem/gradle/GitFlowVersion.groovy create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/gitflow-version.properties diff --git a/build.gradle b/build.gradle index 3dfcde7..903abe3 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'jacoco' apply plugin: 'gitflow-version' sourceCompatibility = 1.7 -group = 'ch.dissem.jabit' +group = 'ch.dissem.msgpack' repositories { mavenCentral() @@ -56,7 +56,7 @@ uploadArchives { pom.project { name 'msgpack' packaging 'jar' - url 'https://github.com/Dissem/msgpack' + url 'https://dissem.ch/msgpack' scm { connection 'scm:git:https://github.com/Dissem/msgpack.git' diff --git a/buildSrc/src/main/groovy/ch/dissem/gradle/GitFlowVersion.groovy b/buildSrc/src/main/groovy/ch/dissem/gradle/GitFlowVersion.groovy new file mode 100644 index 0000000..869d57e --- /dev/null +++ b/buildSrc/src/main/groovy/ch/dissem/gradle/GitFlowVersion.groovy @@ -0,0 +1,57 @@ +package ch.dissem.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * Sets the version as follows: + *
    + *
  • If the branch is 'master', the version is set to the latest tag (which is expected to be set by Git flow)
  • + *
  • Otherwise, the version is set to the branch name, with '-SNAPSHOT' appended
  • + *
+ */ +class GitFlowVersion implements Plugin { + def getBranch(Project project) { + def stdout = new ByteArrayOutputStream() + project.exec { + commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD' + standardOutput = stdout + } + return stdout.toString().trim() + } + + def getTag(Project project) { + def stdout = new ByteArrayOutputStream() + project.exec { + commandLine 'git', 'describe', '--abbrev=0' + standardOutput = stdout + } + return stdout.toString().trim() + } + + def isRelease(Project project) { + return "master" == getBranch(project); + } + + def getVersion(Project project) { + if (project.ext.isRelease) { + return getTag(project) + } else { + def branch = getBranch(project) + if ("develop" == branch) { + return "development-SNAPSHOT" + } + return branch.replaceAll("/", "-") + "-SNAPSHOT" + } + } + + @Override + void apply(Project project) { + project.ext.isRelease = isRelease(project) + project.version = getVersion(project) + + project.task('version') << { + println "Version deduced from git: '${project.version}'" + } + } +} diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/gitflow-version.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/gitflow-version.properties new file mode 100644 index 0000000..1fb4f78 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/gitflow-version.properties @@ -0,0 +1 @@ +implementation-class=ch.dissem.gradle.GitFlowVersion From def99f7a7c1892f00b0f9664ffa1fcc1f4bbb78f Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 23 Jan 2017 17:38:02 +0100 Subject: [PATCH 06/20] Added license (Apache 2.0) --- LICENSE | 201 ++++++++++++++++++ src/main/java/ch/dissem/msgpack/Reader.java | 18 +- .../java/ch/dissem/msgpack/types/MPArray.java | 17 ++ .../ch/dissem/msgpack/types/MPBinary.java | 16 ++ .../ch/dissem/msgpack/types/MPBoolean.java | 16 ++ .../ch/dissem/msgpack/types/MPDouble.java | 16 ++ .../java/ch/dissem/msgpack/types/MPFloat.java | 16 ++ .../ch/dissem/msgpack/types/MPInteger.java | 16 ++ .../java/ch/dissem/msgpack/types/MPMap.java | 16 ++ .../java/ch/dissem/msgpack/types/MPNil.java | 16 ++ .../ch/dissem/msgpack/types/MPString.java | 16 ++ .../java/ch/dissem/msgpack/types/MPType.java | 16 ++ .../java/ch/dissem/msgpack/types/Utils.java | 16 ++ .../java/ch/dissem/msgpack/ReaderTest.java | 17 +- 14 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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/src/main/java/ch/dissem/msgpack/Reader.java b/src/main/java/ch/dissem/msgpack/Reader.java index 51d1518..b3a8e5c 100644 --- a/src/main/java/ch/dissem/msgpack/Reader.java +++ b/src/main/java/ch/dissem/msgpack/Reader.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack; import ch.dissem.msgpack.types.*; @@ -11,7 +27,7 @@ import java.util.List; * Reads MPType object from an {@link InputStream}. */ public class Reader { - private List> unpackers = new LinkedList>(); + private List> unpackers = new LinkedList<>(); public Reader() { unpackers.add(new MPNil.Unpacker()); diff --git a/src/main/java/ch/dissem/msgpack/types/MPArray.java b/src/main/java/ch/dissem/msgpack/types/MPArray.java index f83cc39..35d8328 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPArray.java +++ b/src/main/java/ch/dissem/msgpack/types/MPArray.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import ch.dissem.msgpack.Reader; @@ -76,6 +92,7 @@ public class MPArray implements MPType>, List { } @Override + @SuppressWarnings("SuspiciousToArrayCall") public T1[] toArray(T1[] t1s) { return array.toArray(t1s); } diff --git a/src/main/java/ch/dissem/msgpack/types/MPBinary.java b/src/main/java/ch/dissem/msgpack/types/MPBinary.java index 163d724..a9ae0d0 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBinary.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBinary.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java index fa95460..d99745a 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/main/java/ch/dissem/msgpack/types/MPDouble.java b/src/main/java/ch/dissem/msgpack/types/MPDouble.java index 7407a23..b76a83a 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPDouble.java +++ b/src/main/java/ch/dissem/msgpack/types/MPDouble.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/main/java/ch/dissem/msgpack/types/MPFloat.java b/src/main/java/ch/dissem/msgpack/types/MPFloat.java index 8d31318..710c98c 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPFloat.java +++ b/src/main/java/ch/dissem/msgpack/types/MPFloat.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/main/java/ch/dissem/msgpack/types/MPInteger.java b/src/main/java/ch/dissem/msgpack/types/MPInteger.java index c8bf051..5125717 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPInteger.java +++ b/src/main/java/ch/dissem/msgpack/types/MPInteger.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/main/java/ch/dissem/msgpack/types/MPMap.java b/src/main/java/ch/dissem/msgpack/types/MPMap.java index f8e8d3d..a286b2c 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPMap.java +++ b/src/main/java/ch/dissem/msgpack/types/MPMap.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import ch.dissem.msgpack.Reader; diff --git a/src/main/java/ch/dissem/msgpack/types/MPNil.java b/src/main/java/ch/dissem/msgpack/types/MPNil.java index 5243115..ae24831 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPNil.java +++ b/src/main/java/ch/dissem/msgpack/types/MPNil.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java index 0b0db09..9bf6890 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPString.java +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/main/java/ch/dissem/msgpack/types/MPType.java b/src/main/java/ch/dissem/msgpack/types/MPType.java index 2e62fe5..668467a 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPType.java +++ b/src/main/java/ch/dissem/msgpack/types/MPType.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/main/java/ch/dissem/msgpack/types/Utils.java b/src/main/java/ch/dissem/msgpack/types/Utils.java index b90b610..3531455 100644 --- a/src/main/java/ch/dissem/msgpack/types/Utils.java +++ b/src/main/java/ch/dissem/msgpack/types/Utils.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack.types; import java.io.IOException; diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index 90b36f6..5b16358 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.msgpack; import ch.dissem.msgpack.types.*; @@ -9,7 +25,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Random; import static org.hamcrest.CoreMatchers.instanceOf; From 7a84d1e22024f123233f6e0b453e7d18afb48988 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 23 Jan 2017 22:33:39 +0100 Subject: [PATCH 07/20] Improved test, fixed bug --- src/main/java/ch/dissem/msgpack/types/MPString.java | 5 +++-- src/test/java/ch/dissem/msgpack/ReaderTest.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java index 9bf6890..a68a475 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPString.java +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -66,7 +66,8 @@ public class MPString implements MPType, CharSequence { } public void pack(OutputStream out) throws IOException { - int size = value.length(); + byte[] bytes = value.getBytes(encoding); + int size = bytes.length; if (size < 32) { out.write(FIXSTR_PREFIX + size); } else if (size < STR8_LIMIT) { @@ -79,7 +80,7 @@ public class MPString implements MPType, CharSequence { out.write(STR32_PREFIX); out.write(ByteBuffer.allocate(4).putInt(size).array()); } - out.write(value.getBytes(encoding)); + out.write(bytes); } @Override diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index 5b16358..b69cddb 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -63,7 +63,7 @@ public class ReaderTest { new MPInteger(42), new MPMap<>(new HashMap()), new MPNil(), - new MPString("yay!") // TODO: emoji + new MPString("yay! \uD83E\uDD13") ); ByteArrayOutputStream out = new ByteArrayOutputStream(); array.pack(out); From 428ffb2a6d7a4a3771b4df331897380be2c83a6a Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Wed, 25 Jan 2017 17:38:04 +0100 Subject: [PATCH 08/20] Removed MPDouble, MPFloat automatically decides on the precision according to used data type. --- .../ch/dissem/msgpack/types/MPDouble.java | 83 ------------------- .../java/ch/dissem/msgpack/types/MPFloat.java | 49 ++++++++--- .../java/ch/dissem/msgpack/ReaderTest.java | 50 ++++++++--- 3 files changed, 76 insertions(+), 106 deletions(-) delete mode 100644 src/main/java/ch/dissem/msgpack/types/MPDouble.java diff --git a/src/main/java/ch/dissem/msgpack/types/MPDouble.java b/src/main/java/ch/dissem/msgpack/types/MPDouble.java deleted file mode 100644 index b76a83a..0000000 --- a/src/main/java/ch/dissem/msgpack/types/MPDouble.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2017 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.msgpack.types; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; - -import static ch.dissem.msgpack.types.Utils.bytes; - -/** - * Representation of a msgpack encoded float64 number. - */ -public class MPDouble implements MPType { - private double value; - - public MPDouble(double value) { - this.value = value; - } - - @Override - public Double getValue() { - return value; - } - - public void pack(OutputStream out) throws IOException { - out.write(0xCB); - out.write(ByteBuffer.allocate(8).putDouble(value).array()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MPDouble mpDouble = (MPDouble) o; - return Double.compare(mpDouble.value, value) == 0; - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return String.valueOf(value); - } - - @Override - public String toJson() { - return String.valueOf(value); - } - - public static class Unpacker implements MPType.Unpacker { - public boolean is(int firstByte) { - return firstByte == 0xCB; - } - - public MPDouble unpack(int firstByte, InputStream in) throws IOException { - if (firstByte == 0xCB) { - return new MPDouble(bytes(in, 8).getDouble()); - } else { - throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); - } - } - } -} diff --git a/src/main/java/ch/dissem/msgpack/types/MPFloat.java b/src/main/java/ch/dissem/msgpack/types/MPFloat.java index 710c98c..20cd7a1 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPFloat.java +++ b/src/main/java/ch/dissem/msgpack/types/MPFloat.java @@ -25,23 +25,45 @@ import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; /** - * Representation of a msgpack encoded float32 number. + * Representation of a msgpack encoded float32 or float64 number. */ -public class MPFloat implements MPType { - private float value; +public class MPFloat implements MPType { + + public enum Precision {FLOAT32, FLOAT64} + + private final double value; + private final Precision precision; public MPFloat(float value) { this.value = value; + this.precision = Precision.FLOAT32; + } + + public MPFloat(double value) { + this.value = value; + this.precision = Precision.FLOAT64; } @Override - public Float getValue() { + public Double getValue() { return value; } + public Precision getPrecision() { + return precision; + } + public void pack(OutputStream out) throws IOException { - out.write(0xCA); - out.write(ByteBuffer.allocate(4).putFloat(value).array()); + switch (precision) { + case FLOAT32: + out.write(0xCA); + out.write(ByteBuffer.allocate(4).putFloat((float) value).array()); + break; + case FLOAT64: + out.write(0xCB); + out.write(ByteBuffer.allocate(8).putDouble(value).array()); + break; + } } @Override @@ -49,7 +71,7 @@ public class MPFloat implements MPType { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MPFloat mpFloat = (MPFloat) o; - return Float.compare(mpFloat.value, value) == 0; + return Double.compare(mpFloat.value, value) == 0; } @Override @@ -69,14 +91,17 @@ public class MPFloat implements MPType { public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { - return firstByte == 0xCA; + return firstByte == 0xCA || firstByte == 0xCB; } public MPFloat unpack(int firstByte, InputStream in) throws IOException { - if (firstByte == 0xCA) { - return new MPFloat(bytes(in, 4).getFloat()); - } else { - throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); + switch (firstByte) { + case 0xCA: + return new MPFloat(bytes(in, 4).getFloat()); + case 0xCB: + return new MPFloat(bytes(in, 8).getDouble()); + default: + throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); } } } diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index b69cddb..1504f88 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -27,13 +27,15 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Random; +import static ch.dissem.msgpack.types.Utils.mp; +import static ch.dissem.msgpack.types.Utils.nil; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; public class ReaderTest { private static final Random RANDOM = new Random(); - private Reader reader = new Reader(); + private Reader reader = Reader.getInstance(); @Test public void ensureDemoJsonIsParsedCorrectly() throws Exception { @@ -44,9 +46,10 @@ public class ReaderTest { @Test public void ensureDemoJsonIsEncodedCorrectly() throws Exception { + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") MPMap> object = new MPMap<>(); - object.put(new MPString("compact"), new MPBoolean(true)); - object.put(new MPString("schema"), new MPInteger(0)); + object.put(mp("compact"), mp(true)); + object.put(mp("schema"), mp(0)); ByteArrayOutputStream out = new ByteArrayOutputStream(); object.pack(out); assertThat(out.toByteArray(), is(bytes("demo.mp"))); @@ -56,14 +59,14 @@ public class ReaderTest { @SuppressWarnings("unchecked") public void ensureMPArrayIsEncodedAndDecodedCorrectly() throws Exception { MPArray> array = new MPArray<>( - new MPBinary(new byte[]{1, 3, 3, 7}), - new MPBoolean(false), - new MPDouble(Math.PI), - new MPFloat(1.5f), - new MPInteger(42), - new MPMap<>(new HashMap()), - new MPNil(), - new MPString("yay! \uD83E\uDD13") + mp(new byte[]{1, 3, 3, 7}), + mp(false), + mp(Math.PI), + mp(1.5f), + mp(42), + new MPMap(), + nil(), + mp("yay! \uD83E\uDD13") ); ByteArrayOutputStream out = new ByteArrayOutputStream(); array.pack(out); @@ -72,6 +75,31 @@ public class ReaderTest { assertThat((MPArray>) read, is(array)); } + @Test + public void ensureFloatIsEncodedAndDecodedCorrectly() throws Exception { + MPFloat expected = new MPFloat(1.5f); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + expected.pack(out); + MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); + assertThat(read, instanceOf(MPFloat.class)); + MPFloat actual = (MPFloat) read; + assertThat(actual, is(expected)); + assertThat(actual.getPrecision(), is(MPFloat.Precision.FLOAT32)); + } + + @Test + public void ensureDoubleIsEncodedAndDecodedCorrectly() throws Exception { + MPFloat expected = new MPFloat(Math.PI); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + expected.pack(out); + MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); + assertThat(read, instanceOf(MPFloat.class)); + MPFloat actual = (MPFloat) read; + assertThat(actual, is(expected)); + assertThat(actual.getValue(), is(Math.PI)); + assertThat(actual.getPrecision(), is(MPFloat.Precision.FLOAT64)); + } + @Test public void ensureStringsAreEncodedAndDecodedCorrectly() throws Exception { ensureStringIsEncodedAndDecodedCorrectly(0); From 43c7267e14416a8fc50729245ba7bf391799c234 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Wed, 25 Jan 2017 22:41:57 +0100 Subject: [PATCH 09/20] Added some helpful utils, license --- build.gradle | 6 ++-- src/main/java/ch/dissem/msgpack/Reader.java | 9 ++++-- .../java/ch/dissem/msgpack/types/MPMap.java | 16 ---------- .../java/ch/dissem/msgpack/types/Utils.java | 31 ++++++++++++++++++- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 903abe3..509edc2 100644 --- a/build.gradle +++ b/build.gradle @@ -59,9 +59,9 @@ uploadArchives { url 'https://dissem.ch/msgpack' scm { - connection 'scm:git:https://github.com/Dissem/msgpack.git' - developerConnection 'scm:git:git@github.com:Dissem/msgpack.git' - url 'https://github.com/Dissem/msgpack.git' + connection 'scm:git:https://git.dissem.ch/chris/MessagePack.git' + developerConnection 'scm:git:git@git.dissem.ch:chris/MessagePack.git' + url 'https://git.dissem.ch/chris/MessagePack.git' } licenses { diff --git a/src/main/java/ch/dissem/msgpack/Reader.java b/src/main/java/ch/dissem/msgpack/Reader.java index b3a8e5c..9b4e5ee 100644 --- a/src/main/java/ch/dissem/msgpack/Reader.java +++ b/src/main/java/ch/dissem/msgpack/Reader.java @@ -29,18 +29,23 @@ import java.util.List; public class Reader { private List> unpackers = new LinkedList<>(); - public Reader() { + private static final Reader instance = new Reader(); + + private Reader() { unpackers.add(new MPNil.Unpacker()); unpackers.add(new MPBoolean.Unpacker()); unpackers.add(new MPInteger.Unpacker()); unpackers.add(new MPFloat.Unpacker()); - unpackers.add(new MPDouble.Unpacker()); unpackers.add(new MPString.Unpacker()); unpackers.add(new MPBinary.Unpacker()); unpackers.add(new MPMap.Unpacker(this)); unpackers.add(new MPArray.Unpacker(this)); } + public static Reader getInstance() { + return instance; + } + /** * Register your own extensions */ diff --git a/src/main/java/ch/dissem/msgpack/types/MPMap.java b/src/main/java/ch/dissem/msgpack/types/MPMap.java index a286b2c..f8e8d3d 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPMap.java +++ b/src/main/java/ch/dissem/msgpack/types/MPMap.java @@ -1,19 +1,3 @@ -/* - * Copyright 2017 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package ch.dissem.msgpack.types; import ch.dissem.msgpack.Reader; diff --git a/src/main/java/ch/dissem/msgpack/types/Utils.java b/src/main/java/ch/dissem/msgpack/types/Utils.java index 3531455..62f68f7 100644 --- a/src/main/java/ch/dissem/msgpack/types/Utils.java +++ b/src/main/java/ch/dissem/msgpack/types/Utils.java @@ -20,8 +20,9 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; -class Utils { +public class Utils { private static final char[] BASE64_CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(); + private static final MPNil NIL = new MPNil(); /** * Returns a {@link ByteBuffer} containing the next count bytes from the {@link InputStream}. @@ -83,4 +84,32 @@ class Utils { return result.toString(); } + + public static MPString mp(String value) { + return new MPString(value); + } + + public static MPBoolean mp(boolean value) { + return new MPBoolean(value); + } + + public static MPFloat mp(double value) { + return new MPFloat(value); + } + + public static MPFloat mp(float value) { + return new MPFloat(value); + } + + public static MPInteger mp(int value) { + return new MPInteger(value); + } + + public static MPBinary mp(byte... data) { + return new MPBinary(data); + } + + public static MPNil nil() { + return NIL; + } } From e96d3938728297e286c8b7ace4a906eaddfebe5d Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Thu, 26 Jan 2017 07:31:27 +0100 Subject: [PATCH 10/20] Added README.md --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..adf8605 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +msgpack +======= +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/ch.dissem.msgpack/msgpack/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ch.dissem.msgpack/msgpack) +[![Javadoc](https://javadoc-emblem.rhcloud.com/doc/ch.dissem.msgpack/msgpack/badge.svg)](http://www.javadoc.io/doc/ch.dissem.msgpack/msgpack) +[![Apache 2](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE) + +This is a simple Java library for handling MessagePack data. It doesn't do any object mapping, but maps to special +objects representing MessagePack types. To build, use command `./gradlew build`. + +For most cases you might be better off using `org.msgpack:msgpack`, but I found that I needed something that generically +represents the internal structure of the data. + +msgpack uses Semantic Versioning, meaning as long as the major version doesn't change, nothing should break if you +update. Be aware though that this doesn't necessarily applies for SNAPSHOT builds and the development branch. + + +Limitations +-------------- + +* There is no fallback to BigInteger for large integer type numbers, so there might be an integer overflow when reading + too large numbers +* `MPFloat` uses the data type you're using to decide on precision (float 32 or 64) - not the actual value. E.g. 0.5 + could be saved perfectly as a float 42, but if you provide a double value, it will be stored as float 64, wasting + 4 bytes. + +Setup +----- + +Add msgpack as Gradle dependency: +```Gradle +compile "ch.dissem.msgpack:msgpack:1.0.0" +``` + +Usage +----- + +### Serialize Data + +First, you'll need to create some msgpack objects to serialize: +```Java +MPMap> object = new MPMap<>(); +object.put(new MPString("compact"), new MPBoolean(true)); +object.put(new MPString("schema"), new MPInteger(0)); +``` +or the shorthand version for simple types: +```Java +import static ch.dissem.msgpack.types.Utils.mp; + +MPMap> object = new MPMap<>(); +object.put(mp("compact"), mp(true)); +object.put(mp("schema"), mp(0)); +``` +then just use `pack(OutputStream)`: +```Java +OutputStream out = ...; +object.pack(out); +``` + + +### Deserialize Data + +For deserializing data there is the reader object: +```Java +Reader reader = Reader.getInstance() +``` +just use `reader.read(InputStream)`. Unfortunately you'll need to make sure you got what you expected, the following +example might result in `ClassCastException` at weird places: +```Java +InputStream in = ...; +MPType read = reader.read(in); +MPMap map = (MPMap) read; +String value = map.get(mp("key")).getValue(); +``` \ No newline at end of file From d24f0ac1ed23096aed2943a638d29059bb4c10fe Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 27 Jan 2017 08:40:52 +0100 Subject: [PATCH 11/20] Updated README.md and Travis config file --- .travis.yml | 10 ++++++++++ README.md | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..aacb38f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: java +sudo: false # faster builds +jdk: + - oraclejdk8 + +before_install: + - pip install --user codecov + +after_success: + - codecov diff --git a/README.md b/README.md index adf8605..38b9eed 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,16 @@ msgpack uses Semantic Versioning, meaning as long as the major version doesn't c update. Be aware though that this doesn't necessarily applies for SNAPSHOT builds and the development branch. +#### Master +[![Build Status](https://travis-ci.org/Dissem/MsgPack.svg?branch=master)](https://travis-ci.org/Dissem/MsgPack) +[![Code Quality](https://img.shields.io/codacy/eb92c25247b4444383b163304e57a3ce/master.svg)](https://www.codacy.com/app/chrigu-meyer/MsgPack/dashboard?bid=TODO) +[![Test Coverage](https://codecov.io/github/Dissem/Jabit/coverage.svg?branch=master)](https://codecov.io/github/Dissem/Jabit?branch=master) + +#### Develop +[![Build Status](https://travis-ci.org/Dissem/MsgPack.svg?branch=develop)](https://travis-ci.org/Dissem/MsgPack?branch=develop) +[![Code Quality](https://img.shields.io/codacy/eb92c25247b4444383b163304e57a3ce/develop.svg)](https://www.codacy.com/app/chrigu-meyer/MsgPack/dashboard?bid=4118049) +[![Test Coverage](https://codecov.io/github/Dissem/MsgPack/coverage.svg?branch=develop)](https://codecov.io/github/Dissem/MsgPack?branch=develop) + Limitations -------------- From 9b3c2df88b3fd8394b15b9ebc71c7c2a446559b1 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 27 Jan 2017 13:32:16 +0100 Subject: [PATCH 12/20] Add default gradle settings --- gradle.properties | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 gradle.properties diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..bb8beb4 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,11 @@ +# Don't change this file - override those properties in the +# gradle.properties file in your home directory instead + +signing.keyId= +signing.password= +#signing.secretKeyRingFile= + +ossrhUsername= +ossrhPassword= + +systemTestsEnabled=false \ No newline at end of file From 29203b355f9aa182563ded573e827b31c31ca74b Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 27 Jan 2017 13:51:45 +0100 Subject: [PATCH 13/20] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 38b9eed..46c32f8 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ update. Be aware though that this doesn't necessarily applies for SNAPSHOT build #### Master [![Build Status](https://travis-ci.org/Dissem/MsgPack.svg?branch=master)](https://travis-ci.org/Dissem/MsgPack) -[![Code Quality](https://img.shields.io/codacy/eb92c25247b4444383b163304e57a3ce/master.svg)](https://www.codacy.com/app/chrigu-meyer/MsgPack/dashboard?bid=TODO) -[![Test Coverage](https://codecov.io/github/Dissem/Jabit/coverage.svg?branch=master)](https://codecov.io/github/Dissem/Jabit?branch=master) +[![Code Quality](https://img.shields.io/codacy/eb92c25247b4444383b163304e57a3ce/master.svg)](https://www.codacy.com/app/chrigu-meyer/MsgPack/dashboard?bid=4122049) +[![Test Coverage](https://codecov.io/github/Dissem/MsgPack/coverage.svg?branch=master)](https://codecov.io/github/Dissem/MsgPack?branch=master) #### Develop [![Build Status](https://travis-ci.org/Dissem/MsgPack.svg?branch=develop)](https://travis-ci.org/Dissem/MsgPack?branch=develop) From 4fff06e34596edbeccc2325e79a40890e7ebdcf4 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 27 Jan 2017 22:44:11 +0100 Subject: [PATCH 14/20] Added test for MPInteger and fixed bugs --- .../ch/dissem/msgpack/types/MPInteger.java | 17 +++++-- .../java/ch/dissem/msgpack/types/Utils.java | 2 +- .../java/ch/dissem/msgpack/ReaderTest.java | 44 +++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/dissem/msgpack/types/MPInteger.java b/src/main/java/ch/dissem/msgpack/types/MPInteger.java index 5125717..3a15a27 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPInteger.java +++ b/src/main/java/ch/dissem/msgpack/types/MPInteger.java @@ -52,7 +52,7 @@ public class MPInteger implements MPType { } else if (value <= 0xFFFF) { out.write(0xCD); out.write(ByteBuffer.allocate(2).putShort((short) value).array()); - } else if (value < 0xFFFFFFFFL) { + } else if (value <= 0xFFFFFFFFL) { out.write(0xCE); out.write(ByteBuffer.allocate(4).putInt((int) value).array()); } else { @@ -60,7 +60,11 @@ public class MPInteger implements MPType { out.write(ByteBuffer.allocate(8).putLong(value).array()); } } else { - if (value >= Byte.MIN_VALUE) { + if (value >= -32) { + out.write(new byte[]{ + (byte) value + }); + } else if (value >= Byte.MIN_VALUE) { out.write(0xD0); out.write(ByteBuffer.allocate(1).put((byte) value).array()); } else if (value >= Short.MIN_VALUE) { @@ -125,8 +129,13 @@ public class MPInteger implements MPType { return new MPInteger(in.read()); case 0xCD: return new MPInteger(in.read() << 8 | in.read()); - case 0xCE: - return new MPInteger(in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read()); + case 0xCE: { + long value = 0; + for (int i = 0; i < 4; i++) { + value = value << 8 | in.read(); + } + return new MPInteger(value); + } case 0xCF: { long value = 0; for (int i = 0; i < 8; i++) { diff --git a/src/main/java/ch/dissem/msgpack/types/Utils.java b/src/main/java/ch/dissem/msgpack/types/Utils.java index 62f68f7..7bc32a3 100644 --- a/src/main/java/ch/dissem/msgpack/types/Utils.java +++ b/src/main/java/ch/dissem/msgpack/types/Utils.java @@ -101,7 +101,7 @@ public class Utils { return new MPFloat(value); } - public static MPInteger mp(int value) { + public static MPInteger mp(long value) { return new MPInteger(value); } diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index 1504f88..6066bd0 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -100,6 +100,50 @@ public class ReaderTest { assertThat(actual.getPrecision(), is(MPFloat.Precision.FLOAT64)); } + @Test + public void ensureLongsAreEncodedAndDecodedCorrectly() throws Exception { + // positive fixnum + ensureLongIsEncodedAndDecodedCorrectly(0, 1); + ensureLongIsEncodedAndDecodedCorrectly(127, 1); + // negative fixnum + ensureLongIsEncodedAndDecodedCorrectly(-1, 1); + ensureLongIsEncodedAndDecodedCorrectly(-32, 1); + // uint 8 + ensureLongIsEncodedAndDecodedCorrectly(128, 2); + ensureLongIsEncodedAndDecodedCorrectly(255, 2); + // uint 16 + ensureLongIsEncodedAndDecodedCorrectly(256, 3); + ensureLongIsEncodedAndDecodedCorrectly(65535, 3); + // uint 32 + ensureLongIsEncodedAndDecodedCorrectly(65536, 5); + ensureLongIsEncodedAndDecodedCorrectly(4294967295L, 5); + // uint 64 + ensureLongIsEncodedAndDecodedCorrectly(4294967296L, 9); + ensureLongIsEncodedAndDecodedCorrectly(Long.MAX_VALUE, 9); + // int 8 + ensureLongIsEncodedAndDecodedCorrectly(-33, 2); + ensureLongIsEncodedAndDecodedCorrectly(-128, 2); + // int 16 + ensureLongIsEncodedAndDecodedCorrectly(-129, 3); + ensureLongIsEncodedAndDecodedCorrectly(-32768, 3); + // int 32 + ensureLongIsEncodedAndDecodedCorrectly(-32769, 5); + ensureLongIsEncodedAndDecodedCorrectly(Integer.MIN_VALUE, 5); + // int 64 + ensureLongIsEncodedAndDecodedCorrectly(-2147483649L, 9); + ensureLongIsEncodedAndDecodedCorrectly(Long.MIN_VALUE, 9); + } + + private void ensureLongIsEncodedAndDecodedCorrectly(long val, int bytes) throws Exception { + MPInteger value = mp(val); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + value.pack(out); + MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); + assertThat(out.size(), is(bytes)); + assertThat(read, instanceOf(MPInteger.class)); + assertThat((MPInteger) read, is(value)); + } + @Test public void ensureStringsAreEncodedAndDecodedCorrectly() throws Exception { ensureStringIsEncodedAndDecodedCorrectly(0); From cd50e9440c45141cc8f3942dd2c80e00376167f5 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 27 Jan 2017 23:25:49 +0100 Subject: [PATCH 15/20] Fixed some issues --- src/main/java/ch/dissem/msgpack/types/MPFloat.java | 2 ++ src/main/java/ch/dissem/msgpack/types/MPString.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/dissem/msgpack/types/MPFloat.java b/src/main/java/ch/dissem/msgpack/types/MPFloat.java index 20cd7a1..486a89f 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPFloat.java +++ b/src/main/java/ch/dissem/msgpack/types/MPFloat.java @@ -63,6 +63,8 @@ public class MPFloat implements MPType { out.write(0xCB); out.write(ByteBuffer.allocate(8).putDouble(value).array()); break; + default: + throw new IllegalArgumentException("Unknown precision: " + precision); } } diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java index a68a475..3ffd2d2 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPString.java +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -43,6 +43,8 @@ public class MPString implements MPType, CharSequence { private static Charset encoding = Charset.forName("UTF-8"); + private final String value; + /** * Use this method if for some messed up reason you really need to use something else than UTF-8. * Ask yourself: why should I? Is this really necessary? @@ -55,8 +57,6 @@ public class MPString implements MPType, CharSequence { MPString.encoding = encoding; } - private final String value; - public MPString(String value) { this.value = value; } From bc1ed4fe3c3200ab31feaa9121f95255241ed0f9 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Tue, 31 Jan 2017 15:50:55 +0100 Subject: [PATCH 16/20] Fixed and improved tests --- src/test/java/ch/dissem/msgpack/ReaderTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index 6066bd0..d0b1bef 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -73,6 +73,7 @@ public class ReaderTest { MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); assertThat(read, instanceOf(MPArray.class)); assertThat((MPArray>) read, is(array)); + assertThat(read.toJson(), is("[\n AQMDBw==,\n false,\n 3.141592653589793,\n 1.5,\n 42,\n {\n },\n null,\n \"yay! 🤓\"\n]")); } @Test @@ -217,18 +218,18 @@ public class ReaderTest { } @SuppressWarnings("unchecked") - private void ensureMapIsEncodedAndDecodedCorrectly(int length) throws Exception { + private void ensureMapIsEncodedAndDecodedCorrectly(int size) throws Exception { MPNil nil = new MPNil(); - HashMap map = new HashMap<>(length); - for (int i = 0; i < length; i++) { - map.put(nil, nil); + HashMap map = new HashMap<>(size); + for (int i = 0; i < size; i++) { + map.put(mp(i), nil); } - MPMap value = new MPMap<>(map); + MPMap value = new MPMap<>(map); ByteArrayOutputStream out = new ByteArrayOutputStream(); value.pack(out); MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); assertThat(read, instanceOf(MPMap.class)); - assertThat((MPMap) read, is(value)); + assertThat((MPMap) read, is(value)); } private String stringWithLength(int length) { From 4505c5b22ce84e6d15aa97e1917452437691648f Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Wed, 1 Feb 2017 08:13:47 +0100 Subject: [PATCH 17/20] Fixed MPString#toJson and added test --- src/main/java/ch/dissem/msgpack/types/MPString.java | 2 +- src/test/java/ch/dissem/msgpack/ReaderTest.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java index 3ffd2d2..e0f50bb 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPString.java +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -147,7 +147,7 @@ public class MPString implements MPType, CharSequence { if (c < ' ') { result.append("\\u"); String hex = Integer.toHexString(c); - for (int j = 0; j - hex.length() < 4; j++) { + for (int j = 0; j + hex.length() < 4; j++) { result.append('0'); } result.append(hex); diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index d0b1bef..cc7b12c 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -156,6 +156,16 @@ public class ReaderTest { ensureStringIsEncodedAndDecodedCorrectly(65536); } + @Test + public void ensureJsonStringsAreEscapedCorrectly() throws Exception { + StringBuilder builder = new StringBuilder(); + for (char c = '\u0001'; c < ' '; c++) { + builder.append(c); + } + MPString string = new MPString(builder.toString()); + assertThat(string.toJson(), is("\"\\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\\u001f\"")); + } + private void ensureStringIsEncodedAndDecodedCorrectly(int length) throws Exception { MPString value = new MPString(stringWithLength(length)); ByteArrayOutputStream out = new ByteArrayOutputStream(); From 97dad0a7b910c44c125ee032360f2aad45df5001 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 10 Feb 2017 07:26:20 +0100 Subject: [PATCH 18/20] Changed library name --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 46c32f8..f1a5897 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -msgpack -======= +Simple MessagePack +================== [![Maven Central](https://maven-badges.herokuapp.com/maven-central/ch.dissem.msgpack/msgpack/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ch.dissem.msgpack/msgpack) [![Javadoc](https://javadoc-emblem.rhcloud.com/doc/ch.dissem.msgpack/msgpack/badge.svg)](http://www.javadoc.io/doc/ch.dissem.msgpack/msgpack) [![Apache 2](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE) @@ -10,7 +10,7 @@ objects representing MessagePack types. To build, use command `./gradlew build`. For most cases you might be better off using `org.msgpack:msgpack`, but I found that I needed something that generically represents the internal structure of the data. -msgpack uses Semantic Versioning, meaning as long as the major version doesn't change, nothing should break if you +_Simple MessagePack_ uses Semantic Versioning, meaning as long as the major version doesn't change, nothing should break if you update. Be aware though that this doesn't necessarily applies for SNAPSHOT builds and the development branch. From 0c0bf3c324dfe8b39f32a71c9cedda5168f89c77 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 10 Feb 2017 07:35:46 +0100 Subject: [PATCH 19/20] Updated README.md, custom readers take precedence --- README.md | 3 +++ src/main/java/ch/dissem/msgpack/Reader.java | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f1a5897..2ada957 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,9 @@ Limitations * `MPFloat` uses the data type you're using to decide on precision (float 32 or 64) - not the actual value. E.g. 0.5 could be saved perfectly as a float 42, but if you provide a double value, it will be stored as float 64, wasting 4 bytes. +* If you want to use the 'ext format family', you'll need to implement and register your own `MPType` and + `MPType.Unpacker`. Be aware that they take precedence over the default unpackers, meaning if you accidentally define + your unpacker to handle strings, for example, you won't be able to unpack any regular strings anymore. Setup ----- diff --git a/src/main/java/ch/dissem/msgpack/Reader.java b/src/main/java/ch/dissem/msgpack/Reader.java index 9b4e5ee..e24ebf8 100644 --- a/src/main/java/ch/dissem/msgpack/Reader.java +++ b/src/main/java/ch/dissem/msgpack/Reader.java @@ -27,7 +27,7 @@ import java.util.List; * Reads MPType object from an {@link InputStream}. */ public class Reader { - private List> unpackers = new LinkedList<>(); + private LinkedList> unpackers = new LinkedList<>(); private static final Reader instance = new Reader(); @@ -50,7 +50,7 @@ public class Reader { * Register your own extensions */ public void register(MPType.Unpacker unpacker) { - unpackers.add(unpacker); + unpackers.addFirst(unpacker); } public MPType read(InputStream in) throws IOException { From 5579ef499b1cff39292bd8efeea0d2acc1dff106 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Sat, 11 Feb 2017 09:55:08 +0100 Subject: [PATCH 20/20] Minor README.md update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ada957..485b2e7 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ Limitations could be saved perfectly as a float 42, but if you provide a double value, it will be stored as float 64, wasting 4 bytes. * If you want to use the 'ext format family', you'll need to implement and register your own `MPType` and - `MPType.Unpacker`. Be aware that they take precedence over the default unpackers, meaning if you accidentally define + `MPType.Unpacker`. +* Be aware that custom `MPType.Unpacker` take precedence over the default unpackers, i.e. if you accidentally define your unpacker to handle strings, for example, you won't be able to unpack any regular strings anymore. Setup