Compare commits
	
		
			17 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f1ecc8f767 | |||
| f1c7ad2afd | |||
| 42a5f35726 | |||
| 8e06248d05 | |||
| 12a0d11a27 | |||
| bc970b47ae | |||
| 6ad5741931 | |||
| 69bb57d0c3 | |||
| 48f4848c0a | |||
| 233b152338 | |||
| da44c7fa62 | |||
| 687836b295 | |||
| 843f6bc714 | |||
| a87ab60c68 | |||
| 86a14c85df | |||
| a14207f421 | |||
| e667f2b6d8 | 
							
								
								
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | Changelog | ||||||
|  | ========= | ||||||
|  |  | ||||||
|  | 2.0.0 | ||||||
|  | ----- | ||||||
|  | Migrated to Kotlin. If you didn't implement your own `Unpacker`, nothing should change except for the added null safety. | ||||||
|  | Otherwise, because `is` is a reserved word in Kotlin, the method was renamed to `doesUnpack` for ease of use. | ||||||
|  |  | ||||||
|  | 1.0.0 | ||||||
|  | ----- | ||||||
|  | Initial version | ||||||
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							| @@ -4,25 +4,25 @@ Simple MessagePack | |||||||
| [](http://www.javadoc.io/doc/ch.dissem.msgpack/msgpack) | [](http://www.javadoc.io/doc/ch.dissem.msgpack/msgpack) | ||||||
| [](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE) | [](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 | This is a simple Kotlin/Java library for handling MessagePack data. It doesn't do any object mapping, but maps to | ||||||
| objects representing MessagePack types. To build, use command `./gradlew build`. | 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 | 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. | represents the internal structure of the data. | ||||||
|  |  | ||||||
| _Simple MessagePack_ 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 | ||||||
| update. Be aware though that this doesn't necessarily applies for SNAPSHOT builds and the development branch. | if you update. Be aware though that this doesn't necessarily applies for SNAPSHOT builds and the development branch. | ||||||
|  |  | ||||||
|  |  | ||||||
| #### Master | #### Master | ||||||
| [](https://travis-ci.org/Dissem/MsgPack)  | [](https://travis-ci.org/Dissem/Simple-MsgPack)  | ||||||
| [](https://www.codacy.com/app/chrigu-meyer/MsgPack/dashboard?bid=4122049) | [](https://www.codacy.com/app/chrigu-meyer/Simple-MsgPack/dashboard?bid=4122049) | ||||||
| [](https://codecov.io/github/Dissem/MsgPack?branch=master) | [](https://codecov.io/github/Dissem/Simple-MsgPack?branch=master) | ||||||
|  |  | ||||||
| #### Develop | #### Develop | ||||||
| [](https://travis-ci.org/Dissem/MsgPack?branch=develop)  | [](https://travis-ci.org/Dissem/Simple-MsgPack?branch=develop)  | ||||||
| [](https://www.codacy.com/app/chrigu-meyer/MsgPack/dashboard?bid=4118049) | [](https://www.codacy.com/app/chrigu-meyer/Simple-MsgPack/dashboard?bid=4118049) | ||||||
| [](https://codecov.io/github/Dissem/MsgPack?branch=develop) | [](https://codecov.io/github/Dissem/Simple-MsgPack?branch=develop) | ||||||
|  |  | ||||||
| Limitations | Limitations | ||||||
| -------------- | -------------- | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -1,4 +1,15 @@ | |||||||
|  | buildscript { | ||||||
|  |     ext.kotlin_version = '1.1.60' | ||||||
|  |     repositories { | ||||||
|  |         mavenCentral() | ||||||
|  |     } | ||||||
|  |     dependencies { | ||||||
|  |         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| apply plugin: 'java' | apply plugin: 'java' | ||||||
|  | apply plugin: 'kotlin' | ||||||
| apply plugin: 'maven' | apply plugin: 'maven' | ||||||
| apply plugin: 'signing' | apply plugin: 'signing' | ||||||
| apply plugin: 'jacoco' | apply plugin: 'jacoco' | ||||||
| @@ -12,6 +23,9 @@ repositories { | |||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|  |     compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" | ||||||
|  |     compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" | ||||||
|  |  | ||||||
|     testCompile group: 'junit', name: 'junit', version: '4.11' |     testCompile group: 'junit', name: 'junit', version: '4.11' | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -55,6 +69,7 @@ uploadArchives { | |||||||
|  |  | ||||||
|             pom.project { |             pom.project { | ||||||
|                 name 'msgpack' |                 name 'msgpack' | ||||||
|  |                 description 'A simple Java library for handling MessagePack data' | ||||||
|                 packaging 'jar' |                 packaging 'jar' | ||||||
|                 url 'https://dissem.ch/msgpack' |                 url 'https://dissem.ch/msgpack' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ class GitFlowVersion implements Plugin<Project> { | |||||||
|         project.ext.isRelease = isRelease(project) |         project.ext.isRelease = isRelease(project) | ||||||
|         project.version = getVersion(project) |         project.version = getVersion(project) | ||||||
|  |  | ||||||
|         project.task('version') << { |         project.task('version').doLast { | ||||||
|             println "Version deduced from git: '${project.version}'" |             println "Version deduced from git: '${project.version}'" | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| #Tue Jan 17 07:22:12 CET 2017 | #Mon Nov 20 19:17:21 CET 2017 | ||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| #!/usr/bin/env bash | #!/usr/bin/env sh | ||||||
|  |  | ||||||
| ############################################################################## | ############################################################################## | ||||||
| ## | ## | ||||||
| @@ -154,16 +154,19 @@ if $cygwin ; then | |||||||
|     esac |     esac | ||||||
| fi | fi | ||||||
|  |  | ||||||
| # Split up the JVM_OPTS And GRADLE_OPTS values into an value, following the shell quoting and substitution rules | # Escape application args | ||||||
| function splitJvmOpts() { | save () { | ||||||
|     JVM_OPTS=("$@") |     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | ||||||
|  |     echo " " | ||||||
| } | } | ||||||
| eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS | APP_ARGS=$(save "$@") | ||||||
| JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" |  | ||||||
|  | # Collect all arguments for the java command, following the shell quoting and substitution rules | ||||||
|  | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | ||||||
|  |  | ||||||
| # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong | # 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 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then | ||||||
|   cd "$(dirname "$0")" |   cd "$(dirname "$0")" | ||||||
| fi | fi | ||||||
|  |  | ||||||
| exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" | exec "$JAVACMD" "$@" | ||||||
|   | |||||||
| @@ -1,65 +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; |  | ||||||
|  |  | ||||||
| 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 LinkedList<MPType.Unpacker<?>> unpackers = new LinkedList<>(); |  | ||||||
|  |  | ||||||
|     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 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 |  | ||||||
|      */ |  | ||||||
|     public void register(MPType.Unpacker<?> unpacker) { |  | ||||||
|         unpackers.addFirst(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)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,256 +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 ch.dissem.msgpack.Reader; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.*; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Representation of a msgpack encoded array. Uses a list to represent data internally, and implements the {@link List} |  | ||||||
|  * interface for your convenience. |  | ||||||
|  * |  | ||||||
|  * @param <T> |  | ||||||
|  */ |  | ||||||
| public class MPArray<T extends MPType> implements MPType<List<T>>, List<T> { |  | ||||||
|     private List<T> array; |  | ||||||
|  |  | ||||||
|     public MPArray() { |  | ||||||
|         this.array = new LinkedList<>(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public MPArray(List<T> array) { |  | ||||||
|         this.array = array; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @SafeVarargs |  | ||||||
|     public MPArray(T... objects) { |  | ||||||
|         this.array = Arrays.asList(objects); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<T> 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 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<T> iterator() { |  | ||||||
|         return array.iterator(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Object[] toArray() { |  | ||||||
|         return array.toArray(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     @SuppressWarnings("SuspiciousToArrayCall") |  | ||||||
|     public <T1> 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<? extends T> collection) { |  | ||||||
|         return array.addAll(collection); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean addAll(int i, Collection<? extends T> 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; |  | ||||||
|         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 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<T> listIterator() { |  | ||||||
|         return array.listIterator(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public ListIterator<T> listIterator(int i) { |  | ||||||
|         return array.listIterator(i); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<T> subList(int fromIndex, int toIndex) { |  | ||||||
|         return array.subList(fromIndex, toIndex); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toString() { |  | ||||||
|         return toJson(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toJson() { |  | ||||||
|         return toJson(""); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String toJson(String indent) { |  | ||||||
|         StringBuilder result = new StringBuilder(); |  | ||||||
|         result.append("[\n"); |  | ||||||
|         Iterator<T> iterator = array.iterator(); |  | ||||||
|         String indent2 = indent + "  "; |  | ||||||
|         while (iterator.hasNext()) { |  | ||||||
|             T item = iterator.next(); |  | ||||||
|             result.append(indent2); |  | ||||||
|             result.append(Utils.toJson(item, indent2)); |  | ||||||
|             if (iterator.hasNext()) { |  | ||||||
|                 result.append(','); |  | ||||||
|             } |  | ||||||
|             result.append('\n'); |  | ||||||
|         } |  | ||||||
|         result.append("]"); |  | ||||||
|         return result.toString(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Unpacker implements MPType.Unpacker<MPArray> { |  | ||||||
|         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<MPType<?>> 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<MPType<?>> list = new LinkedList<>(); |  | ||||||
|             for (int i = 0; i < size; i++) { |  | ||||||
|                 MPType value = reader.read(in); |  | ||||||
|                 list.add(value); |  | ||||||
|             } |  | ||||||
|             return new MPArray<>(list); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,98 +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.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<byte[]> { |  | ||||||
|     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 toJson(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toJson() { |  | ||||||
|         return Utils.base64(value); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Unpacker implements MPType.Unpacker<MPBinary> { |  | ||||||
|         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(); |  | ||||||
|             } 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()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,88 +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.util.Objects; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Representation of a msgpack encoded boolean. |  | ||||||
|  */ |  | ||||||
| public class MPBoolean implements MPType<Boolean> { |  | ||||||
|     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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toJson() { |  | ||||||
|         return String.valueOf(value); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Unpacker implements MPType.Unpacker<MPBoolean> { |  | ||||||
|  |  | ||||||
|         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)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,110 +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 float32 or float64 number. |  | ||||||
|  */ |  | ||||||
| public class MPFloat implements MPType<Double> { |  | ||||||
|  |  | ||||||
|     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 Double getValue() { |  | ||||||
|         return value; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Precision getPrecision() { |  | ||||||
|         return precision; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void pack(OutputStream out) throws IOException { |  | ||||||
|         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; |  | ||||||
|             default: |  | ||||||
|                 throw new IllegalArgumentException("Unknown precision: " + precision); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         MPFloat mpFloat = (MPFloat) o; |  | ||||||
|         return Double.compare(mpFloat.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<MPFloat> { |  | ||||||
|         public boolean is(int firstByte) { |  | ||||||
|             return firstByte == 0xCA || firstByte == 0xCB; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public MPFloat unpack(int firstByte, InputStream in) throws IOException { |  | ||||||
|             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)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,160 +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 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<Long> { |  | ||||||
|     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 >= -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) { |  | ||||||
|                 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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toJson() { |  | ||||||
|         return String.valueOf(value); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Unpacker implements MPType.Unpacker<MPInteger> { |  | ||||||
|         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: { |  | ||||||
|                         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++) { |  | ||||||
|                             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)); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,185 +0,0 @@ | |||||||
| 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.*; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 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 <K> |  | ||||||
|  * @param <V> |  | ||||||
|  */ |  | ||||||
| public class MPMap<K extends MPType, V extends MPType> implements MPType<Map<K, V>>, Map<K, V> { |  | ||||||
|     private Map<K, V> map; |  | ||||||
|  |  | ||||||
|     public MPMap() { |  | ||||||
|         this.map = new LinkedHashMap<>(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public MPMap(Map<K, V> map) { |  | ||||||
|         this.map = map; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Map<K, V> 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<K, V> e : map.entrySet()) { |  | ||||||
|             e.getKey().pack(out); |  | ||||||
|             e.getValue().pack(out); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int size() { |  | ||||||
|         return map.size(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean isEmpty() { |  | ||||||
|         return map.isEmpty(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean containsKey(Object o) { |  | ||||||
|         return map.containsKey(o); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean containsValue(Object o) { |  | ||||||
|         return map.containsValue(o); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public V get(Object o) { |  | ||||||
|         return map.get(o); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public V put(K k, V v) { |  | ||||||
|         return map.put(k, v); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public V remove(Object o) { |  | ||||||
|         return map.remove(o); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void putAll(Map<? extends K, ? extends V> map) { |  | ||||||
|         this.map.putAll(map); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void clear() { |  | ||||||
|         map.clear(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Set<K> keySet() { |  | ||||||
|         return map.keySet(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Collection<V> values() { |  | ||||||
|         return map.values(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Set<Entry<K, V>> entrySet() { |  | ||||||
|         return map.entrySet(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @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() { |  | ||||||
|         return toJson(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String toJson(String indent) { |  | ||||||
|         StringBuilder result = new StringBuilder(); |  | ||||||
|         result.append("{\n"); |  | ||||||
|         Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator(); |  | ||||||
|         String indent2 = indent + "  "; |  | ||||||
|         while (iterator.hasNext()) { |  | ||||||
|             Map.Entry<K, V> item = iterator.next(); |  | ||||||
|             result.append(indent2); |  | ||||||
|             result.append(Utils.toJson(item.getKey(), indent2)); |  | ||||||
|             result.append(": "); |  | ||||||
|             result.append(Utils.toJson(item.getValue(), indent2)); |  | ||||||
|             if (iterator.hasNext()) { |  | ||||||
|                 result.append(','); |  | ||||||
|             } |  | ||||||
|             result.append('\n'); |  | ||||||
|         } |  | ||||||
|         result.append(indent).append("}"); |  | ||||||
|         return result.toString(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toJson() { |  | ||||||
|         return toJson(""); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Unpacker implements MPType.Unpacker<MPMap> { |  | ||||||
|         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<?>, 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<?>, 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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,70 +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; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Representation of msgpack encoded nil / null. |  | ||||||
|  */ |  | ||||||
| public class MPNil implements MPType<Void> { |  | ||||||
|     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 "null"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toJson() { |  | ||||||
|         return "null"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Unpacker implements MPType.Unpacker<MPNil> { |  | ||||||
|  |  | ||||||
|         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(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,185 +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.nio.charset.Charset; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| import static ch.dissem.msgpack.types.Utils.bytes; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Representation of a msgpack encoded string. The encoding is automatically selected according to the string's length. |  | ||||||
|  * <p> |  | ||||||
|  * The default encoding is UTF-8. |  | ||||||
|  * </p> |  | ||||||
|  */ |  | ||||||
| public class MPString implements MPType<String>, 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"); |  | ||||||
|  |  | ||||||
|     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? |  | ||||||
|      * <p> |  | ||||||
|      * It will set the encoding for all {@link MPString}s, but if you have inconsistent encoding in your |  | ||||||
|      * format you're lost anyway. |  | ||||||
|      * </p> |  | ||||||
|      */ |  | ||||||
|     public static void setEncoding(Charset encoding) { |  | ||||||
|         MPString.encoding = encoding; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public MPString(String value) { |  | ||||||
|         this.value = value; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getValue() { |  | ||||||
|         return value; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void pack(OutputStream out) throws IOException { |  | ||||||
|         byte[] bytes = value.getBytes(encoding); |  | ||||||
|         int size = bytes.length; |  | ||||||
|         if (size < 32) { |  | ||||||
|             out.write(FIXSTR_PREFIX + size); |  | ||||||
|         } else if (size < STR8_LIMIT) { |  | ||||||
|             out.write(STR8_PREFIX); |  | ||||||
|             out.write(size); |  | ||||||
|         } else if (size < STR16_LIMIT) { |  | ||||||
|             out.write(STR16_PREFIX); |  | ||||||
|             out.write(ByteBuffer.allocate(2).putShort((short) size).array()); |  | ||||||
|         } else { |  | ||||||
|             out.write(STR32_PREFIX); |  | ||||||
|             out.write(ByteBuffer.allocate(4).putInt(size).array()); |  | ||||||
|         } |  | ||||||
|         out.write(bytes); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @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 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; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @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<MPString> { |  | ||||||
|         public boolean is(int firstByte) { |  | ||||||
|             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 & FIXSTR_PREFIX_FILTER) == FIXSTR_PREFIX) { |  | ||||||
|                 size = firstByte & FIXSTR_FILTER; |  | ||||||
|             } else if (firstByte == STR8_PREFIX) { |  | ||||||
|                 size = in.read(); |  | ||||||
|             } else if (firstByte == STR16_PREFIX) { |  | ||||||
|                 size = in.read() << 8 | in.read(); |  | ||||||
|             } 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(), encoding)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,115 +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.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| 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 <code>count</code> bytes from the {@link InputStream}. |  | ||||||
|      */ |  | ||||||
|     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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 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(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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(long value) { |  | ||||||
|         return new MPInteger(value); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static MPBinary mp(byte... data) { |  | ||||||
|         return new MPBinary(data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static MPNil nil() { |  | ||||||
|         return NIL; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										57
									
								
								src/main/kotlin/ch/dissem/msgpack/Reader.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/main/kotlin/ch/dissem/msgpack/Reader.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | /* | ||||||
|  |  * 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.* | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.InputStream | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Reads MPType object from an [InputStream]. | ||||||
|  |  */ | ||||||
|  | object Reader { | ||||||
|  |     private val unpackers = mutableListOf<MPType.Unpacker<*>>() | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         unpackers.add(MPNil.Unpacker()) | ||||||
|  |         unpackers.add(MPBoolean.Unpacker()) | ||||||
|  |         unpackers.add(MPInteger.Unpacker()) | ||||||
|  |         unpackers.add(MPFloat.Unpacker()) | ||||||
|  |         unpackers.add(MPString.Unpacker()) | ||||||
|  |         unpackers.add(MPBinary.Unpacker()) | ||||||
|  |         unpackers.add(MPMap.Unpacker(this)) | ||||||
|  |         unpackers.add(MPArray.Unpacker(this)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Register your own extensions. The last registered unpacker always takes precedence. | ||||||
|  |      */ | ||||||
|  |     fun register(unpacker: MPType.Unpacker<*>) { | ||||||
|  |         unpackers.add(0, unpacker) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     fun read(input: InputStream): MPType<*> { | ||||||
|  |         val firstByte = input.read() | ||||||
|  |         unpackers | ||||||
|  |                 .firstOrNull { it.doesUnpack(firstByte) } | ||||||
|  |                 ?.let { | ||||||
|  |                     return it.unpack(firstByte, input) | ||||||
|  |                 } | ||||||
|  |         throw IOException(String.format("Unsupported input, no reader for 0x%02x", firstByte)) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPArray.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPArray.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | |||||||
|  | /* | ||||||
|  |  * 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 | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Representation of a msgpack encoded array. Uses a list to represent data internally, and implements the [List] | ||||||
|  |  * interface for your convenience. | ||||||
|  |  | ||||||
|  |  * @param <E> content type | ||||||
|  |  */ | ||||||
|  | data class MPArray<E : MPType<*>>(override val value: MutableList<E> = mutableListOf()) : MPType<MutableList<E>>, MutableList<E> { | ||||||
|  |  | ||||||
|  |     @SafeVarargs | ||||||
|  |     constructor(vararg objects: E) : this(mutableListOf(*objects)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     override fun pack(out: OutputStream) { | ||||||
|  |         val size = value.size | ||||||
|  |         when { | ||||||
|  |             size < 16 -> out.write(144 + size) | ||||||
|  |             size < 65536 -> { | ||||||
|  |                 out.write(0xDC) | ||||||
|  |                 out.write(ByteBuffer.allocate(2).putShort(size.toShort()).array()) | ||||||
|  |             } | ||||||
|  |             else -> { | ||||||
|  |                 out.write(0xDD) | ||||||
|  |                 out.write(ByteBuffer.allocate(4).putInt(size).array()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         for (o in value) { | ||||||
|  |             o.pack(out) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val size: Int | ||||||
|  |         get() = value.size | ||||||
|  |  | ||||||
|  |     override fun isEmpty(): Boolean { | ||||||
|  |         return value.isEmpty() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun contains(element: E) = value.contains(element) | ||||||
|  |  | ||||||
|  |     override fun iterator() = value.iterator() | ||||||
|  |  | ||||||
|  |     override fun add(element: E) = value.add(element) | ||||||
|  |  | ||||||
|  |     override fun remove(element: E) = value.remove(element) | ||||||
|  |  | ||||||
|  |     override fun containsAll(elements: Collection<E>) = value.containsAll(elements) | ||||||
|  |  | ||||||
|  |     override fun addAll(elements: Collection<E>) = value.addAll(elements) | ||||||
|  |  | ||||||
|  |     override fun addAll(index: Int, elements: Collection<E>) = value.addAll(index, elements) | ||||||
|  |  | ||||||
|  |     override fun removeAll(elements: Collection<E>) = value.removeAll(elements) | ||||||
|  |  | ||||||
|  |     override fun retainAll(elements: Collection<E>) = value.retainAll(elements) | ||||||
|  |  | ||||||
|  |     override fun clear() = value.clear() | ||||||
|  |  | ||||||
|  |     override fun get(index: Int) = value[index] | ||||||
|  |  | ||||||
|  |     override operator fun set(index: Int, element: E) = value.set(index, element) | ||||||
|  |  | ||||||
|  |     override fun add(index: Int, element: E) = value.add(index, element) | ||||||
|  |  | ||||||
|  |     override fun removeAt(index: Int) = value.removeAt(index) | ||||||
|  |  | ||||||
|  |     override fun indexOf(element: E) = value.indexOf(element) | ||||||
|  |  | ||||||
|  |     override fun lastIndexOf(element: E) = value.lastIndexOf(element) | ||||||
|  |  | ||||||
|  |     override fun listIterator() = value.listIterator() | ||||||
|  |  | ||||||
|  |     override fun listIterator(index: Int) = value.listIterator(index) | ||||||
|  |  | ||||||
|  |     override fun subList(fromIndex: Int, toIndex: Int) = value.subList(fromIndex, toIndex) | ||||||
|  |  | ||||||
|  |     override fun toString() = toJson() | ||||||
|  |  | ||||||
|  |     override fun toJson() = toJson("") | ||||||
|  |  | ||||||
|  |     internal fun toJson(indent: String): String { | ||||||
|  |         val result = StringBuilder() | ||||||
|  |         result.append("[\n") | ||||||
|  |         val iterator = value.iterator() | ||||||
|  |         val indent2 = indent + "  " | ||||||
|  |         while (iterator.hasNext()) { | ||||||
|  |             val item = iterator.next() | ||||||
|  |             result.append(indent2) | ||||||
|  |             result.append(Utils.toJson(item, indent2)) | ||||||
|  |             if (iterator.hasNext()) { | ||||||
|  |                 result.append(',') | ||||||
|  |             } | ||||||
|  |             result.append('\n') | ||||||
|  |         } | ||||||
|  |         result.append("]") | ||||||
|  |         return result.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Unpacker(private val reader: Reader) : MPType.Unpacker<MPArray<*>> { | ||||||
|  |  | ||||||
|  |         override fun doesUnpack(firstByte: Int): Boolean { | ||||||
|  |             return firstByte == 0xDC || firstByte == 0xDD || firstByte and 240 == 144 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Throws(IOException::class) | ||||||
|  |         override fun unpack(firstByte: Int, input: InputStream): MPArray<MPType<*>> { | ||||||
|  |             val size: Int = when { | ||||||
|  |                 firstByte and 240 == 144 -> firstByte and 15 | ||||||
|  |                 firstByte == 0xDC -> input.read() shl 8 or input.read() | ||||||
|  |                 firstByte == 0xDD -> input.read() shl 24 or (input.read() shl 16) or (input.read() shl 8) or input.read() | ||||||
|  |                 else -> throw IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)) | ||||||
|  |             } | ||||||
|  |             val list = mutableListOf<MPType<*>>() | ||||||
|  |             for (i in 0 until size) { | ||||||
|  |                 val value = reader.read(input) | ||||||
|  |                 list.add(value) | ||||||
|  |             } | ||||||
|  |             return MPArray(list) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPBinary.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPBinary.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | /* | ||||||
|  |  * 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.types.Utils.bytes | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Representation of msgpack encoded binary data a.k.a. byte array. | ||||||
|  |  */ | ||||||
|  | data class MPBinary(override val value: ByteArray) : MPType<ByteArray> { | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     override fun pack(out: OutputStream) { | ||||||
|  |         val size = value.size | ||||||
|  |         when { | ||||||
|  |             size < 256 -> { | ||||||
|  |                 out.write(0xC4) | ||||||
|  |                 out.write(size.toByte().toInt()) | ||||||
|  |             } | ||||||
|  |             size < 65536 -> { | ||||||
|  |                 out.write(0xC5) | ||||||
|  |                 out.write(ByteBuffer.allocate(2).putShort(size.toShort()).array()) | ||||||
|  |             } | ||||||
|  |             else -> { | ||||||
|  |                 out.write(0xC6) | ||||||
|  |                 out.write(ByteBuffer.allocate(4).putInt(size).array()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         out.write(value) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is MPBinary) return false | ||||||
|  |         return Arrays.equals(value, other.value) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         return Arrays.hashCode(value) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return toJson() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toJson(): String { | ||||||
|  |         return Utils.base64(value) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Unpacker : MPType.Unpacker<MPBinary> { | ||||||
|  |         override fun doesUnpack(firstByte: Int): Boolean { | ||||||
|  |             return firstByte == 0xC4 || firstByte == 0xC5 || firstByte == 0xC6 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Throws(IOException::class) | ||||||
|  |         override fun unpack(firstByte: Int, input: InputStream): MPBinary { | ||||||
|  |             val size: Int = when (firstByte) { | ||||||
|  |                 0xC4 -> input.read() | ||||||
|  |                 0xC5 -> input.read() shl 8 or input.read() | ||||||
|  |                 0xC6 -> input.read() shl 24 or (input.read() shl 16) or (input.read() shl 8) or input.read() | ||||||
|  |                 else -> throw IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)) | ||||||
|  |             } | ||||||
|  |             return MPBinary(bytes(input, size).array()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPBoolean.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPBoolean.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | /* | ||||||
|  |  * 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 | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Representation of a msgpack encoded boolean. | ||||||
|  |  */ | ||||||
|  | data class MPBoolean(override val value: Boolean) : MPType<Boolean> { | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     override fun pack(out: OutputStream) { | ||||||
|  |         out.write(if (value) { | ||||||
|  |             TRUE | ||||||
|  |         } else { | ||||||
|  |             FALSE | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return value.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toJson(): String { | ||||||
|  |         return value.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Unpacker : MPType.Unpacker<MPBoolean> { | ||||||
|  |  | ||||||
|  |         override fun doesUnpack(firstByte: Int): Boolean { | ||||||
|  |             return firstByte == TRUE || firstByte == FALSE | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         override fun unpack(firstByte: Int, input: InputStream) = when (firstByte) { | ||||||
|  |             TRUE -> MPBoolean(true) | ||||||
|  |             FALSE -> MPBoolean(false) | ||||||
|  |             else -> throw IllegalArgumentException(String.format("0xC2 or 0xC3 expected but was 0x%02x", firstByte)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private const val FALSE = 0xC2 | ||||||
|  |         private const val TRUE = 0xC3 | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPFloat.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPFloat.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | /* | ||||||
|  |  * 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.types.Utils.bytes | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Representation of a msgpack encoded float32 or float64 number. | ||||||
|  |  */ | ||||||
|  | data class MPFloat(override val value: Double, val precision: Precision) : MPType<Double> { | ||||||
|  |  | ||||||
|  |     enum class Precision { | ||||||
|  |         FLOAT32, FLOAT64 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(value: Float) : this(value.toDouble(), Precision.FLOAT32) | ||||||
|  |     constructor(value: Double) : this(value, Precision.FLOAT64) | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     override fun pack(out: OutputStream) { | ||||||
|  |         when (precision) { | ||||||
|  |             MPFloat.Precision.FLOAT32 -> { | ||||||
|  |                 out.write(0xCA) | ||||||
|  |                 out.write(ByteBuffer.allocate(4).putFloat(value.toFloat()).array()) | ||||||
|  |             } | ||||||
|  |             MPFloat.Precision.FLOAT64 -> { | ||||||
|  |                 out.write(0xCB) | ||||||
|  |                 out.write(ByteBuffer.allocate(8).putDouble(value).array()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return value.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toJson(): String { | ||||||
|  |         return value.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Unpacker : MPType.Unpacker<MPFloat> { | ||||||
|  |         override fun doesUnpack(firstByte: Int): Boolean { | ||||||
|  |             return firstByte == 0xCA || firstByte == 0xCB | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Throws(IOException::class) | ||||||
|  |         override fun unpack(firstByte: Int, input: InputStream) = when (firstByte) { | ||||||
|  |             0xCA -> MPFloat(bytes(input, 4).float) | ||||||
|  |             0xCB -> MPFloat(bytes(input, 8).double) | ||||||
|  |             else -> throw IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										116
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPInteger.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPInteger.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | /* | ||||||
|  |  * 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.types.Utils.bytes | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | data class MPInteger(override val value: Long) : MPType<Long> { | ||||||
|  |  | ||||||
|  |     constructor(value: Byte) : this(value.toLong()) | ||||||
|  |     constructor(value: Short) : this(value.toLong()) | ||||||
|  |     constructor(value: Int) : this(value.toLong()) | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     override fun pack(out: OutputStream) { | ||||||
|  |         when (value) { | ||||||
|  |             in -32..127 -> out.write(value.toInt()) | ||||||
|  |             in 0..0xFF -> { | ||||||
|  |                 out.write(0xCC) | ||||||
|  |                 out.write(value.toInt()) | ||||||
|  |             } | ||||||
|  |             in 0..0xFFFF -> { | ||||||
|  |                 out.write(0xCD) | ||||||
|  |                 out.write(ByteBuffer.allocate(2).putShort(value.toShort()).array()) | ||||||
|  |             } | ||||||
|  |             in 0..0xFFFFFFFFL -> { | ||||||
|  |                 out.write(0xCE) | ||||||
|  |                 out.write(ByteBuffer.allocate(4).putInt(value.toInt()).array()) | ||||||
|  |             } | ||||||
|  |             in 0..Long.MAX_VALUE -> { | ||||||
|  |                 out.write(0xCF) | ||||||
|  |                 out.write(ByteBuffer.allocate(8).putLong(value).array()) | ||||||
|  |             } | ||||||
|  |             in Byte.MIN_VALUE..0 -> { | ||||||
|  |                 out.write(0xD0) | ||||||
|  |                 out.write(ByteBuffer.allocate(1).put(value.toByte()).array()) | ||||||
|  |             } | ||||||
|  |             in Short.MIN_VALUE..0 -> { | ||||||
|  |                 out.write(0xD1) | ||||||
|  |                 out.write(ByteBuffer.allocate(2).putShort(value.toShort()).array()) | ||||||
|  |             } | ||||||
|  |             in Int.MIN_VALUE..0 -> { | ||||||
|  |                 out.write(0xD2) | ||||||
|  |                 out.write(ByteBuffer.allocate(4).putInt(value.toInt()).array()) | ||||||
|  |             } | ||||||
|  |             in Long.MIN_VALUE..0 -> { | ||||||
|  |                 out.write(0xD3) | ||||||
|  |                 out.write(ByteBuffer.allocate(8).putLong(value).array()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString() = value.toString() | ||||||
|  |  | ||||||
|  |     override fun toJson() = value.toString() | ||||||
|  |  | ||||||
|  |     class Unpacker : MPType.Unpacker<MPInteger> { | ||||||
|  |         override fun doesUnpack(firstByte: Int): Boolean { | ||||||
|  |             return when (firstByte) { | ||||||
|  |                 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3 -> true | ||||||
|  |                 else -> firstByte and 128 == 0 || firstByte and 224 == 224 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Throws(IOException::class) | ||||||
|  |         override fun unpack(firstByte: Int, input: InputStream): MPInteger { | ||||||
|  |             if (firstByte and 128 == 0 || firstByte and 224 == 224) { | ||||||
|  |                 // The cast needs to happen for the MPInteger to have the correct sign | ||||||
|  |                 return MPInteger(firstByte.toByte()) | ||||||
|  |             } else { | ||||||
|  |                 return when (firstByte) { | ||||||
|  |                     0xCC -> MPInteger(readLong(input, 1)) | ||||||
|  |                     0xCD -> MPInteger(readLong(input, 2)) | ||||||
|  |                     0xCE -> MPInteger(readLong(input, 4)) | ||||||
|  |                     0xCF -> MPInteger(readLong(input, 8)) | ||||||
|  |                     0xD0 -> MPInteger(bytes(input, 1).get()) | ||||||
|  |                     0xD1 -> MPInteger(bytes(input, 2).short) | ||||||
|  |                     0xD2 -> MPInteger(bytes(input, 4).int) | ||||||
|  |                     0xD3 -> MPInteger(bytes(input, 8).long) | ||||||
|  |                     else -> throw IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private fun readLong(input: InputStream, length: Int): Long { | ||||||
|  |             var value: Long = 0 | ||||||
|  |             for (i in 0 until length) { | ||||||
|  |                 value = value shl 8 or input.read().toLong() | ||||||
|  |             } | ||||||
|  |             return value | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										111
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPMap.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPMap.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | 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 | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Representation of a msgpack encoded map. It is recommended to use a [LinkedHashMap] to ensure the order | ||||||
|  |  * of entries. For convenience, it also implements the [Map] interface. | ||||||
|  |  * | ||||||
|  |  * @param <K> key type | ||||||
|  |  * @param <V> value type | ||||||
|  |  */ | ||||||
|  | data class MPMap<K : MPType<*>, V : MPType<*>>(override val value: MutableMap<K, V> = LinkedHashMap<K, V>()) : MPType<Map<K, V>>, MutableMap<K, V> { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     override fun pack(out: OutputStream) { | ||||||
|  |         val size = value.size | ||||||
|  |         when { | ||||||
|  |             size < 16 -> { | ||||||
|  |                 out.write(0x80 + size) | ||||||
|  |             } | ||||||
|  |             size < 65536 -> { | ||||||
|  |                 out.write(0xDE) | ||||||
|  |                 out.write(ByteBuffer.allocate(2).putShort(size.toShort()).array()) | ||||||
|  |             } | ||||||
|  |             else -> { | ||||||
|  |                 out.write(0xDF) | ||||||
|  |                 out.write(ByteBuffer.allocate(4).putInt(size).array()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         for ((key, value1) in value) { | ||||||
|  |             key.pack(out) | ||||||
|  |             value1.pack(out) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val size: Int | ||||||
|  |         get() = value.size | ||||||
|  |  | ||||||
|  |     override fun isEmpty() = value.isEmpty() | ||||||
|  |  | ||||||
|  |     override fun containsKey(key: K) = value.containsKey(key) | ||||||
|  |  | ||||||
|  |     override fun containsValue(value: V) = this.value.containsValue(value) | ||||||
|  |  | ||||||
|  |     override fun get(key: K) = value[key] | ||||||
|  |  | ||||||
|  |     override fun putIfAbsent(key: K, value: V) = this.value.putIfAbsent(key, value) | ||||||
|  |  | ||||||
|  |     override fun put(key: K, value: V): V? = this.value.put(key, value) | ||||||
|  |  | ||||||
|  |     override fun remove(key: K): V? = value.remove(key) | ||||||
|  |  | ||||||
|  |     override fun putAll(from: Map<out K, V>) = value.putAll(from) | ||||||
|  |  | ||||||
|  |     override fun clear() = value.clear() | ||||||
|  |  | ||||||
|  |     override val keys: MutableSet<K> | ||||||
|  |         get() = value.keys | ||||||
|  |  | ||||||
|  |     override val values: MutableCollection<V> | ||||||
|  |         get() = value.values | ||||||
|  |  | ||||||
|  |     override val entries: MutableSet<MutableMap.MutableEntry<K, V>> | ||||||
|  |         get() = value.entries | ||||||
|  |  | ||||||
|  |     override fun toString() = toJson() | ||||||
|  |  | ||||||
|  |     internal fun toJson(indent: String) = if (value.entries.isEmpty()) { | ||||||
|  |         "{}" | ||||||
|  |     } else { | ||||||
|  |         value.entries.joinToString( | ||||||
|  |                 separator = ",\n", | ||||||
|  |                 prefix = "{\n", | ||||||
|  |                 postfix = "\n$indent}") { i -> | ||||||
|  |             "$indent  ${Utils.toJson(i.key, "$indent  ")}: ${Utils.toJson(i.value, "$indent  ")}" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toJson(): String { | ||||||
|  |         return toJson("") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Unpacker(private val reader: Reader) : MPType.Unpacker<MPMap<*, *>> { | ||||||
|  |  | ||||||
|  |         override fun doesUnpack(firstByte: Int): Boolean { | ||||||
|  |             return firstByte == 0xDE || firstByte == 0xDF || firstByte and 0xF0 == 0x80 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Throws(IOException::class) | ||||||
|  |         override fun unpack(firstByte: Int, input: InputStream): MPMap<MPType<*>, MPType<*>> { | ||||||
|  |             val size: Int = when { | ||||||
|  |                 firstByte and 0xF0 == 0x80 -> firstByte and 0x0F | ||||||
|  |                 firstByte == 0xDE -> input.read() shl 8 or input.read() | ||||||
|  |                 firstByte == 0xDF -> input.read() shl 24 or (input.read() shl 16) or (input.read() shl 8) or input.read() | ||||||
|  |                 else -> throw IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)) | ||||||
|  |             } | ||||||
|  |             val map = LinkedHashMap<MPType<*>, MPType<*>>() | ||||||
|  |             for (i in 0 until size) { | ||||||
|  |                 val key = reader.read(input) | ||||||
|  |                 val value = reader.read(input) | ||||||
|  |                 map.put(key, value) | ||||||
|  |             } | ||||||
|  |             return MPMap(map) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPNil.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPNil.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | /* | ||||||
|  |  * 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 | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Representation of msgpack encoded nil / null. | ||||||
|  |  */ | ||||||
|  | object MPNil : MPType<Void?> { | ||||||
|  |  | ||||||
|  |     override val value: Void? = null | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     override fun pack(out: OutputStream) = out.write(NIL) | ||||||
|  |  | ||||||
|  |     override fun toString() = "null" | ||||||
|  |  | ||||||
|  |     override fun toJson() = "null" | ||||||
|  |  | ||||||
|  |     class Unpacker : MPType.Unpacker<MPNil> { | ||||||
|  |  | ||||||
|  |         override fun doesUnpack(firstByte: Int) = firstByte == NIL | ||||||
|  |  | ||||||
|  |         override fun unpack(firstByte: Int, input: InputStream): MPNil { | ||||||
|  |             return when (firstByte) { | ||||||
|  |                 NIL -> MPNil | ||||||
|  |                 else -> throw IllegalArgumentException(String.format("0xC0 expected but was 0x%02x", firstByte)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private val NIL = 0xC0 | ||||||
|  | } | ||||||
							
								
								
									
										135
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPString.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/main/kotlin/ch/dissem/msgpack/types/MPString.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | |||||||
|  | /* | ||||||
|  |  * 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.types.Utils.bytes | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.nio.charset.Charset | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Representation of a msgpack encoded string. The encoding is automatically selected according to the string's length. | ||||||
|  |  * | ||||||
|  |  * The default encoding is UTF-8. | ||||||
|  |  */ | ||||||
|  | data class MPString(override val value: String) : MPType<String>, CharSequence { | ||||||
|  |  | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     override fun pack(out: OutputStream) { | ||||||
|  |         val bytes = value.toByteArray(encoding) | ||||||
|  |         val size = bytes.size | ||||||
|  |         when { | ||||||
|  |             size < 32 -> out.write(FIXSTR_PREFIX + size) | ||||||
|  |             size < STR8_LIMIT -> { | ||||||
|  |                 out.write(STR8_PREFIX) | ||||||
|  |                 out.write(size) | ||||||
|  |             } | ||||||
|  |             size < STR16_LIMIT -> { | ||||||
|  |                 out.write(STR16_PREFIX) | ||||||
|  |                 out.write(ByteBuffer.allocate(2).putShort(size.toShort()).array()) | ||||||
|  |             } | ||||||
|  |             else -> { | ||||||
|  |                 out.write(STR32_PREFIX) | ||||||
|  |                 out.write(ByteBuffer.allocate(4).putInt(size).array()) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         out.write(bytes) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val length: Int = value.length | ||||||
|  |  | ||||||
|  |     override fun get(index: Int): Char { | ||||||
|  |         return value[index] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { | ||||||
|  |         return value.subSequence(startIndex, endIndex) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return value | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toJson(): String { | ||||||
|  |         val result = StringBuilder(value.length + 4) | ||||||
|  |         result.append('"') | ||||||
|  |         value.forEach { | ||||||
|  |             when (it) { | ||||||
|  |                 '\\', '"', '/' -> result.append('\\').append(it) | ||||||
|  |                 '\b' -> result.append("\\b") | ||||||
|  |                 '\t' -> result.append("\\t") | ||||||
|  |                 '\n' -> result.append("\\n") | ||||||
|  |                 '\r' -> result.append("\\r") | ||||||
|  |                 else -> if (it < ' ') { | ||||||
|  |                     result.append("\\u") | ||||||
|  |                     val hex = Integer.toHexString(it.toInt()) | ||||||
|  |                     var j = 0 | ||||||
|  |                     while (j + hex.length < 4) { | ||||||
|  |                         result.append('0') | ||||||
|  |                         j++ | ||||||
|  |                     } | ||||||
|  |                     result.append(hex) | ||||||
|  |                 } else { | ||||||
|  |                     result.append(it) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         result.append('"') | ||||||
|  |         return result.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Unpacker : MPType.Unpacker<MPString> { | ||||||
|  |         override fun doesUnpack(firstByte: Int): Boolean { | ||||||
|  |             return firstByte == STR8_PREFIX || firstByte == STR16_PREFIX || firstByte == STR32_PREFIX | ||||||
|  |                     || firstByte and FIXSTR_PREFIX_FILTER == FIXSTR_PREFIX | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Throws(IOException::class) | ||||||
|  |         override fun unpack(firstByte: Int, input: InputStream): MPString { | ||||||
|  |             val size: Int = when { | ||||||
|  |                 firstByte and FIXSTR_PREFIX_FILTER == FIXSTR_PREFIX -> firstByte and FIXSTR_FILTER | ||||||
|  |                 firstByte == STR8_PREFIX -> input.read() | ||||||
|  |                 firstByte == STR16_PREFIX -> input.read() shl 8 or input.read() | ||||||
|  |                 firstByte == STR32_PREFIX -> input.read() shl 24 or (input.read() shl 16) or (input.read() shl 8) or input.read() | ||||||
|  |                 else -> throw IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)) | ||||||
|  |             } | ||||||
|  |             return MPString(String(bytes(input, size).array(), encoding)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val FIXSTR_PREFIX = 160 | ||||||
|  |         private val FIXSTR_PREFIX_FILTER = 224 | ||||||
|  |         private val STR8_PREFIX = 0xD9 | ||||||
|  |         private val STR8_LIMIT = 256 | ||||||
|  |         private val STR16_PREFIX = 0xDA | ||||||
|  |         private val STR16_LIMIT = 65536 | ||||||
|  |         private val STR32_PREFIX = 0xDB | ||||||
|  |         private val FIXSTR_FILTER = 31 | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Use this 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 [MPString]s, but if you have inconsistent encoding in your | ||||||
|  |          * format you're lost anyway. | ||||||
|  |          */ | ||||||
|  |         var encoding = Charset.forName("UTF-8") | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -14,25 +14,27 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package ch.dissem.msgpack.types; | package ch.dissem.msgpack.types | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException | ||||||
| import java.io.InputStream; | import java.io.InputStream | ||||||
| import java.io.OutputStream; | import java.io.OutputStream | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Representation of some msgpack encoded data. |  * Representation of some msgpack encoded data. | ||||||
|  */ |  */ | ||||||
| public interface MPType<T> { | interface MPType<out T> { | ||||||
|     interface Unpacker<M extends MPType> { |     interface Unpacker<out M : MPType<*>> { | ||||||
|         boolean is(int firstByte); |         fun doesUnpack(firstByte: Int): Boolean | ||||||
| 
 | 
 | ||||||
|         M unpack(int firstByte, InputStream in) throws IOException; |         @Throws(IOException::class) | ||||||
|  |         fun unpack(firstByte: Int, input: InputStream): M | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     T getValue(); |     val value: T | ||||||
| 
 | 
 | ||||||
|     void pack(OutputStream out) throws IOException; |     @Throws(IOException::class) | ||||||
|  |     fun pack(out: OutputStream) | ||||||
| 
 | 
 | ||||||
|     String toJson(); |     fun toJson(): String | ||||||
| } | } | ||||||
							
								
								
									
										133
									
								
								src/main/kotlin/ch/dissem/msgpack/types/Utils.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/main/kotlin/ch/dissem/msgpack/types/Utils.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | /* | ||||||
|  |  * 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.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | object Utils { | ||||||
|  |     private val BASE64_CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray() | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns a [ByteBuffer] containing the next `count` bytes from the [InputStream]. | ||||||
|  |      */ | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     internal fun bytes(`in`: InputStream, count: Int): ByteBuffer { | ||||||
|  |         val result = ByteArray(count) | ||||||
|  |         var off = 0 | ||||||
|  |         while (off < count) { | ||||||
|  |             val read = `in`.read(result, off, count - off) | ||||||
|  |             if (read < 0) { | ||||||
|  |                 throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off") | ||||||
|  |             } | ||||||
|  |             off += read | ||||||
|  |         } | ||||||
|  |         return ByteBuffer.wrap(result) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Helper method to decide which types support extra indention (for pretty printing JSON) | ||||||
|  |      */ | ||||||
|  |     internal fun toJson(type: MPType<*>, indent: String): String { | ||||||
|  |         if (type is MPMap<*, *>) { | ||||||
|  |             return type.toJson(indent) | ||||||
|  |         } | ||||||
|  |         if (type is MPArray<*>) { | ||||||
|  |             return type.toJson(indent) | ||||||
|  |         } | ||||||
|  |         return type.toJson() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Slightly improved code from https://en.wikipedia.org/wiki/Base64 | ||||||
|  |      */ | ||||||
|  |     internal fun base64(data: ByteArray): String { | ||||||
|  |         val result = StringBuilder(data.size * 4 / 3 + 3) | ||||||
|  |         var b: Int | ||||||
|  |         var i = 0 | ||||||
|  |         while (i < data.size) { | ||||||
|  |             b = data[i].toInt() and 0xFC shr 2 | ||||||
|  |             result.append(BASE64_CODES[b]) | ||||||
|  |             b = data[i].toInt() and 0x03 shl 4 | ||||||
|  |             if (i + 1 < data.size) { | ||||||
|  |                 b = b or (data[i + 1].toInt() and 0xF0 shr 4) | ||||||
|  |                 result.append(BASE64_CODES[b]) | ||||||
|  |                 b = data[i + 1].toInt() and 0x0F shl 2 | ||||||
|  |                 if (i + 2 < data.size) { | ||||||
|  |                     b = b or (data[i + 2].toInt() and 0xC0 shr 6) | ||||||
|  |                     result.append(BASE64_CODES[b]) | ||||||
|  |                     b = data[i + 2].toInt() and 0x3F | ||||||
|  |                     result.append(BASE64_CODES[b]) | ||||||
|  |                 } else { | ||||||
|  |                     result.append(BASE64_CODES[b]) | ||||||
|  |                     result.append('=') | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 result.append(BASE64_CODES[b]) | ||||||
|  |                 result.append("==") | ||||||
|  |             } | ||||||
|  |             i += 3 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return result.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     val String.mp | ||||||
|  |         @JvmName("mp") | ||||||
|  |         get() = MPString(this) | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     val Boolean.mp | ||||||
|  |         @JvmName("mp") | ||||||
|  |         get() = MPBoolean(this) | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     val Float.mp | ||||||
|  |         @JvmName("mp") | ||||||
|  |         get() = MPFloat(this) | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     val Double.mp | ||||||
|  |         @JvmName("mp") | ||||||
|  |         get() = MPFloat(this) | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     val Int.mp | ||||||
|  |         @JvmName("mp") | ||||||
|  |         get() = MPInteger(this.toLong()) | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     val Long.mp | ||||||
|  |         @JvmName("mp") | ||||||
|  |         get() = MPInteger(this) | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     val ByteArray.mp | ||||||
|  |         get() = MPBinary(this) | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     fun mp(vararg data: Byte): MPBinary { | ||||||
|  |         return MPBinary(data) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     fun nil(): MPNil { | ||||||
|  |         return MPNil | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,270 +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; |  | ||||||
|  |  | ||||||
| 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.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 = Reader.getInstance(); |  | ||||||
|  |  | ||||||
|     @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 { |  | ||||||
|         @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") |  | ||||||
|         MPMap<MPString, MPType<?>> object = new MPMap<>(); |  | ||||||
|         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"))); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     @SuppressWarnings("unchecked") |  | ||||||
|     public void ensureMPArrayIsEncodedAndDecodedCorrectly() throws Exception { |  | ||||||
|         MPArray<MPType<?>> array = new MPArray<>( |  | ||||||
|                 mp(new byte[]{1, 3, 3, 7}), |  | ||||||
|                 mp(false), |  | ||||||
|                 mp(Math.PI), |  | ||||||
|                 mp(1.5f), |  | ||||||
|                 mp(42), |  | ||||||
|                 new MPMap<MPNil, MPNil>(), |  | ||||||
|                 nil(), |  | ||||||
|                 mp("yay! \uD83E\uDD13") |  | ||||||
|         ); |  | ||||||
|         ByteArrayOutputStream out = new ByteArrayOutputStream(); |  | ||||||
|         array.pack(out); |  | ||||||
|         MPType read = reader.read(new ByteArrayInputStream(out.toByteArray())); |  | ||||||
|         assertThat(read, instanceOf(MPArray.class)); |  | ||||||
|         assertThat((MPArray<MPType<?>>) 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 |  | ||||||
|     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 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); |  | ||||||
|         ensureStringIsEncodedAndDecodedCorrectly(31); |  | ||||||
|         ensureStringIsEncodedAndDecodedCorrectly(32); |  | ||||||
|         ensureStringIsEncodedAndDecodedCorrectly(255); |  | ||||||
|         ensureStringIsEncodedAndDecodedCorrectly(256); |  | ||||||
|         ensureStringIsEncodedAndDecodedCorrectly(65535); |  | ||||||
|         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(); |  | ||||||
|         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<MPNil> list = new ArrayList<>(length); |  | ||||||
|         for (int i = 0; i < length; i++) { |  | ||||||
|             list.add(nil); |  | ||||||
|         } |  | ||||||
|         MPArray<MPNil> 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<MPNil>) 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 size) throws Exception { |  | ||||||
|         MPNil nil = new MPNil(); |  | ||||||
|         HashMap<MPInteger, MPNil> map = new HashMap<>(size); |  | ||||||
|         for (int i = 0; i < size; i++) { |  | ||||||
|             map.put(mp(i), nil); |  | ||||||
|         } |  | ||||||
|         MPMap<MPInteger, MPNil> 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<MPInteger, MPNil>) 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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										268
									
								
								src/test/kotlin/ch/dissem/msgpack/ReaderTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/test/kotlin/ch/dissem/msgpack/ReaderTest.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | |||||||
|  | /* | ||||||
|  |  * 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.* | ||||||
|  | import ch.dissem.msgpack.types.Utils.mp | ||||||
|  | import org.hamcrest.CoreMatchers.equalTo | ||||||
|  | import org.hamcrest.CoreMatchers.instanceOf | ||||||
|  | import org.junit.Assert.assertThat | ||||||
|  | import org.junit.Test | ||||||
|  | import java.io.ByteArrayInputStream | ||||||
|  | import java.io.ByteArrayOutputStream | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | class ReaderTest { | ||||||
|  |     @Test | ||||||
|  |     fun `ensure demo json is parsed correctly`() { | ||||||
|  |         val read = Reader.read(stream("demo.mp")) | ||||||
|  |         assertThat(read, instanceOf(MPMap::class.java)) | ||||||
|  |         assertThat(read.toJson(), equalTo(string("demo.json"))) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure demo json is encoded correctly`() { | ||||||
|  |         val obj = MPMap<MPString, MPType<*>>() | ||||||
|  |         obj.put("compact".mp, true.mp) | ||||||
|  |         obj.put("schema".mp, 0.mp) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         obj.pack(out) | ||||||
|  |         assertThat(out.toByteArray(), equalTo(bytes("demo.mp"))) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure mpArray is encoded and decoded correctly`() { | ||||||
|  |         val array = MPArray( | ||||||
|  |                 byteArrayOf(1, 3, 3, 7).mp, | ||||||
|  |                 false.mp, | ||||||
|  |                 Math.PI.mp, | ||||||
|  |                 1.5f.mp, | ||||||
|  |                 42.mp, | ||||||
|  |                 MPMap<MPNil, MPNil>(), | ||||||
|  |                 MPNil, | ||||||
|  |                 "yay! \uD83E\uDD13".mp | ||||||
|  |         ) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         array.pack(out) | ||||||
|  |         val read = Reader.read(ByteArrayInputStream(out.toByteArray())) | ||||||
|  |         assertThat(read, instanceOf(MPArray::class.java)) | ||||||
|  |         @Suppress("UNCHECKED_CAST") | ||||||
|  |         assertThat(read as MPArray<MPType<*>>, equalTo(array)) | ||||||
|  |         assertThat(read.toJson(), equalTo("[\n  AQMDBw==,\n  false,\n  3.141592653589793,\n  1.5,\n  42,\n  {},\n  null,\n  \"yay! 🤓\"\n]")) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure float is encoded and decoded correctly`() { | ||||||
|  |         val expected = MPFloat(1.5f) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         expected.pack(out) | ||||||
|  |         val read = Reader.read(ByteArrayInputStream(out.toByteArray())) | ||||||
|  |         assertThat(read, instanceOf<Any>(MPFloat::class.java)) | ||||||
|  |         val actual = read as MPFloat | ||||||
|  |         assertThat(actual, equalTo(expected)) | ||||||
|  |         assertThat(actual.precision, equalTo(MPFloat.Precision.FLOAT32)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure double is encoded and decoded correctly`() { | ||||||
|  |         val expected = MPFloat(Math.PI) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         expected.pack(out) | ||||||
|  |         val read = Reader.read(ByteArrayInputStream(out.toByteArray())) | ||||||
|  |         assertThat(read, instanceOf<Any>(MPFloat::class.java)) | ||||||
|  |         val actual = read as MPFloat | ||||||
|  |         assertThat(actual, equalTo(expected)) | ||||||
|  |         assertThat(actual.value, equalTo(Math.PI)) | ||||||
|  |         assertThat(actual.precision, equalTo(MPFloat.Precision.FLOAT64)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure longs are encoded and decoded correctly`() { | ||||||
|  |         // 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(java.lang.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.toLong(), 5) | ||||||
|  |         // int 64 | ||||||
|  |         ensureLongIsEncodedAndDecodedCorrectly(-2147483649L, 9) | ||||||
|  |         ensureLongIsEncodedAndDecodedCorrectly(java.lang.Long.MIN_VALUE, 9) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun ensureLongIsEncodedAndDecodedCorrectly(`val`: Long, bytes: Int) { | ||||||
|  |         val value = `val`.mp | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         value.pack(out) | ||||||
|  |         val read = Reader.read(ByteArrayInputStream(out.toByteArray())) | ||||||
|  |         assertThat(out.size(), equalTo(bytes)) | ||||||
|  |         assertThat(read, instanceOf<Any>(MPInteger::class.java)) | ||||||
|  |         assertThat(read as MPInteger, equalTo(value)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure strings are encoded and decoded correctly`() { | ||||||
|  |         ensureStringIsEncodedAndDecodedCorrectly(0) | ||||||
|  |         ensureStringIsEncodedAndDecodedCorrectly(31) | ||||||
|  |         ensureStringIsEncodedAndDecodedCorrectly(32) | ||||||
|  |         ensureStringIsEncodedAndDecodedCorrectly(255) | ||||||
|  |         ensureStringIsEncodedAndDecodedCorrectly(256) | ||||||
|  |         ensureStringIsEncodedAndDecodedCorrectly(65535) | ||||||
|  |         ensureStringIsEncodedAndDecodedCorrectly(65536) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure json strings are escaped correctly`() { | ||||||
|  |         val builder = StringBuilder() | ||||||
|  |         var c = '\u0001' | ||||||
|  |         while (c < ' ') { | ||||||
|  |             builder.append(c) | ||||||
|  |             c++ | ||||||
|  |         } | ||||||
|  |         val string = MPString(builder.toString()) | ||||||
|  |         assertThat(string.toJson(), equalTo("\"\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\u000c\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\"")) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun ensureStringIsEncodedAndDecodedCorrectly(length: Int) { | ||||||
|  |         val value = MPString(stringWithLength(length)) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         value.pack(out) | ||||||
|  |         val read = Reader.read(ByteArrayInputStream(out.toByteArray())) | ||||||
|  |         assertThat(read, instanceOf<Any>(MPString::class.java)) | ||||||
|  |         assertThat(read as MPString, equalTo(value)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure binaries are encoded and decoded correctly`() { | ||||||
|  |         ensureBinaryIsEncodedAndDecodedCorrectly(0) | ||||||
|  |         ensureBinaryIsEncodedAndDecodedCorrectly(255) | ||||||
|  |         ensureBinaryIsEncodedAndDecodedCorrectly(256) | ||||||
|  |         ensureBinaryIsEncodedAndDecodedCorrectly(65535) | ||||||
|  |         ensureBinaryIsEncodedAndDecodedCorrectly(65536) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun ensureBinaryIsEncodedAndDecodedCorrectly(length: Int) { | ||||||
|  |         val value = MPBinary(ByteArray(length)) | ||||||
|  |         RANDOM.nextBytes(value.value) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         value.pack(out) | ||||||
|  |         val read = Reader.read(ByteArrayInputStream(out.toByteArray())) | ||||||
|  |         assertThat(read, instanceOf<Any>(MPBinary::class.java)) | ||||||
|  |         assertThat(read as MPBinary, equalTo(value)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure arrays are encoded and decoded correctly`() { | ||||||
|  |         ensureArrayIsEncodedAndDecodedCorrectly(0) | ||||||
|  |         ensureArrayIsEncodedAndDecodedCorrectly(15) | ||||||
|  |         ensureArrayIsEncodedAndDecodedCorrectly(16) | ||||||
|  |         ensureArrayIsEncodedAndDecodedCorrectly(65535) | ||||||
|  |         ensureArrayIsEncodedAndDecodedCorrectly(65536) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun ensureArrayIsEncodedAndDecodedCorrectly(length: Int) { | ||||||
|  |         val nil = MPNil | ||||||
|  |         val list = ArrayList<MPNil>(length) | ||||||
|  |         for (i in 0 until length) { | ||||||
|  |             list.add(nil) | ||||||
|  |         } | ||||||
|  |         val value = MPArray(list) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         value.pack(out) | ||||||
|  |         val read = Reader.read(ByteArrayInputStream(out.toByteArray())) | ||||||
|  |         assertThat(read, instanceOf(MPArray::class.java)) | ||||||
|  |         @Suppress("UNCHECKED_CAST") | ||||||
|  |         assertThat(read as MPArray<MPNil>, equalTo(value)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     fun `ensure maps are encoded and decoded correctly`() { | ||||||
|  |         ensureMapIsEncodedAndDecodedCorrectly(0) | ||||||
|  |         ensureMapIsEncodedAndDecodedCorrectly(15) | ||||||
|  |         ensureMapIsEncodedAndDecodedCorrectly(16) | ||||||
|  |         ensureMapIsEncodedAndDecodedCorrectly(65535) | ||||||
|  |         ensureMapIsEncodedAndDecodedCorrectly(65536) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun ensureMapIsEncodedAndDecodedCorrectly(size: Int) { | ||||||
|  |         val nil = MPNil | ||||||
|  |         val map = HashMap<MPInteger, MPNil>(size) | ||||||
|  |         for (i in 0 until size) { | ||||||
|  |             map.put(i.mp, nil) | ||||||
|  |         } | ||||||
|  |         val value = MPMap(map) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         value.pack(out) | ||||||
|  |         val read = Reader.read(ByteArrayInputStream(out.toByteArray())) | ||||||
|  |         assertThat(read, instanceOf<Any>(MPMap::class.java)) | ||||||
|  |         @Suppress("UNCHECKED_CAST") | ||||||
|  |         assertThat(read as MPMap<MPInteger, MPNil>, equalTo(value)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun stringWithLength(length: Int): String { | ||||||
|  |         val result = StringBuilder(length) | ||||||
|  |         for (i in 0 until length) { | ||||||
|  |             result.append('a') | ||||||
|  |         } | ||||||
|  |         return result.toString() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun stream(resource: String): InputStream { | ||||||
|  |         return javaClass.classLoader.getResourceAsStream(resource) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun bytes(resource: String): ByteArray { | ||||||
|  |         val `in` = stream(resource) | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         val buffer = ByteArray(100) | ||||||
|  |         var size = `in`.read(buffer) | ||||||
|  |         while (size >= 0) { | ||||||
|  |             out.write(buffer, 0, size) | ||||||
|  |             size = `in`.read(buffer) | ||||||
|  |         } | ||||||
|  |         return out.toByteArray() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun string(resource: String): String { | ||||||
|  |         return String(bytes(resource)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val RANDOM = Random() | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user