diff --git a/.idea/copyright/msgpack.xml b/.idea/copyright/msgpack.xml new file mode 100644 index 000000000..0859c620a --- /dev/null +++ b/.idea/copyright/msgpack.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 000000000..8635b5bef --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index cd27c1101..9dd79943e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,14 @@ language: scala -scala: - - 2.11.1 + +cache: + directories: + - $HOME/.m2/repository/ + - $HOME/.ivy2/cache/ + - $HOME/.sbt/boot/ + +sudo: false jdk: - - openjdk6 - openjdk7 - oraclejdk7 - oraclejdk8 @@ -12,10 +17,6 @@ branches: only: - /^v07.*$/ -script: sbt ++$TRAVIS_SCALA_VERSION test - -notifications: - email: - - ozawa.tsuyoshi@gmail.com - - leo@xerial.org - - komamitsu@gmail.com +script: + - ./sbt test + - ./sbt test -J-Dmsgpack.universal-buffer=true diff --git a/README.md b/README.md index da7e4cfa6..d2787d7e0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ MessagePack for Java * Message Pack specification: -MessagePack v7 (0.7.x) is a faster implementation of the previous version [v06](https://github.com/msgpack/msgpack-java/tree/v06), and supports all of the message pack types, including [extended format](https://github.com/msgpack/msgpack/blob/master/spec.md#formats-ext). +MessagePack v7 (or later) is a faster implementation of the previous version [v06](https://github.com/msgpack/msgpack-java/tree/v06), and +supports all of the message pack types, including [extension format](https://github.com/msgpack/msgpack/blob/master/spec.md#formats-ext). ## Limitation - Value API is in a designing phase: https://github.com/msgpack/msgpack-java/pull/109 @@ -17,16 +18,27 @@ For Maven users: org.msgpack msgpack-core - 0.7.0-p5 + 0.8.0 ``` For sbt users: ``` -libraryDependencies += "org.msgpack" % "msgpack-core" % "0.7.0-p5" +libraryDependencies += "org.msgpack" % "msgpack-core" % "0.8.0" ``` -- [Usage examples](msgpack-core/src/main/java/org/msgpack/core/example/MessagePackExample.java) +For gradle users: +``` +repositories { + mavenCentral() +} + +dependencies { + compile 'org.msgpack:msgpack-core:0.8.0' +} +``` + +- [Usage examples](msgpack-core/src/test/java/org/msgpack/core/example/MessagePackExample.java) msgpack-java supports serialization and deserialization of Java objects through [jackson-databind](https://github.com/FasterXML/jackson-databind). For details, see [msgpack-jackson/README.md](msgpack-jackson/README.md). The template-based serialization mechanism used in v06 is deprecated. @@ -38,6 +50,10 @@ For details, see [msgpack-jackson/README.md](msgpack-jackson/README.md). The tem msgpack-java uses [sbt](http://www.scala-sbt.org/) for building the projects. For the basic usage of sbt, see: * [Building Java projects with sbt](http://xerial.org/blog/2014/03/24/sbt/) +Coding style + * msgpack-java uses [the same coding style](https://github.com/airlift/codestyle) with Facebook Presto + * [IntelliJ setting file](https://raw.githubusercontent.com/airlift/codestyle/master/IntelliJIdea13/Airlift.xml) + ### Basic sbt commands Enter the sbt console: ``` @@ -55,6 +71,7 @@ Here is a list of sbt commands for daily development: > package # Create a jar file in the target folder of each project > findbugs # Produce findbugs report in target/findbugs > jacoco:cover # Report the code coverage of tests to target/jacoco folder +> jcheckStyle # Run check style ``` ### Publishing diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1fa8c0f7a..52b12f9d4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,12 +1,44 @@ # Release Notes -* 2015-02-11 0.7.0-p6 +* 0.8.0 + * Split MessagePack.Config into MessagePack.Packer/UnpackerConfig + * Changed MessageBuffer API + * It allows releasing the previously allocated buffers upon MessageBufferInput.next() call. + * MessageBufferOutput now can read data from external byte arrays + * MessagePacker supports addPayload(byte[]) to feed the data from an external data source + * This saves the cost of copying large data to the internal message buffer + * Performance improvement of packString + +* 0.7.1 + * Fix ImmutableLongValueImpl#asShort [#287](https://github.com/msgpack/msgpack-java/pull/287) + +* 0.7.0 + * Support non-string key in jackson-dataformat-msgpack + * Update the version of jackson-databind to 2.6.3 + * Several bug fixes + +* 0.7.0-M6 + * Add a prototype of Value implementation + * Apply strict coding style + * Several bug fixes + +* 0.7.0-p9 + * Fix [#217](https://github.com/msgpack/msgpack-java/issues/217) when reading from SockectInputStream + +* 0.7.0-p8 + * Support Extension type (defined in MessagePack) in msgpack-jackson + * Support BigDecimal type (defined in Jackson) in msgpack-jackson + * Fix MessageUnpacker#unpackString [#215](https://github.com/msgpack/msgpack-java/pull/215), [#216](https://github.com/msgpack/msgpack-java/pull/216) + +* 0.7.0-p7 + * Google App Engine (GAE) support + +* 0.7.0-p6 * Add MessagePacker.getTotalWrittenBytes() -* 2015-01-28 0.7.0-p5 +* 0.7.0-p5 * Fix skipValue [#185](https://github.com/msgpack/msgpack-java/pull/185) -* 2015-01-19 0.7.0-p4 +* 0.7.0-p4 * Supporting some java6 platform and Android - diff --git a/build.sbt b/build.sbt new file mode 100644 index 000000000..365719b83 --- /dev/null +++ b/build.sbt @@ -0,0 +1,105 @@ +import de.johoop.findbugs4sbt.ReportType +import ReleaseTransformations._ + +val buildSettings = findbugsSettings ++ jacoco.settings ++ Seq[Setting[_]]( + organization := "org.msgpack", + organizationName := "MessagePack", + organizationHomepage := Some(new URL("http://msgpack.org/")), + description := "MessagePack for Java", + scalaVersion := "2.11.7", + logBuffered in Test := false, + autoScalaLibrary := false, + crossPaths := false, + // For performance testing, ensure each test run one-by-one + concurrentRestrictions in Global := Seq( + Tags.limit(Tags.Test, 1) + ), + // JVM options for building + scalacOptions ++= Seq("-encoding", "UTF-8", "-deprecation", "-unchecked", "-target:jvm-1.6", "-feature"), + javaOptions in Test ++= Seq("-ea"), + javacOptions in (Compile, compile) ++= Seq("-encoding", "UTF-8", "-Xlint:unchecked", "-Xlint:deprecation", "-source", "1.6", "-target", "1.6"), + // Use lenient validation mode when generating Javadoc (for Java8) + javacOptions in doc := { + val opts = Seq("-source", "1.6") + if (scala.util.Properties.isJavaAtLeast("1.8")) { + opts ++ Seq("-Xdoclint:none") + } + else { + opts + } + }, + // Release settings + releaseTagName := { (version in ThisBuild).value }, + releaseProcess := Seq[ReleaseStep]( + checkSnapshotDependencies, + inquireVersions, + runClean, + runTest, + setReleaseVersion, + commitReleaseVersion, + tagRelease, + ReleaseStep(action = Command.process("publishSigned", _)), + setNextVersion, + commitNextVersion, + ReleaseStep(action = Command.process("sonatypeReleaseAll", _)), + pushChanges + ), + + // Jacoco code coverage report + parallelExecution in jacoco.Config := false, + + // Find bugs + findbugsReportType := Some(ReportType.FancyHtml), + findbugsReportPath := Some(crossTarget.value / "findbugs" / "report.html"), + + // Style check config: (sbt-jchekcstyle) + jcheckStyleConfig := "facebook", + + // Run jcheckstyle both for main and test codes + compile <<= (compile in Compile) dependsOn (jcheckStyle in Compile), + compile <<= (compile in Test) dependsOn (jcheckStyle in Test) +) + +val junitInterface = "com.novocode" % "junit-interface" % "0.11" % "test" + +// Project settings +lazy val root = Project(id = "msgpack-java", base = file(".")) + .settings( + buildSettings, + // Do not publish the root project + publishArtifact := false, + publish := {}, + publishLocal := {}, + findbugs := { + // do not run findbugs for the root project + } + ).aggregate(msgpackCore, msgpackJackson) + +lazy val msgpackCore = Project(id = "msgpack-core", base = file("msgpack-core")) + .settings( + buildSettings, + description := "Core library of the MessagePack for Java", + libraryDependencies ++= Seq( + // msgpack-core should have no external dependencies + junitInterface, + "org.scalatest" %% "scalatest" % "2.2.4" % "test", + "org.scalacheck" %% "scalacheck" % "1.12.2" % "test", + "org.xerial" % "xerial-core" % "3.3.6" % "test", + "org.msgpack" % "msgpack" % "0.6.11" % "test", + "commons-codec" % "commons-codec" % "1.10" % "test", + "com.typesafe.akka" %% "akka-actor" % "2.3.9" % "test" + ) + ) + +lazy val msgpackJackson = Project(id = "msgpack-jackson", base = file("msgpack-jackson")) + .settings( + buildSettings, + name := "jackson-dataformat-msgpack", + description := "Jackson extension that adds support for MessagePack", + libraryDependencies ++= Seq( + "com.fasterxml.jackson.core" % "jackson-databind" % "2.6.3", + junitInterface, + "org.apache.commons" % "commons-math3" % "3.4.1" % "test" + ), + testOptions += Tests.Argument(TestFrameworks.JUnit, "-v") + ).dependsOn(msgpackCore) diff --git a/msgpack-core/src/main/java/org/msgpack/core/ExtendedTypeHeader.java b/msgpack-core/src/main/java/org/msgpack/core/ExtendedTypeHeader.java deleted file mode 100644 index c4d4cc15e..000000000 --- a/msgpack-core/src/main/java/org/msgpack/core/ExtendedTypeHeader.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.msgpack.core; - -import static org.msgpack.core.Preconditions.*; - -/** - * Header of the extended types - */ -public class ExtendedTypeHeader { - private final int length; - private final int type; - - ExtendedTypeHeader(int length, int type) { - checkArgument(length >= 0, String.format("length must be >= 0: %,d", length)); - this.length = length; - this.type = type; - } - - public int getType() { - return type; - } - - public int getLength() { - return length; - } - - @Override - public int hashCode() { - return (type + 31) * 31 + length; - } - - @Override - public boolean equals(Object obj) { - if(obj instanceof ExtendedTypeHeader) { - ExtendedTypeHeader other = (ExtendedTypeHeader) obj; - return this.type == other.type && this.length == other.length; - } - return false; - } - - @Override - public String toString() { - return String.format("ExtendedTypeHeader(type:%d, length:%,d)", type, length); - } - -} diff --git a/msgpack-core/src/main/java/org/msgpack/core/ExtensionTypeHeader.java b/msgpack-core/src/main/java/org/msgpack/core/ExtensionTypeHeader.java new file mode 100644 index 000000000..73e92035a --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/core/ExtensionTypeHeader.java @@ -0,0 +1,88 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.core; + +import static org.msgpack.core.Preconditions.checkArgument; + +/** + * Header of the Extension types + */ +public class ExtensionTypeHeader +{ + private final byte type; + private final int length; + + /** + * Create an extension type header + * Example: + *
+     * {@code
+     * import org.msgpack.core.ExtensionTypeHeader;
+     * import static org.msgpack.core.ExtensionTypeHeader.checkedCastToByte;
+     * ...
+     * ExtensionTypeHeader header = new ExtensionTypeHeader(checkedCastToByte(0x01), 32);
+     * ...
+     * }
+     * 
+ * + * @param type extension type (byte). You can check the valid byte range with {@link #checkedCastToByte(int)} method. + * @param length extension type data length + */ + public ExtensionTypeHeader(byte type, int length) + { + checkArgument(length >= 0, "length must be >= 0"); + this.type = type; + this.length = length; + } + + public static byte checkedCastToByte(int code) + { + checkArgument(Byte.MIN_VALUE <= code && code <= Byte.MAX_VALUE, "Extension type code must be within the range of byte"); + return (byte) code; + } + + public byte getType() + { + return type; + } + + public int getLength() + { + return length; + } + + @Override + public int hashCode() + { + return (type + 31) * 31 + length; + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof ExtensionTypeHeader) { + ExtensionTypeHeader other = (ExtensionTypeHeader) obj; + return this.type == other.type && this.length == other.length; + } + return false; + } + + @Override + public String toString() + { + return String.format("ExtensionTypeHeader(type:%d, length:%,d)", type, length); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageBufferPacker.java b/msgpack-core/src/main/java/org/msgpack/core/MessageBufferPacker.java new file mode 100644 index 000000000..640b35f9d --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageBufferPacker.java @@ -0,0 +1,74 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.core; + +import org.msgpack.core.buffer.ArrayBufferOutput; +import org.msgpack.core.buffer.MessageBuffer; +import org.msgpack.core.buffer.MessageBufferOutput; + +import java.io.IOException; +import java.util.List; + +/** + * MessagePacker that is useful to produce byte array output + */ +public class MessageBufferPacker + extends MessagePacker +{ + public MessageBufferPacker(MessagePack.PackerConfig config) + { + this(new ArrayBufferOutput(), config); + } + + public MessageBufferPacker(ArrayBufferOutput out, MessagePack.PackerConfig config) + { + super(out, config); + } + + public MessageBufferOutput reset(MessageBufferOutput out) + throws IOException + { + if (!(out instanceof ArrayBufferOutput)) { + throw new IllegalArgumentException("MessageBufferPacker accepts only ArrayBufferOutput"); + } + return super.reset(out); + } + + private ArrayBufferOutput getArrayBufferOut() + { + return (ArrayBufferOutput) out; + } + + public void clear() + { + getArrayBufferOut().clear(); + } + + public byte[] toByteArray() + { + return getArrayBufferOut().toByteArray(); + } + + public MessageBuffer toMessageBuffer() + { + return getArrayBufferOut().toMessageBuffer(); + } + + public List toBufferList() + { + return getArrayBufferOut().toBufferList(); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageFloatOverflowException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageFloatOverflowException.java deleted file mode 100644 index 88449e6d5..000000000 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageFloatOverflowException.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.msgpack.core; - -/** - * This error is thrown when the user tries to read a value that has decimal component as byte, short, int and long. - * - */ -public class MessageFloatOverflowException extends MessageOverflowException { - - private final double value; - - public MessageFloatOverflowException(double value) { - super(); - this.value = value; - } - - public double getValue() { - return value; - } - -} diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageFormat.java b/msgpack-core/src/main/java/org/msgpack/core/MessageFormat.java index cffb525e2..d57c446f2 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageFormat.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageFormat.java @@ -1,15 +1,29 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core; -import org.msgpack.core.MessagePack.Code; import org.msgpack.core.annotations.VisibleForTesting; import org.msgpack.value.ValueType; - +import org.msgpack.core.MessagePack.Code; /** * Describes the list of the message format types defined in the MessagePack specification. */ -public enum MessageFormat { - +public enum MessageFormat +{ // INT7 POSFIXINT(ValueType.INTEGER), // MAP4 @@ -24,9 +38,9 @@ public enum MessageFormat { BIN8(ValueType.BINARY), BIN16(ValueType.BINARY), BIN32(ValueType.BINARY), - EXT8(ValueType.EXTENDED), - EXT16(ValueType.EXTENDED), - EXT32(ValueType.EXTENDED), + EXT8(ValueType.EXTENSION), + EXT16(ValueType.EXTENSION), + EXT32(ValueType.EXTENSION), FLOAT32(ValueType.FLOAT), FLOAT64(ValueType.FLOAT), UINT8(ValueType.INTEGER), @@ -38,11 +52,11 @@ public enum MessageFormat { INT16(ValueType.INTEGER), INT32(ValueType.INTEGER), INT64(ValueType.INTEGER), - FIXEXT1(ValueType.EXTENDED), - FIXEXT2(ValueType.EXTENDED), - FIXEXT4(ValueType.EXTENDED), - FIXEXT8(ValueType.EXTENDED), - FIXEXT16(ValueType.EXTENDED), + FIXEXT1(ValueType.EXTENSION), + FIXEXT2(ValueType.EXTENSION), + FIXEXT4(ValueType.EXTENSION), + FIXEXT8(ValueType.EXTENSION), + FIXEXT16(ValueType.EXTENSION), STR8(ValueType.STRING), STR16(ValueType.STRING), STR32(ValueType.STRING), @@ -50,31 +64,34 @@ public enum MessageFormat { ARRAY32(ValueType.ARRAY), MAP16(ValueType.MAP), MAP32(ValueType.MAP), - NEGFIXINT(ValueType.INTEGER) - ; + NEGFIXINT(ValueType.INTEGER); + private static final MessageFormat[] formatTable = new MessageFormat[256]; private final ValueType valueType; - private MessageFormat(ValueType valueType) { + private MessageFormat(ValueType valueType) + { this.valueType = valueType; } /** * Retruns the ValueType corresponding to this MessageFormat + * * @return value type * @throws MessageFormatException if this == NEVER_USED type */ - public ValueType getValueType() throws MessageFormatException { - if(this == NEVER_USED) + public ValueType getValueType() + throws MessageFormatException + { + if (this == NEVER_USED) { throw new MessageFormatException("Cannot convert NEVER_USED to ValueType"); + } return valueType; } - private final static MessageFormat[] formatTable = new MessageFormat[256]; - static { // Preparing a look up table for converting byte values into MessageFormat types - for(int b = 0; b <= 0xFF; ++b) { + for (int b = 0; b <= 0xFF; ++b) { MessageFormat mf = toMessageFormat((byte) b); formatTable[b] = mf; } @@ -82,20 +99,24 @@ public ValueType getValueType() throws MessageFormatException { /** * Returns a MessageFormat type of the specified byte value + * * @param b MessageFormat of the given byte * @return */ - public static MessageFormat valueOf(final byte b) { + public static MessageFormat valueOf(final byte b) + { return formatTable[b & 0xFF]; } /** * Converting a byte value into MessageFormat. For faster performance, use {@link #valueOf} + * * @param b MessageFormat of the given byte * @return */ @VisibleForTesting - public static MessageFormat toMessageFormat(final byte b) { + static MessageFormat toMessageFormat(final byte b) + { if (Code.isPosFixInt(b)) { return POSFIXINT; } @@ -177,5 +198,4 @@ public static MessageFormat toMessageFormat(final byte b) { return NEVER_USED; } } - } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageFormatException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageFormatException.java index f79bea279..cf169af80 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageFormatException.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageFormatException.java @@ -18,18 +18,21 @@ /** * Thrown when the input message pack format is invalid */ -public class MessageFormatException extends MessagePackException { - - public MessageFormatException(Throwable e) { +public class MessageFormatException + extends MessagePackException +{ + public MessageFormatException(Throwable e) + { super(e); } - - public MessageFormatException(String message) { + public MessageFormatException(String message) + { super(message); } - public MessageFormatException(String message, Throwable cause) { + public MessageFormatException(String message, Throwable cause) + { super(message, cause); } } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageInsufficientBufferException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageInsufficientBufferException.java new file mode 100644 index 000000000..838dc77ab --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageInsufficientBufferException.java @@ -0,0 +1,40 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.core; + +public class MessageInsufficientBufferException + extends MessagePackException +{ + public MessageInsufficientBufferException() + { + super(); + } + + public MessageInsufficientBufferException(String message) + { + super(message); + } + + public MessageInsufficientBufferException(Throwable cause) + { + super(cause); + } + + public MessageInsufficientBufferException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageIntegerOverflowException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageIntegerOverflowException.java index c3d561ec1..b3199ae2e 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageIntegerOverflowException.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageIntegerOverflowException.java @@ -17,36 +17,41 @@ import java.math.BigInteger; - /** * This error is thrown when the user tries to read an integer value * using a smaller types. For example, calling MessageUnpacker.unpackInt() for an integer value * that is larger than Integer.MAX_VALUE will cause this exception. */ -public class MessageIntegerOverflowException extends MessageOverflowException { - +public class MessageIntegerOverflowException + extends MessageTypeException +{ private final BigInteger bigInteger; - public MessageIntegerOverflowException(BigInteger bigInteger) { + public MessageIntegerOverflowException(BigInteger bigInteger) + { super(); this.bigInteger = bigInteger; } - public MessageIntegerOverflowException(long value) { + public MessageIntegerOverflowException(long value) + { this(BigInteger.valueOf(value)); } - public MessageIntegerOverflowException(String message, BigInteger bigInteger) { + public MessageIntegerOverflowException(String message, BigInteger bigInteger) + { super(message); this.bigInteger = bigInteger; } - public BigInteger getBigInteger() { + public BigInteger getBigInteger() + { return bigInteger; } @Override - public String getMessage() { + public String getMessage() + { return bigInteger.toString(); } } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageNeverUsedFormatException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageNeverUsedFormatException.java new file mode 100644 index 000000000..726ffb497 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageNeverUsedFormatException.java @@ -0,0 +1,38 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.core; + +/** + * Thrown when the input message pack format is invalid + */ +public class MessageNeverUsedFormatException + extends MessageFormatException +{ + public MessageNeverUsedFormatException(Throwable e) + { + super(e); + } + + public MessageNeverUsedFormatException(String message) + { + super(message); + } + + public MessageNeverUsedFormatException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageOverflowException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageOverflowException.java deleted file mode 100644 index 2190a34c1..000000000 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageOverflowException.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.msgpack.core; - -/** - * Created on 5/28/14. - */ -public class MessageOverflowException extends MessageTypeException { - - public MessageOverflowException() { - super(); - } - - public MessageOverflowException(String message) { - super(message); - } - -} diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessagePack.java b/msgpack-core/src/main/java/org/msgpack/core/MessagePack.java index a6653a356..59bbbad48 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessagePack.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessagePack.java @@ -15,7 +15,13 @@ // package org.msgpack.core; -import org.msgpack.core.buffer.*; +import org.msgpack.core.buffer.ArrayBufferInput; +import org.msgpack.core.buffer.ChannelBufferInput; +import org.msgpack.core.buffer.ChannelBufferOutput; +import org.msgpack.core.buffer.InputStreamBufferInput; +import org.msgpack.core.buffer.MessageBufferInput; +import org.msgpack.core.buffer.MessageBufferOutput; +import org.msgpack.core.buffer.OutputStreamBufferOutput; import java.io.InputStream; import java.io.OutputStream; @@ -24,190 +30,51 @@ import java.nio.charset.Charset; import java.nio.charset.CodingErrorAction; -import static org.msgpack.core.Preconditions.checkArgument; - /** * This class has MessagePack prefix code definitions and packer/unpacker factory methods. - * */ -public class MessagePack { - +public class MessagePack +{ public static final Charset UTF8 = Charset.forName("UTF-8"); - /** - * Message packer/unpacker configuration object - */ - public static class Config { - private final boolean readStringAsBinary; - private final boolean readBinaryAsString; - private final CodingErrorAction onMalFormedInput; - private final CodingErrorAction onUnmappableCharacter; - private final int maxUnpackStringSize; - private final int stringEncoderBufferSize; - private final int stringDecoderBufferSize; - private final int packerBufferSize; - private final int packerRawDataCopyingThreshold; - - public Config( - boolean readStringAsBinary, - boolean readBinaryAsString, - CodingErrorAction onMalFormedInput, - CodingErrorAction onUnmappableCharacter, - int maxUnpackStringSize, - int stringEncoderBufferSize, - int stringDecoderBufferSize, - int packerBufferSize, - int packerRawDataCopyingThreshold) { - - checkArgument(packerBufferSize > 0, "packer buffer size must be larger than 0: " + packerBufferSize); - checkArgument(stringEncoderBufferSize > 0, "string encoder buffer size must be larger than 0: " + stringEncoderBufferSize); - checkArgument(stringDecoderBufferSize > 0, "string decoder buffer size must be larger than 0: " + stringDecoderBufferSize); - - this.readStringAsBinary = readStringAsBinary; - this.readBinaryAsString = readBinaryAsString; - this.onMalFormedInput = onMalFormedInput; - this.onUnmappableCharacter = onUnmappableCharacter; - this.maxUnpackStringSize = maxUnpackStringSize; - this.stringEncoderBufferSize = stringEncoderBufferSize; - this.stringDecoderBufferSize = stringDecoderBufferSize; - this.packerBufferSize = packerBufferSize; - this.packerRawDataCopyingThreshold = packerRawDataCopyingThreshold; - } - - /** - * allow unpackBinaryHeader to read str format family (default:true) - */ - public boolean isReadStringAsBinary() { return readStringAsBinary; } - - /** - * allow unpackRawStringHeader and unpackString to read bin format family (default: true) - */ - public boolean isReadBinaryAsString() { return readBinaryAsString; } - /** - * Action when encountered a malformed input - */ - public CodingErrorAction getActionOnMalFormedInput() { return onMalFormedInput; } - /** - * Action when an unmappable character is found - */ - public CodingErrorAction getActionOnUnmappableCharacter() { return onUnmappableCharacter; } - - /** - * unpackString size limit. (default: Integer.MAX_VALUE) - */ - public int getMaxUnpackStringSize() { return maxUnpackStringSize; } - - public int getStringEncoderBufferSize() { return stringEncoderBufferSize; } - public int getStringDecoderBufferSize() { return stringDecoderBufferSize; } - - public int getPackerBufferSize() { return packerBufferSize; } - public int getPackerRawDataCopyingThreshold() { return packerRawDataCopyingThreshold; } - } - - /** - * Builder of the configuration object - */ - public static class ConfigBuilder { - - private boolean readStringAsBinary = true; - private boolean readBinaryAsString = true; - - private CodingErrorAction onMalFormedInput = CodingErrorAction.REPLACE; - private CodingErrorAction onUnmappableCharacter = CodingErrorAction.REPLACE; - - private int maxUnpackStringSize = Integer.MAX_VALUE; - private int stringEncoderBufferSize = 8192; - private int stringDecoderBufferSize = 8192; - private int packerBufferSize = 8192; - private int packerRawDataCopyingThreshold = 512; - - public Config build() { - return new Config( - readStringAsBinary, - readBinaryAsString, - onMalFormedInput, - onUnmappableCharacter, - maxUnpackStringSize, - stringEncoderBufferSize, - stringDecoderBufferSize, - packerBufferSize, - packerRawDataCopyingThreshold - ); - } - - public ConfigBuilder readStringAsBinary(boolean enable) { - this.readStringAsBinary = enable; - return this; - } - public ConfigBuilder readBinaryAsString(boolean enable) { - this.readBinaryAsString = enable; - return this; - } - public ConfigBuilder onMalFormedInput(CodingErrorAction action) { - this.onMalFormedInput = action; - return this; - } - public ConfigBuilder onUnmappableCharacter(CodingErrorAction action) { - this.onUnmappableCharacter = action; - return this; - } - public ConfigBuilder maxUnpackStringSize(int size){ - this.maxUnpackStringSize = size; - return this; - } - public ConfigBuilder stringEncoderBufferSize(int size) { - this.stringEncoderBufferSize = size; - return this; - } - public ConfigBuilder stringDecoderBufferSize(int size) { - this.stringDecoderBufferSize = size; - return this; - } - public ConfigBuilder packerBufferSize(int size) { - this.packerBufferSize = size; - return this; - } - public ConfigBuilder packerRawDataCopyingThreshold(int threshold) { - this.packerRawDataCopyingThreshold = threshold; - return this; - } - } - - - - /** - * Default configuration, which is visible only from classes in the core package. - */ - static final Config DEFAULT_CONFIG = new ConfigBuilder().build(); - - /** * The prefix code set of MessagePack. See also https://github.com/msgpack/msgpack/blob/master/spec.md for details. */ - public static final class Code { - - public static final boolean isFixInt(byte b) { + public static final class Code + { + public static final boolean isFixInt(byte b) + { int v = b & 0xFF; return v <= 0x7f || v >= 0xe0; } - public static final boolean isPosFixInt(byte b) { + public static final boolean isPosFixInt(byte b) + { return (b & POSFIXINT_MASK) == 0; } - public static final boolean isNegFixInt(byte b) { + + public static final boolean isNegFixInt(byte b) + { return (b & NEGFIXINT_PREFIX) == NEGFIXINT_PREFIX; } - public static final boolean isFixStr(byte b) { + + public static final boolean isFixStr(byte b) + { return (b & (byte) 0xe0) == Code.FIXSTR_PREFIX; } - public static final boolean isFixedArray(byte b) { + + public static final boolean isFixedArray(byte b) + { return (b & (byte) 0xf0) == Code.FIXARRAY_PREFIX; } - public static final boolean isFixedMap(byte b) { - return (b & (byte) 0xe0) == Code.FIXMAP_PREFIX; + + public static final boolean isFixedMap(byte b) + { + return (b & (byte) 0xf0) == Code.FIXMAP_PREFIX; } - public static final boolean isFixedRaw(byte b) { + public static final boolean isFixedRaw(byte b) + { return (b & (byte) 0xe0) == Code.FIXSTR_PREFIX; } @@ -258,137 +125,260 @@ public static final boolean isFixedRaw(byte b) { public static final byte NEGFIXINT_PREFIX = (byte) 0xe0; } - // Packer/Unpacker factory methods - - private final MessagePack.Config config; - - public MessagePack() { - this(MessagePack.DEFAULT_CONFIG); - } - - public MessagePack(MessagePack.Config config) { - this.config = config; + private MessagePack() + { + // Prohibit instantiation of this class } /** - * Default MessagePack packer/unpacker factory - */ - public static final MessagePack DEFAULT = new MessagePack(MessagePack.DEFAULT_CONFIG); - - - /** - * Create a MessagePacker that outputs the packed data to the specified stream, using the default configuration + * Create a packer that outputs the packed data to the specified output + * * @param out * @return */ - public static MessagePacker newDefaultPacker(OutputStream out) { - return DEFAULT.newPacker(out); + public static MessagePacker newDefaultPacker(MessageBufferOutput out) + { + return new PackerConfig().newPacker(out); } /** - * Create a MessagePacker that outputs the packed data to the specified channel, using the default configuration - * @param channel + * Create a packer that outputs the packed data to a target output stream + * + * @param out * @return */ - public static MessagePacker newDefaultPacker(WritableByteChannel channel) { - return DEFAULT.newPacker(channel); + public static MessagePacker newDefaultPacker(OutputStream out) + { + return new PackerConfig().newPacker(out); } /** - * Create a MessageUnpacker that reads data from then given InputStream, using the default configuration - * @param in + * Create a packer that outputs the packed data to a channel + * + * @param channel * @return */ - public static MessageUnpacker newDefaultUnpacker(InputStream in) { - return DEFAULT.newUnpacker(in); + public static MessagePacker newDefaultPacker(WritableByteChannel channel) + { + return new PackerConfig().newPacker(channel); } /** - * Create a MessageUnpacker that reads data from the given channel, using the default configuration - * @param channel + * Create a packer for storing packed data into a byte array + * * @return */ - public static MessageUnpacker newDefaultUnpacker(ReadableByteChannel channel) { - return DEFAULT.newUnpacker(channel); + public static MessageBufferPacker newDefaultBufferPacker() + { + return new PackerConfig().newBufferPacker(); } /** - * Create a MessageUnpacker that reads data from the given byte array, using the default configuration - * @param arr + * Create an unpacker that reads the data from a given input + * + * @param in * @return */ - public static MessageUnpacker newDefaultUnpacker(byte[] arr) { - return DEFAULT.newUnpacker(arr); + public static MessageUnpacker newDefaultUnpacker(MessageBufferInput in) + { + return new UnpackerConfig().newUnpacker(in); } /** - * Create a MessageUnpacker that reads data form the given byte array [offset, .. offset+length), using the default - * configuration. - * @param arr - * @param offset - * @param length + * Create an unpacker that reads the data from a given input stream + * + * @param in * @return */ - public static MessageUnpacker newDefaultUnpacker(byte[] arr, int offset, int length) { - return DEFAULT.newUnpacker(arr, offset, length); + public static MessageUnpacker newDefaultUnpacker(InputStream in) + { + return new UnpackerConfig().newUnpacker(in); } - /** - * Create a MessagePacker that outputs the packed data to the specified stream - * @param out + * Create an unpacker that reads the data from a given channel + * + * @param channel + * @return */ - public MessagePacker newPacker(OutputStream out) { - return new MessagePacker(new OutputStreamBufferOutput(out), config); + public static MessageUnpacker newDefaultUnpacker(ReadableByteChannel channel) + { + return new UnpackerConfig().newUnpacker(channel); } /** - * Create a MessagePacker that outputs the packed data to the specified channel - * @param channel + * Create an unpacker that reads the data from a given byte array + * + * @param contents + * @return */ - public MessagePacker newPacker(WritableByteChannel channel) { - return new MessagePacker(new ChannelBufferOutput(channel), config); + public static MessageUnpacker newDefaultUnpacker(byte[] contents) + { + return new UnpackerConfig().newUnpacker(contents); } /** - * Create a MessageUnpacker that reads data from the given InputStream. - * For reading data efficiently from byte[], use {@link MessageUnpacker(byte[])} or {@link MessageUnpacker(byte[], int, int)} instead of this constructor. + * Create an unpacker that reads the data from a given byte array [offset, offset+length) * - * @param in + * @param contents + * @param offset + * @param length + * @return */ - public MessageUnpacker newUnpacker(InputStream in) { - return new MessageUnpacker(InputStreamBufferInput.newBufferInput(in), config); + public static MessageUnpacker newDefaultUnpacker(byte[] contents, int offset, int length) + { + return new UnpackerConfig().newUnpacker(contents, offset, length); } /** - * Create a MessageUnpacker that reads data from the given ReadableByteChannel. - * @param in + * MessagePacker configuration. */ - public MessageUnpacker newUnpacker(ReadableByteChannel in) { - return new MessageUnpacker(new ChannelBufferInput(in), config); - } + public static class PackerConfig + { + /** + * Use String.getBytes() for converting Java Strings that are smaller than this threshold into UTF8. + * Note that this parameter is subject to change. + */ + public int smallStringOptimizationThreshold = 512; + /** + * When the next payload size exceeds this threshold, MessagePacker will call MessageBufferOutput.flush() before + * packing the data. + */ + public int bufferFlushThreshold = 8192; - /** - * Create a MessageUnpacker that reads data from the given byte array. - * - * @param arr - */ - public MessageUnpacker newUnpacker(byte[] arr) { - return new MessageUnpacker(new ArrayBufferInput(arr), config); + /** + * Create a packer that outputs the packed data to a given output + * + * @param out + * @return + */ + public MessagePacker newPacker(MessageBufferOutput out) + { + return new MessagePacker(out, this); + } + + /** + * Create a packer that outputs the packed data to a given output stream + * + * @param out + * @return + */ + public MessagePacker newPacker(OutputStream out) + { + return newPacker(new OutputStreamBufferOutput(out)); + } + + /** + * Create a packer that outputs the packed data to a given output channel + * + * @param channel + * @return + */ + public MessagePacker newPacker(WritableByteChannel channel) + { + return newPacker(new ChannelBufferOutput(channel)); + } + + /** + * Create a packer for storing packed data into a byte array + * + * @return + */ + public MessageBufferPacker newBufferPacker() + { + return new MessageBufferPacker(this); + } } /** - * Create a MessageUnpacker that reads data from the given byte array [offset, offset+length) - * @param arr - * @param offset - * @param length + * MessageUnpacker configuration. */ - public MessageUnpacker newUnpacker(byte[] arr, int offset, int length) { - return new MessageUnpacker(new ArrayBufferInput(arr, offset, length), config); - } + public static class UnpackerConfig + { + /** + * Allow unpackBinaryHeader to read str format family (default:true) + */ + public boolean allowReadingStringAsBinary = true; + /** + * Allow unpackRawStringHeader and unpackString to read bin format family (default: true) + */ + public boolean allowReadingBinaryAsString = true; + /** + * Action when encountered a malformed input + */ + public CodingErrorAction actionOnMalformedString = CodingErrorAction.REPLACE; + /** + * Action when an unmappable character is found + */ + public CodingErrorAction actionOnUnmappableString = CodingErrorAction.REPLACE; + /** + * unpackString size limit. (default: Integer.MAX_VALUE) + */ + public int stringSizeLimit = Integer.MAX_VALUE; + + /** + * + */ + public int stringDecoderBufferSize = 8192; + + /** + * Create an unpacker that reads the data from a given input + * + * @param in + * @return + */ + public MessageUnpacker newUnpacker(MessageBufferInput in) + { + return new MessageUnpacker(in, this); + } + + /** + * Create an unpacker that reads the data from a given input stream + * + * @param in + * @return + */ + public MessageUnpacker newUnpacker(InputStream in) + { + return newUnpacker(new InputStreamBufferInput(in)); + } + + /** + * Create an unpacker that reads the data from a given channel + * + * @param channel + * @return + */ + public MessageUnpacker newUnpacker(ReadableByteChannel channel) + { + return newUnpacker(new ChannelBufferInput(channel)); + } + + /** + * Create an unpacker that reads the data from a given byte array + * + * @param contents + * @return + */ + public MessageUnpacker newUnpacker(byte[] contents) + { + return newUnpacker(new ArrayBufferInput(contents)); + } + + /** + * Create an unpacker that reads the data from a given byte array [offset, offset+size) + * + * @param contents + * @return + */ + public MessageUnpacker newUnpacker(byte[] contents, int offset, int length) + { + return newUnpacker(new ArrayBufferInput(contents, offset, length)); + } + } } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessagePackException.java b/msgpack-core/src/main/java/org/msgpack/core/MessagePackException.java index 4359f8f32..79474b4b7 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessagePackException.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessagePackException.java @@ -15,31 +15,36 @@ // package org.msgpack.core; - /** * A base class of all of the message pack exceptions. */ -public class MessagePackException extends RuntimeException { - public MessagePackException() { +public class MessagePackException + extends RuntimeException +{ + public MessagePackException() + { super(); } - public MessagePackException(String message) { + public MessagePackException(String message) + { super(message); } - public MessagePackException(String message, Throwable cause) { + public MessagePackException(String message, Throwable cause) + { super(message, cause); } - public MessagePackException(Throwable cause) { + public MessagePackException(Throwable cause) + { super(cause); } - - public static UnsupportedOperationException UNSUPPORTED(String operationName) { + public static UnsupportedOperationException UNSUPPORTED(String operationName) + { return new UnsupportedOperationException(operationName); } - public static IllegalStateException UNREACHABLE = new IllegalStateException("Cannot reach here"); + public static final IllegalStateException UNREACHABLE = new IllegalStateException("Cannot reach here"); } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessagePacker.java b/msgpack-core/src/main/java/org/msgpack/core/MessagePacker.java index 16f49c656..c99653fc9 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessagePacker.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessagePacker.java @@ -20,17 +20,49 @@ import org.msgpack.value.Value; import java.io.Closeable; -import java.math.BigInteger; import java.io.IOException; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; -import java.nio.charset.CodingErrorAction; -import static org.msgpack.core.MessagePack.Code.*; -import static org.msgpack.core.Preconditions.*; +import static org.msgpack.core.MessagePack.Code.ARRAY16; +import static org.msgpack.core.MessagePack.Code.ARRAY32; +import static org.msgpack.core.MessagePack.Code.BIN16; +import static org.msgpack.core.MessagePack.Code.BIN32; +import static org.msgpack.core.MessagePack.Code.BIN8; +import static org.msgpack.core.MessagePack.Code.EXT16; +import static org.msgpack.core.MessagePack.Code.EXT32; +import static org.msgpack.core.MessagePack.Code.EXT8; +import static org.msgpack.core.MessagePack.Code.FALSE; +import static org.msgpack.core.MessagePack.Code.FIXARRAY_PREFIX; +import static org.msgpack.core.MessagePack.Code.FIXEXT1; +import static org.msgpack.core.MessagePack.Code.FIXEXT16; +import static org.msgpack.core.MessagePack.Code.FIXEXT2; +import static org.msgpack.core.MessagePack.Code.FIXEXT4; +import static org.msgpack.core.MessagePack.Code.FIXEXT8; +import static org.msgpack.core.MessagePack.Code.FIXMAP_PREFIX; +import static org.msgpack.core.MessagePack.Code.FIXSTR_PREFIX; +import static org.msgpack.core.MessagePack.Code.FLOAT32; +import static org.msgpack.core.MessagePack.Code.FLOAT64; +import static org.msgpack.core.MessagePack.Code.INT16; +import static org.msgpack.core.MessagePack.Code.INT32; +import static org.msgpack.core.MessagePack.Code.INT64; +import static org.msgpack.core.MessagePack.Code.INT8; +import static org.msgpack.core.MessagePack.Code.MAP16; +import static org.msgpack.core.MessagePack.Code.MAP32; +import static org.msgpack.core.MessagePack.Code.NIL; +import static org.msgpack.core.MessagePack.Code.STR16; +import static org.msgpack.core.MessagePack.Code.STR32; +import static org.msgpack.core.MessagePack.Code.STR8; +import static org.msgpack.core.MessagePack.Code.TRUE; +import static org.msgpack.core.MessagePack.Code.UINT16; +import static org.msgpack.core.MessagePack.Code.UINT32; +import static org.msgpack.core.MessagePack.Code.UINT64; +import static org.msgpack.core.MessagePack.Code.UINT8; +import static org.msgpack.core.Preconditions.checkNotNull; /** * Writer of message packed data. @@ -49,42 +81,43 @@ * the binary data of the specified length in the header. *

*/ -public class MessagePacker implements Closeable { +public class MessagePacker + implements Closeable +{ + private final int smallStringOptimizationThreshold; - private final MessagePack.Config config; + private final int bufferFlushThreshold; + + protected MessageBufferOutput out; - private MessageBufferOutput out; private MessageBuffer buffer; - private MessageBuffer strLenBuffer; private int position; /** * Total written byte size */ - private long flushedBytes; + private long totalFlushBytes; /** * String encoder */ private CharsetEncoder encoder; - /** * Create an MessagePacker that outputs the packed data to the given {@link org.msgpack.core.buffer.MessageBufferOutput} * * @param out MessageBufferOutput. Use {@link org.msgpack.core.buffer.OutputStreamBufferOutput}, {@link org.msgpack.core.buffer.ChannelBufferOutput} or - * your own implementation of {@link org.msgpack.core.buffer.MessageBufferOutput} interface. + * your own implementation of {@link org.msgpack.core.buffer.MessageBufferOutput} interface. */ - public MessagePacker(MessageBufferOutput out) { - this(out, MessagePack.DEFAULT_CONFIG); - } - - public MessagePacker(MessageBufferOutput out, MessagePack.Config config) { - this.config = checkNotNull(config, "config is null"); + public MessagePacker(MessageBufferOutput out, MessagePack.PackerConfig config) + { this.out = checkNotNull(out, "MessageBufferOutput is null"); + // We must copy the configuration parameters here since the config object is mutable + this.smallStringOptimizationThreshold = config.smallStringOptimizationThreshold; + this.bufferFlushThreshold = config.bufferFlushThreshold; this.position = 0; - this.flushedBytes = 0; + this.totalFlushBytes = 0; } /** @@ -93,52 +126,40 @@ public MessagePacker(MessageBufferOutput out, MessagePack.Config config) { * @param out new output * @return the old resource */ - public MessageBufferOutput reset(MessageBufferOutput out) throws IOException { + public MessageBufferOutput reset(MessageBufferOutput out) + throws IOException + { // Validate the argument MessageBufferOutput newOut = checkNotNull(out, "MessageBufferOutput is null"); - // Reset the internal states + // Flush before reset + flush(); MessageBufferOutput old = this.out; this.out = newOut; - this.position = 0; - this.flushedBytes = 0; - return old; - } - public long getTotalWrittenBytes() { - return flushedBytes + position; - } + // Reset totalFlushBytes + this.totalFlushBytes = 0; - private void prepareEncoder() { - if(encoder == null) { - this.encoder = MessagePack.UTF8.newEncoder().onMalformedInput(config.getActionOnMalFormedInput()).onUnmappableCharacter(config.getActionOnMalFormedInput()); - } + return old; } - private void prepareBuffer() throws IOException { - if(buffer == null) { - buffer = out.next(config.getPackerBufferSize()); - } + public long getTotalWrittenBytes() + { + return totalFlushBytes + position; } - - public void flush() throws IOException { - if(buffer == null) { - return; - } - - if(position == buffer.size()) { - out.flush(buffer); + public void flush() + throws IOException + { + if (position > 0) { + flushBuffer(); } - else { - out.flush(buffer.slice(0, position)); - } - buffer = null; - flushedBytes += position; - position = 0; + out.flush(); } - public void close() throws IOException { + public void close() + throws IOException + { try { flush(); } @@ -147,91 +168,129 @@ public void close() throws IOException { } } - private void ensureCapacity(int numBytesToWrite) throws IOException { - if(buffer == null || position + numBytesToWrite >= buffer.size()) { - flush(); - buffer = out.next(Math.max(config.getPackerBufferSize(), numBytesToWrite)); + private void flushBuffer() + throws IOException + { + out.writeBuffer(position); + buffer = null; + totalFlushBytes += position; + position = 0; + } + + private void ensureCapacity(int minimumSize) + throws IOException + { + if (buffer == null) { + buffer = out.next(minimumSize); + } + else if (position + minimumSize >= buffer.size()) { + flushBuffer(); + buffer = out.next(minimumSize); } } - private void writeByte(byte b) throws IOException { + private void writeByte(byte b) + throws IOException + { ensureCapacity(1); buffer.putByte(position++, b); } - - private void writeByteAndByte(byte b, byte v) throws IOException { + private void writeByteAndByte(byte b, byte v) + throws IOException + { ensureCapacity(2); buffer.putByte(position++, b); buffer.putByte(position++, v); } - private void writeByteAndShort(byte b, short v) throws IOException { + private void writeByteAndShort(byte b, short v) + throws IOException + { ensureCapacity(3); buffer.putByte(position++, b); buffer.putShort(position, v); position += 2; } - private void writeByteAndInt(byte b, int v) throws IOException { + private void writeByteAndInt(byte b, int v) + throws IOException + { ensureCapacity(5); buffer.putByte(position++, b); buffer.putInt(position, v); position += 4; } - private void writeByteAndFloat(byte b, float v) throws IOException { + private void writeByteAndFloat(byte b, float v) + throws IOException + { ensureCapacity(5); buffer.putByte(position++, b); buffer.putFloat(position, v); position += 4; } - private void writeByteAndDouble(byte b, double v) throws IOException { + private void writeByteAndDouble(byte b, double v) + throws IOException + { ensureCapacity(9); buffer.putByte(position++, b); buffer.putDouble(position, v); position += 8; } - private void writeByteAndLong(byte b, long v) throws IOException { + private void writeByteAndLong(byte b, long v) + throws IOException + { ensureCapacity(9); buffer.putByte(position++, b); buffer.putLong(position, v); position += 8; } - private void writeShort(short v) throws IOException { + private void writeShort(short v) + throws IOException + { ensureCapacity(2); buffer.putShort(position, v); position += 2; } - private void writeInt(int v) throws IOException { + private void writeInt(int v) + throws IOException + { ensureCapacity(4); buffer.putInt(position, v); position += 4; } - private void writeLong(long v) throws IOException { + private void writeLong(long v) + throws IOException + { ensureCapacity(8); buffer.putLong(position, v); position += 8; } - public MessagePacker packNil() throws IOException { + public MessagePacker packNil() + throws IOException + { writeByte(NIL); return this; } - public MessagePacker packBoolean(boolean b) throws IOException { + public MessagePacker packBoolean(boolean b) + throws IOException + { writeByte(b ? TRUE : FALSE); return this; } - - public MessagePacker packByte(byte b) throws IOException { - if(b < -(1 << 5)) { + public MessagePacker packByte(byte b) + throws IOException + { + if (b < -(1 << 5)) { writeByteAndByte(INT8, b); } else { @@ -240,20 +299,22 @@ public MessagePacker packByte(byte b) throws IOException { return this; } - public MessagePacker packShort(short v) throws IOException { - if(v < -(1 << 5)) { - if(v < -(1 << 7)) { + public MessagePacker packShort(short v) + throws IOException + { + if (v < -(1 << 5)) { + if (v < -(1 << 7)) { writeByteAndShort(INT16, v); } else { writeByteAndByte(INT8, (byte) v); } } - else if(v < (1 << 7)) { + else if (v < (1 << 7)) { writeByte((byte) v); } else { - if(v < (1 << 8)) { + if (v < (1 << 8)) { writeByteAndByte(UINT8, (byte) v); } else { @@ -263,26 +324,28 @@ else if(v < (1 << 7)) { return this; } - public MessagePacker packInt(int r) throws IOException { - if(r < -(1 << 5)) { - if(r < -(1 << 15)) { + public MessagePacker packInt(int r) + throws IOException + { + if (r < -(1 << 5)) { + if (r < -(1 << 15)) { writeByteAndInt(INT32, r); } - else if(r < -(1 << 7)) { + else if (r < -(1 << 7)) { writeByteAndShort(INT16, (short) r); } else { writeByteAndByte(INT8, (byte) r); } } - else if(r < (1 << 7)) { + else if (r < (1 << 7)) { writeByte((byte) r); } else { - if(r < (1 << 8)) { + if (r < (1 << 8)) { writeByteAndByte(UINT8, (byte) r); } - else if(r < (1 << 16)) { + else if (r < (1 << 16)) { writeByteAndShort(UINT16, (short) r); } else { @@ -293,10 +356,12 @@ else if(r < (1 << 16)) { return this; } - public MessagePacker packLong(long v) throws IOException { - if(v < -(1L << 5)) { - if(v < -(1L << 15)) { - if(v < -(1L << 31)) { + public MessagePacker packLong(long v) + throws IOException + { + if (v < -(1L << 5)) { + if (v < -(1L << 15)) { + if (v < -(1L << 31)) { writeByteAndLong(INT64, v); } else { @@ -304,7 +369,7 @@ public MessagePacker packLong(long v) throws IOException { } } else { - if(v < -(1 << 7)) { + if (v < -(1 << 7)) { writeByteAndShort(INT16, (short) v); } else { @@ -312,13 +377,13 @@ public MessagePacker packLong(long v) throws IOException { } } } - else if(v < (1 << 7)) { + else if (v < (1 << 7)) { // fixnum writeByte((byte) v); } else { - if(v < (1L << 16)) { - if(v < (1 << 8)) { + if (v < (1L << 16)) { + if (v < (1 << 8)) { writeByteAndByte(UINT8, (byte) v); } else { @@ -326,7 +391,7 @@ else if(v < (1 << 7)) { } } else { - if(v < (1L << 32)) { + if (v < (1L << 32)) { writeByteAndInt(UINT32, (int) v); } else { @@ -337,29 +402,75 @@ else if(v < (1 << 7)) { return this; } - public MessagePacker packBigInteger(BigInteger bi) throws IOException { - if(bi.bitLength() <= 63) { + public MessagePacker packBigInteger(BigInteger bi) + throws IOException + { + if (bi.bitLength() <= 63) { packLong(bi.longValue()); } - else if(bi.bitLength() == 64 && bi.signum() == 1) { + else if (bi.bitLength() == 64 && bi.signum() == 1) { writeByteAndLong(UINT64, bi.longValue()); } else { - throw new IllegalArgumentException("Messagepack cannot serialize BigInteger larger than 2^64-1"); + throw new IllegalArgumentException("MessagePack cannot serialize BigInteger larger than 2^64-1"); } return this; } - public MessagePacker packFloat(float v) throws IOException { + public MessagePacker packFloat(float v) + throws IOException + { writeByteAndFloat(FLOAT32, v); return this; } - public MessagePacker packDouble(double v) throws IOException { + public MessagePacker packDouble(double v) + throws IOException + { writeByteAndDouble(FLOAT64, v); return this; } + private void packStringWithGetBytes(String s) + throws IOException + { + // JVM performs various optimizations (memory allocation, reusing encoder etc.) when String.getBytes is used + byte[] bytes = s.getBytes(MessagePack.UTF8); + // Write the length and payload of small string to the buffer so that it avoids an extra flush of buffer + packRawStringHeader(bytes.length); + addPayload(bytes); + } + + private void prepareEncoder() + { + if (encoder == null) { + this.encoder = MessagePack.UTF8.newEncoder(); + } + } + + private int encodeStringToBufferAt(int pos, String s) + { + prepareEncoder(); + ByteBuffer bb = buffer.sliceAsByteBuffer(pos, buffer.size() - pos); + int startPosition = bb.position(); + CharBuffer in = CharBuffer.wrap(s); + CoderResult cr = encoder.encode(in, bb, true); + if (cr.isError()) { + try { + cr.throwException(); + } + catch (CharacterCodingException e) { + throw new MessageStringCodingException(e); + } + } + if (cr.isUnderflow() || cr.isOverflow()) { + return -1; + } + return bb.position() - startPosition; + } + + private static final int UTF_8_MAX_CHAR_SIZE = 6; + /** * Pack the input String in UTF-8 encoding * @@ -367,87 +478,97 @@ public MessagePacker packDouble(double v) throws IOException { * @return * @throws IOException */ - public MessagePacker packString(String s) throws IOException { - if(s.length() <= 0) { + public MessagePacker packString(String s) + throws IOException + { + if (s.length() <= 0) { packRawStringHeader(0); return this; } - - CharBuffer in = CharBuffer.wrap(s); - prepareEncoder(); - - flush(); - - prepareBuffer(); - boolean isExtended = false; - ByteBuffer encodeBuffer = buffer.toByteBuffer(position, buffer.size() - position); - encoder.reset(); - while(in.hasRemaining()) { - try { - CoderResult cr = encoder.encode(in, encodeBuffer, true); - - // Input data is insufficient - if(cr.isUnderflow()) { - cr = encoder.flush(encodeBuffer); - } - - // encodeBuffer is too small - if(cr.isOverflow()) { - // Allocate a larger buffer - int estimatedRemainingSize = Math.max(1, (int) (in.remaining() * encoder.averageBytesPerChar())); - encodeBuffer.flip(); - ByteBuffer newBuffer = ByteBuffer.allocate(Math.max((int) (encodeBuffer.capacity() * 1.5), encodeBuffer.remaining() + estimatedRemainingSize)); - // Coy the current encodeBuffer contents to the new buffer - newBuffer.put(encodeBuffer); - encodeBuffer = newBuffer; - isExtended = true; - encoder.reset(); - continue; + else if (s.length() < smallStringOptimizationThreshold) { + // Using String.getBytes is generally faster for small strings + packStringWithGetBytes(s); + return this; + } + else if (s.length() < (1 << 8)) { + // ensure capacity for 2-byte raw string header + the maximum string size (+ 1 byte for falback code) + ensureCapacity(2 + s.length() * UTF_8_MAX_CHAR_SIZE + 1); + // keep 2-byte header region and write raw string + int written = encodeStringToBufferAt(position + 2, s); + if (written >= 0) { + if (written < (1 << 8)) { + buffer.putByte(position++, STR8); + buffer.putByte(position++, (byte) written); + position += written; } - - if(cr.isError()) { - if((cr.isMalformed() && config.getActionOnMalFormedInput() == CodingErrorAction.REPORT) || - (cr.isUnmappable() && config.getActionOnUnmappableCharacter() == CodingErrorAction.REPORT)) { - cr.throwException(); + else { + if (written >= (1 << 16)) { + // this must not happen because s.length() is less than 2^8 and (2^8) * UTF_8_MAX_CHAR_SIZE is less than 2^16 + throw new IllegalArgumentException("Unexpected UTF-8 encoder state"); } + // move 1 byte backward to expand 3-byte header region to 3 bytes + buffer.putBytes(position + 3, + buffer.array(), buffer.arrayOffset() + position + 2, written); + // write 3-byte header + buffer.putByte(position++, STR16); + buffer.putShort(position, (short) written); + position += 2; + position += written; } + return this; } - catch(CharacterCodingException e) { - throw new MessageStringCodingException(e); + } + else if (s.length() < (1 << 16)) { + // ensure capacity for 3-byte raw string header + the maximum string size (+ 2 bytes for fallback code) + ensureCapacity(3 + s.length() * UTF_8_MAX_CHAR_SIZE + 2); + // keep 3-byte header region and write raw string + int written = encodeStringToBufferAt(position + 3, s); + if (written >= 0) { + if (written < (1 << 16)) { + buffer.putByte(position++, STR16); + buffer.putShort(position, (short) written); + position += 2; + position += written; + } + else { + if (written >= (1 << 32)) { + // this must not happen because s.length() is less than 2^16 and (2^16) * UTF_8_MAX_CHAR_SIZE is less than 2^32 + throw new IllegalArgumentException("Unexpected UTF-8 encoder state"); + } + // move 2 bytes backward to expand 3-byte header region to 5 bytes + buffer.putBytes(position + 5, + buffer.array(), buffer.arrayOffset() + position + 3, written); + // write 3-byte header header + buffer.putByte(position++, STR32); + buffer.putInt(position, written); + position += 4; + position += written; + } + return this; } } - encodeBuffer.flip(); - int strLen = encodeBuffer.remaining(); - - // Preserve the current buffer - MessageBuffer tmpBuf = buffer; + // Here doesn't use above optimized code for s.length() < (1 << 32) so that + // ensureCapacity is not called with an integer larger than (3 + ((1 << 16) * UTF_8_MAX_CHAR_SIZE) + 2). + // This makes it sure that MessageBufferOutput.next won't be called a size larger than + // 384KB, which is OK size to keep in memory. - // Switch the buffer to write the string length - if(strLenBuffer == null) { - strLenBuffer = MessageBuffer.newBuffer(5); - } - buffer = strLenBuffer; - position = 0; - // pack raw string header (string binary size) - packRawStringHeader(strLen); - flush(); // We need to dump the data here to MessageBufferOutput so that we can switch back to the original buffer - - // Reset to the original buffer (or encodeBuffer if new buffer is allocated) - buffer = isExtended ? MessageBuffer.wrap(encodeBuffer) : tmpBuf; - // No need exists to write payload since the encoded string (payload) is already written to the buffer - position = strLen; + // fallback + packStringWithGetBytes(s); return this; } - public MessagePacker packArrayHeader(int arraySize) throws IOException { - if(arraySize < 0) + public MessagePacker packArrayHeader(int arraySize) + throws IOException + { + if (arraySize < 0) { throw new IllegalArgumentException("array size must be >= 0"); + } - if(arraySize < (1 << 4)) { + if (arraySize < (1 << 4)) { writeByte((byte) (FIXARRAY_PREFIX | arraySize)); } - else if(arraySize < (1 << 16)) { + else if (arraySize < (1 << 16)) { writeByteAndShort(ARRAY16, (short) arraySize); } else { @@ -456,14 +577,17 @@ else if(arraySize < (1 << 16)) { return this; } - public MessagePacker packMapHeader(int mapSize) throws IOException { - if(mapSize < 0) + public MessagePacker packMapHeader(int mapSize) + throws IOException + { + if (mapSize < 0) { throw new IllegalArgumentException("map size must be >= 0"); + } - if(mapSize < (1 << 4)) { + if (mapSize < (1 << 4)) { writeByte((byte) (FIXMAP_PREFIX | mapSize)); } - else if(mapSize < (1 << 16)) { + else if (mapSize < (1 << 16)) { writeByteAndShort(MAP16, (short) mapSize); } else { @@ -472,57 +596,63 @@ else if(mapSize < (1 << 16)) { return this; } - public MessagePacker packValue(Value v) throws IOException { + public MessagePacker packValue(Value v) + throws IOException + { v.writeTo(this); return this; } - public MessagePacker packExtendedTypeHeader(int extType, int payloadLen) throws IOException { - if(payloadLen < (1 << 8)) { - if(payloadLen > 0 && (payloadLen & (payloadLen - 1)) == 0) { // check whether dataLen == 2^x - if(payloadLen == 1) { - writeByteAndByte(FIXEXT1, (byte) extType); + public MessagePacker packExtensionTypeHeader(byte extType, int payloadLen) + throws IOException + { + if (payloadLen < (1 << 8)) { + if (payloadLen > 0 && (payloadLen & (payloadLen - 1)) == 0) { // check whether dataLen == 2^x + if (payloadLen == 1) { + writeByteAndByte(FIXEXT1, extType); } - else if(payloadLen == 2) { - writeByteAndByte(FIXEXT2, (byte) extType); + else if (payloadLen == 2) { + writeByteAndByte(FIXEXT2, extType); } - else if(payloadLen == 4) { - writeByteAndByte(FIXEXT4, (byte) extType); + else if (payloadLen == 4) { + writeByteAndByte(FIXEXT4, extType); } - else if(payloadLen == 8) { - writeByteAndByte(FIXEXT8, (byte) extType); + else if (payloadLen == 8) { + writeByteAndByte(FIXEXT8, extType); } - else if(payloadLen == 16) { - writeByteAndByte(FIXEXT16, (byte) extType); + else if (payloadLen == 16) { + writeByteAndByte(FIXEXT16, extType); } else { writeByteAndByte(EXT8, (byte) payloadLen); - writeByte((byte) extType); + writeByte(extType); } } else { writeByteAndByte(EXT8, (byte) payloadLen); - writeByte((byte) extType); + writeByte(extType); } } - else if(payloadLen < (1 << 16)) { + else if (payloadLen < (1 << 16)) { writeByteAndShort(EXT16, (short) payloadLen); - writeByte((byte) extType); + writeByte(extType); } else { writeByteAndInt(EXT32, payloadLen); - writeByte((byte) extType); + writeByte(extType); // TODO support dataLen > 2^31 - 1 } return this; } - public MessagePacker packBinaryHeader(int len) throws IOException { - if(len < (1 << 8)) { + public MessagePacker packBinaryHeader(int len) + throws IOException + { + if (len < (1 << 8)) { writeByteAndByte(BIN8, (byte) len); } - else if(len < (1 << 16)) { + else if (len < (1 << 16)) { writeByteAndShort(BIN16, (short) len); } else { @@ -531,14 +661,16 @@ else if(len < (1 << 16)) { return this; } - public MessagePacker packRawStringHeader(int len) throws IOException { - if(len < (1 << 5)) { + public MessagePacker packRawStringHeader(int len) + throws IOException + { + if (len < (1 << 5)) { writeByte((byte) (FIXSTR_PREFIX | len)); } - else if(len < (1 << 8)) { + else if (len < (1 << 8)) { writeByteAndByte(STR8, (byte) len); } - else if(len < (1 << 16)) { + else if (len < (1 << 16)) { writeByteAndShort(STR16, (short) len); } else { @@ -547,70 +679,83 @@ else if(len < (1 << 16)) { return this; } + /** + * Writes buffer to the output. + * This method is used with packRawStringHeader or packBinaryHeader. + * + * @param src the data to add + * @return this + * @throws IOException + */ + public MessagePacker writePayload(byte[] src) + throws IOException + { + return writePayload(src, 0, src.length); + } - public MessagePacker writePayload(ByteBuffer src) throws IOException { - int len = src.remaining(); - if(len >= config.getPackerRawDataCopyingThreshold()) { - // Use the source ByteBuffer directly to avoid memory copy - - // First, flush the current buffer contents - flush(); - - // Wrap the input source as a MessageBuffer - MessageBuffer wrapped = MessageBuffer.wrap(src); - // Then, dump the source data to the output - out.flush(wrapped); - src.position(src.limit()); - flushedBytes += len; + /** + * Writes buffer to the output. + * This method is used with packRawStringHeader or packBinaryHeader. + * + * @param src the data to add + * @param off the start offset in the data + * @param len the number of bytes to add + * @return this + * @throws IOException + */ + public MessagePacker writePayload(byte[] src, int off, int len) + throws IOException + { + if (buffer.size() - position < len || len > bufferFlushThreshold) { + flush(); // call flush before write + out.write(src, off, len); + totalFlushBytes += len; } else { - // If the input source is small, simply copy the contents to the buffer - while(src.remaining() > 0) { - if(position >= buffer.size()) { - flush(); - } - prepareBuffer(); - int writeLen = Math.min(buffer.size() - position, src.remaining()); - buffer.putByteBuffer(position, src, writeLen); - position += writeLen; - } + buffer.putBytes(position, src, off, len); + position += len; } - return this; } - public MessagePacker writePayload(byte[] src) throws IOException { - return writePayload(src, 0, src.length); + /** + * Writes buffer to the output. + * Unlike writePayload method, addPayload method doesn't copy the source data. It means that the caller + * must not modify the data after calling this method. + * + * @param src the data to add + * @return this + * @throws IOException + */ + public MessagePacker addPayload(byte[] src) + throws IOException + { + return addPayload(src, 0, src.length); } - public MessagePacker writePayload(byte[] src, int off, int len) throws IOException { - if(len >= config.getPackerRawDataCopyingThreshold()) { - // Use the input array directory to avoid memory copy - - // Flush the current buffer contents - flush(); - - // Wrap the input array as a MessageBuffer - MessageBuffer wrapped = MessageBuffer.wrap(src).slice(off, len); - // Dump the source data to the output - out.flush(wrapped); - flushedBytes += len; + /** + * Writes buffer to the output. + * Unlike writePayload method, addPayload method doesn't copy the source data. It means that the caller + * must not modify the data after calling this method. + * + * @param src the data to add + * @param off the start offset in the data + * @param len the number of bytes to add + * @return this + * @throws IOException + */ + public MessagePacker addPayload(byte[] src, int off, int len) + throws IOException + { + if (buffer.size() - position < len || len > bufferFlushThreshold) { + flush(); // call flush before add + out.add(src, off, len); + totalFlushBytes += len; } else { - int cursor = 0; - while(cursor < len) { - if(buffer != null && position >= buffer.size()) { - flush(); - } - prepareBuffer(); - int writeLen = Math.min(buffer.size() - position, len - cursor); - buffer.putBytes(position, src, off + cursor, writeLen); - position += writeLen; - cursor += writeLen; - } + buffer.putBytes(position, src, off, len); + position += len; } return this; } - - } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageSizeException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageSizeException.java index 0947a17f8..7636f614c 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageSizeException.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageSizeException.java @@ -18,22 +18,25 @@ /** * Thrown to indicate too large message size (e.g, larger than 2^31-1). */ -public class MessageSizeException extends MessagePackException { +public class MessageSizeException + extends MessagePackException +{ private final long size; - public MessageSizeException(long size) { + public MessageSizeException(long size) + { super(); this.size = size; } - public MessageSizeException(String message, long size) { + public MessageSizeException(String message, long size) + { super(message); this.size = size; } - public long getSize() { + public long getSize() + { return size; } - - } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageStringCodingException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageStringCodingException.java index fbe38bffe..b4d549c4b 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageStringCodingException.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageStringCodingException.java @@ -20,18 +20,22 @@ /** * Thrown to indicate an error when encoding/decoding a String value */ -public class MessageStringCodingException extends MessagePackException { - public MessageStringCodingException(String message, CharacterCodingException cause) { +public class MessageStringCodingException + extends MessagePackException +{ + public MessageStringCodingException(String message, CharacterCodingException cause) + { super(message, cause); } - public MessageStringCodingException(CharacterCodingException cause) { + public MessageStringCodingException(CharacterCodingException cause) + { super(cause); } @Override - public CharacterCodingException getCause() { + public CharacterCodingException getCause() + { return (CharacterCodingException) super.getCause(); } - } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageTypeCastException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageTypeCastException.java new file mode 100644 index 000000000..62e7d914e --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageTypeCastException.java @@ -0,0 +1,40 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.core; + +public class MessageTypeCastException + extends MessageTypeException +{ + public MessageTypeCastException() + { + super(); + } + + public MessageTypeCastException(String message) + { + super(message); + } + + public MessageTypeCastException(String message, Throwable cause) + { + super(message, cause); + } + + public MessageTypeCastException(Throwable cause) + { + super(cause); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageTypeException.java b/msgpack-core/src/main/java/org/msgpack/core/MessageTypeException.java index 925f5fd3f..f457ab82b 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageTypeException.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageTypeException.java @@ -18,20 +18,26 @@ /** * Thrown when a type mismatch error occurs */ -public class MessageTypeException extends MessagePackException { - public MessageTypeException() { +public class MessageTypeException + extends MessagePackException +{ + public MessageTypeException() + { super(); } - public MessageTypeException(String message) { + public MessageTypeException(String message) + { super(message); } - public MessageTypeException(String message, Throwable cause) { + public MessageTypeException(String message, Throwable cause) + { super(message, cause); } - public MessageTypeException(Throwable cause) { + public MessageTypeException(Throwable cause) + { super(cause); } } diff --git a/msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java b/msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java index 8f246830e..a7cf970eb 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java +++ b/msgpack-core/src/main/java/org/msgpack/core/MessageUnpacker.java @@ -15,38 +15,37 @@ // package org.msgpack.core; +import org.msgpack.core.MessagePack.Code; +import org.msgpack.core.buffer.MessageBuffer; +import org.msgpack.core.buffer.MessageBufferInput; +import org.msgpack.value.ImmutableValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueFactory; +import org.msgpack.value.Variable; + import java.io.Closeable; -import java.io.EOFException; import java.io.IOException; -import java.nio.ByteBuffer; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; -import java.nio.charset.MalformedInputException; - -import org.msgpack.core.MessagePack.Code; -import org.msgpack.core.buffer.MessageBuffer; -import org.msgpack.core.buffer.MessageBufferInput; -import org.msgpack.value.Cursor; -import org.msgpack.value.ValueType; -import org.msgpack.value.holder.FloatHolder; -import org.msgpack.value.holder.IntegerHolder; -import org.msgpack.value.holder.ValueHolder; -import org.msgpack.value.impl.CursorImpl; - -import static org.msgpack.core.Preconditions.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import static org.msgpack.core.Preconditions.checkNotNull; /** * MessageUnpacker lets an application read message-packed values from a data stream. * The application needs to call {@link #getNextFormat()} followed by an appropriate unpackXXX method according to the the returned format type. - * + *

*

  * 
- *     MessageUnpacker unpacker = MessagePackFactory.DEFAULT.newUnpacker(...);
+ *     MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(...);
  *     while(unpacker.hasNext()) {
  *         MessageFormat f = unpacker.getNextFormat();
  *         switch(f) {
@@ -61,17 +60,23 @@
  *                break;
  *             }
  *             // ...
-  *       }
+ *       }
  *     }
  *
  * 
  * 
*/ -public class MessageUnpacker implements Closeable { - - private final static MessageBuffer EMPTY_BUFFER = MessageBuffer.wrap(new byte[0]); - - private final MessagePack.Config config; +public class MessageUnpacker + implements Closeable +{ + private static final MessageBuffer EMPTY_BUFFER = MessageBuffer.wrap(new byte[0]); + + private final boolean allowReadingStringAsBinary; + private final boolean allowReadingBinaryAsString; + private final CodingErrorAction actionOnMalformedString; + private final CodingErrorAction actionOnUnmappableString; + private final int stringSizeLimit; + private final int stringDecoderBufferSize; private MessageBufferInput in; @@ -79,6 +84,7 @@ public class MessageUnpacker implements Closeable { * Points to the current buffer to read */ private MessageBuffer buffer = EMPTY_BUFFER; + /** * Cursor position in the current buffer */ @@ -90,19 +96,20 @@ public class MessageUnpacker implements Closeable { private long totalReadBytes; /** - * For preserving the next buffer to use + * An extra buffer for reading a small number value across the input buffer boundary. + * At most 8-byte buffer (for readLong used by uint 64 and UTF-8 character decoding) is required. */ - private MessageBuffer secondaryBuffer = null; + private final MessageBuffer numberBuffer = MessageBuffer.allocate(8); /** - * Extra buffer for string data at the buffer boundary. Using 17-byte buffer (for FIXEXT16) is sufficient. + * After calling prepareNumberBuffer(), the caller should use this variable to read from the returned MessageBuffer. */ - private final MessageBuffer extraBuffer = MessageBuffer.wrap(new byte[24]); + private int nextReadPosition; /** - * True if no more data is available from the MessageBufferInput + * For decoding String in unpackString. */ - private boolean reachedEOF = false; + private StringBuilder decodeStringBuffer; /** * For decoding String in unpackString. @@ -114,44 +121,32 @@ public class MessageUnpacker implements Closeable { */ private CharBuffer decodeBuffer; - - /** - * Get a {@link org.msgpack.value.Cursor} for traversing message-packed values - * @return - */ - public Cursor getCursor() { - return new CursorImpl(this); - } - - - /** * Create an MessageUnpacker that reads data from the given MessageBufferInput * * @param in */ - public MessageUnpacker(MessageBufferInput in) { - this(in, MessagePack.DEFAULT_CONFIG); - } - - - /** - * Create an MessageUnpacker - * @param in - * @param config configuration - */ - public MessageUnpacker(MessageBufferInput in, MessagePack.Config config) { - // Root constructor. All of the constructors must call this constructor. + public MessageUnpacker(MessageBufferInput in, MessagePack.UnpackerConfig config) + { this.in = checkNotNull(in, "MessageBufferInput is null"); - this.config = checkNotNull(config, "Config"); + // We need to copy the configuration parameters since the config object is mutable + this.allowReadingStringAsBinary = config.allowReadingStringAsBinary; + this.allowReadingBinaryAsString = config.allowReadingBinaryAsString; + this.actionOnMalformedString = config.actionOnMalformedString; + this.actionOnUnmappableString = config.actionOnUnmappableString; + this.stringSizeLimit = config.stringSizeLimit; + this.stringDecoderBufferSize = config.stringDecoderBufferSize; } /** * Reset input. This method doesn't close the old resource. + * * @param in new input * @return the old resource */ - public MessageBufferInput reset(MessageBufferInput in) throws IOException { + public MessageBufferInput reset(MessageBufferInput in) + throws IOException + { MessageBufferInput newIn = checkNotNull(in, "MessageBufferInput is null"); // Reset the internal states @@ -160,256 +155,194 @@ public MessageBufferInput reset(MessageBufferInput in) throws IOException { this.buffer = EMPTY_BUFFER; this.position = 0; this.totalReadBytes = 0; - this.secondaryBuffer = null; - this.reachedEOF = false; // No need to initialize the already allocated string decoder here since we can reuse it. + return old; } - public long getTotalReadBytes() { + public long getTotalReadBytes() + { return totalReadBytes + position; } - private void prepareDecoder() { - if(decoder == null) { - decodeBuffer = CharBuffer.allocate(config.getStringDecoderBufferSize()); - decoder = MessagePack.UTF8.newDecoder() - .onMalformedInput(config.getActionOnMalFormedInput()) - .onUnmappableCharacter(config.getActionOnUnmappableCharacter()); - } - } - - /** - * Relocate the cursor position so that it points to the real buffer + * Get the next buffer without changing the position * - * @return true if it succeeds to move the cursor, or false if there is no more buffer to read - * @throws IOException when failed to retrieve next buffer + * @return + * @throws IOException */ - private boolean ensureBuffer() throws IOException { - while(buffer != null && position >= buffer.size()) { - // Fetch the next buffer - int bufferSize = buffer.size(); - position -= bufferSize; - totalReadBytes += bufferSize; - buffer = takeNextBuffer(); + private MessageBuffer getNextBuffer() + throws IOException + { + MessageBuffer next = in.next(); + if (next == null) { + throw new MessageInsufficientBufferException(); } - return buffer != null; + assert (buffer != null); + totalReadBytes += buffer.size(); + return next; } - private MessageBuffer takeNextBuffer() throws IOException { - if(reachedEOF) - return null; - - MessageBuffer nextBuffer = null; - if(secondaryBuffer == null) { - nextBuffer = in.next(); - } - else { - nextBuffer = secondaryBuffer; - secondaryBuffer = null; - } - - if(nextBuffer == null) { - reachedEOF = true; - } - return nextBuffer; + private void nextBuffer() + throws IOException + { + buffer = getNextBuffer(); + position = 0; } - /** - * Ensure that the buffer has the data of at least the specified size. + * Returns a short size buffer (upto 8 bytes) to read a number value * - * @param byteSizeToRead the data size to be read - * @return if the buffer can have the data of the specified size returns true, or if the input source reached an EOF, it returns false. + * @param readLength + * @return * @throws IOException + * @throws MessageInsufficientBufferException If no more buffer can be acquired from the input source for reading the specified data length */ - private boolean ensure(int byteSizeToRead) throws IOException { - if(byteSizeToRead == 0) - return true; + private MessageBuffer prepareNumberBuffer(int readLength) + throws IOException + { + int remaining = buffer.size() - position; + if (remaining >= readLength) { + // When the data is contained inside the default buffer + nextReadPosition = position; + position += readLength; // here assumes following buffer.getXxx never throws exception + return buffer; // Return the default buffer + } + else { + // When the default buffer doesn't contain the whole length - if(!ensureBuffer()) - return false; + // TODO loop this method until castBuffer is filled + MessageBuffer next = getNextBuffer(); - // The buffer contains the data - if(position + byteSizeToRead <= buffer.size()) { - // OK - return true; - } + if (remaining > 0) { + // TODO This doesn't work if MessageBuffer is allocated by newDirectBuffer. + // Add copy method to MessageBuffer to solve this issue. - // When the data is at the boundary - /* - |---(byte size to read) ----| - -- current buffer --| - |--- extra buffer (slice) --|----| - |-------|---------- secondary buffer (slice) ----------------| - - */ - - // If the byte size to read fits within the extra buffer, use the extraBuffer - MessageBuffer newBuffer = byteSizeToRead <= extraBuffer.size() ? extraBuffer : MessageBuffer.newBuffer(byteSizeToRead); - - // Copy the remaining buffer contents to the new buffer - int firstHalfSize = buffer.size() - position; - if(firstHalfSize > 0) - buffer.copyTo(position, newBuffer, 0, firstHalfSize); - - // Read the last half contents from the next buffers - int cursor = firstHalfSize; - while(cursor < byteSizeToRead) { - secondaryBuffer = takeNextBuffer(); - if(secondaryBuffer == null) - return false; // No more buffer to read - - // Copy the contents from the secondary buffer to the new buffer - int copyLen = Math.min(byteSizeToRead - cursor, secondaryBuffer.size()); - secondaryBuffer.copyTo(0, newBuffer, cursor, copyLen); - - // Truncate the copied part from the secondaryBuffer - secondaryBuffer = copyLen == secondaryBuffer.size() ? null : secondaryBuffer.slice(copyLen, secondaryBuffer.size()-copyLen); - cursor += copyLen; - } + // Copy the data fragment from the current buffer + numberBuffer.putBytes(0, buffer.array(), buffer.arrayOffset() + position, remaining); + numberBuffer.putBytes(remaining, next.array(), next.arrayOffset(), readLength - remaining); - // Replace the current buffer with the new buffer - totalReadBytes += position; - buffer = byteSizeToRead == newBuffer.size() ? newBuffer : newBuffer.slice(0, byteSizeToRead); - position = 0; + buffer = next; + position = readLength - remaining; + nextReadPosition = 0; - return true; + return numberBuffer; // Return the numberBuffer + } + else { + buffer = next; + position = readLength; + nextReadPosition = 0; + return buffer; + } + } + } + + private static int utf8MultibyteCharacterSize(byte firstByte) + { + return Integer.numberOfLeadingZeros(~(firstByte & 0xff) << 24); } /** * Returns true true if this unpacker has more elements. * When this returns true, subsequent call to {@link #getNextFormat()} returns an - * MessageFormat instance. If false, {@link #getNextFormat()} will throw an EOFException. + * MessageFormat instance. If false, next {@link #getNextFormat()} call will throw an MessageInsufficientBufferException. * * @return true if this unpacker has more elements to read */ - public boolean hasNext() throws IOException { - return ensure(1); + public boolean hasNext() + throws IOException + { + while (buffer.size() <= position) { + MessageBuffer next = in.next(); + if (next == null) { + return false; + } + totalReadBytes += buffer.size(); + buffer = next; + position = 0; + } + return true; } /** * Returns the next MessageFormat type. This method should be called after {@link #hasNext()} returns true. - * If {@link #hasNext()} returns false, calling this method throws {@link java.io.EOFException}. - * + * If {@link #hasNext()} returns false, calling this method throws {@link MessageInsufficientBufferException}. + *

* This method does not proceed the internal cursor. + * * @return the next MessageFormat * @throws IOException when failed to read the input data. - * @throws EOFException when the end of file reached, i.e. {@link #hasNext()} == false. + * @throws MessageInsufficientBufferException when the end of file reached, i.e. {@link #hasNext()} == false. */ - public MessageFormat getNextFormat() throws IOException { - byte b = lookAhead(); + public MessageFormat getNextFormat() + throws IOException + { + // makes sure that buffer has at leat 1 byte + if (!hasNext()) { + throw new MessageInsufficientBufferException(); + } + byte b = buffer.getByte(position); return MessageFormat.valueOf(b); } - /** - * Look-ahead a byte value at the current cursor position. - * This method does not proceed the cursor. - * - * @return - * @throws IOException - */ - private byte lookAhead() throws IOException { - if(ensure(1)) - return buffer.getByte(position); - else - throw new EOFException(); - } - - - /** - * Get the head byte value and proceeds the cursor by 1 - */ - private byte consume() throws IOException { - byte b = lookAhead(); - position += 1; - return b; - } - - /** - * Proceeds the cursor by the specified byte length - */ - private void consume(int numBytes) throws IOException { - assert(numBytes >= 0); - // If position + numBytes becomes negative, it indicates an overflow from Integer.MAX_VALUE. - if(position + numBytes < 0) { - ensureBuffer(); - } - position += numBytes; - } - /** * Read a byte value at the cursor and proceed the cursor. * * @return * @throws IOException */ - private byte readByte() throws IOException { - if(!ensure(1)) { - throw new EOFException("insufficient data length for reading byte value"); + private byte readByte() + throws IOException + { + if (buffer.size() > position) { + byte b = buffer.getByte(position); + position++; + return b; } - byte b = buffer.getByte(position); - consume(1); - return b; - } - - private short readShort() throws IOException { - if(!ensure(2)) { - throw new EOFException("insufficient data length for reading short value"); + else { + nextBuffer(); + if (buffer.size() > 0) { + byte b = buffer.getByte(0); + position = 1; + return b; + } + return readByte(); } - short s = buffer.getShort(position); - consume(2); - return s; } - private int readInt() throws IOException { - if(!ensure(4)) { - throw new EOFException("insufficient data length for reading int value"); - } - int i = buffer.getInt(position); - consume(4); - return i; + private short readShort() + throws IOException + { + MessageBuffer numberBuffer = prepareNumberBuffer(2); + return numberBuffer.getShort(nextReadPosition); } - private float readFloat() throws IOException { - if(!ensure(4)) { - throw new EOFException("insufficient data length for reading float value"); - } - float f = buffer.getFloat(position); - consume(4); - return f; + private int readInt() + throws IOException + { + MessageBuffer numberBuffer = prepareNumberBuffer(4); + return numberBuffer.getInt(nextReadPosition); } - private long readLong() throws IOException { - if(!ensure(8)) { - throw new EOFException("insufficient data length for reading long value"); - } - long l = buffer.getLong(position); - consume(8); - return l; + private long readLong() + throws IOException + { + MessageBuffer numberBuffer = prepareNumberBuffer(8); + return numberBuffer.getLong(nextReadPosition); } - private double readDouble() throws IOException { - if(!ensure(8)) { - throw new EOFException("insufficient data length for reading double value"); - } - double d = buffer.getDouble(position); - consume(8); - return d; + private float readFloat() + throws IOException + { + MessageBuffer numberBuffer = prepareNumberBuffer(4); + return numberBuffer.getFloat(nextReadPosition); } - - /** - * Skip reading the specified number of bytes. Use this method only if you know skipping data is safe. - * For simply skipping the next value, use {@link #skipValue()}. - * - * @param numBytes - * @throws IOException - */ - public void skipBytes(int numBytes) throws IOException { - checkArgument(numBytes >= 0, "skip length must be >= 0: " + numBytes); - consume(numBytes); + private double readDouble() + throws IOException + { + MessageBuffer numberBuffer = prepareNumberBuffer(8); + return numberBuffer.getDouble(nextReadPosition); } /** @@ -417,16 +350,14 @@ public void skipBytes(int numBytes) throws IOException { * * @throws IOException */ - public void skipValue() throws IOException { + public void skipValue() + throws IOException + { int remainingValues = 1; - while(remainingValues > 0) { - if(reachedEOF) { - throw new EOFException(); - } - - MessageFormat f = getNextFormat(); - byte b = consume(); - switch(f) { + while (remainingValues > 0) { + byte b = readByte(); + MessageFormat f = MessageFormat.valueOf(b); + switch (f) { case POSFIXINT: case NEGFIXINT: case BOOLEAN: @@ -444,62 +375,62 @@ public void skipValue() throws IOException { } case FIXSTR: { int strLen = b & 0x1f; - consume(strLen); + skipPayload(strLen); break; } case INT8: case UINT8: - consume(1); + skipPayload(1); break; case INT16: case UINT16: - consume(2); + skipPayload(2); break; case INT32: case UINT32: case FLOAT32: - consume(4); + skipPayload(4); break; case INT64: case UINT64: case FLOAT64: - consume(8); + skipPayload(8); break; case BIN8: case STR8: - consume(readNextLength8()); + skipPayload(readNextLength8()); break; case BIN16: case STR16: - consume(readNextLength16()); + skipPayload(readNextLength16()); break; case BIN32: case STR32: - consume(readNextLength32()); + skipPayload(readNextLength32()); break; case FIXEXT1: - consume(2); + skipPayload(2); break; case FIXEXT2: - consume(3); + skipPayload(3); break; case FIXEXT4: - consume(5); + skipPayload(5); break; case FIXEXT8: - consume(9); + skipPayload(9); break; case FIXEXT16: - consume(17); + skipPayload(17); break; case EXT8: - consume(readNextLength8() + 1); + skipPayload(readNextLength8() + 1); break; case EXT16: - consume(readNextLength16() + 1); + skipPayload(readNextLength16() + 1); break; case EXT32: - consume(readNextLength32() + 1); + skipPayload(readNextLength32() + 1); break; case ARRAY16: remainingValues += readNextLength16(); @@ -514,7 +445,7 @@ public void skipValue() throws IOException { remainingValues += readNextLength32() * 2; // TODO check int overflow break; case NEVER_USED: - throw new MessageFormatException(String.format("unknown code: %02x is found", b)); + throw new MessageNeverUsedFormatException("Encountered 0xC1 \"NEVER_USED\" byte"); } remainingValues--; @@ -529,102 +460,190 @@ public void skipValue() throws IOException { * @return * @throws MessageFormatException */ - private static MessageTypeException unexpected(String expected, byte b) - throws MessageTypeException { - ValueType type = ValueType.valueOf(b); - return new MessageTypeException(String.format("Expected %s, but got %s (%02x)", expected, type.toTypeName(), b)); + private static MessagePackException unexpected(String expected, byte b) + { + MessageFormat format = MessageFormat.valueOf(b); + if (format == MessageFormat.NEVER_USED) { + return new MessageNeverUsedFormatException(String.format("Expected %s, but encountered 0xC1 \"NEVER_USED\" byte", expected)); + } + else { + String name = format.getValueType().name(); + String typeName = name.substring(0, 1) + name.substring(1).toLowerCase(); + return new MessageTypeException(String.format("Expected %s, but got %s (%02x)", expected, typeName, b)); + } } - public MessageFormat unpackValue(ValueHolder holder) throws IOException { + public ImmutableValue unpackValue() + throws IOException + { MessageFormat mf = getNextFormat(); - switch(mf.getValueType()) { + switch (mf.getValueType()) { case NIL: - unpackNil(); - holder.setNil(); - break; + readByte(); + return ValueFactory.newNil(); case BOOLEAN: - holder.setBoolean(unpackBoolean()); - break; - case INTEGER: { - unpackInteger(holder.getIntegerHolder()); - holder.setToInteger(); - break; + return ValueFactory.newBoolean(unpackBoolean()); + case INTEGER: + switch (mf) { + case UINT64: + return ValueFactory.newInteger(unpackBigInteger()); + default: + return ValueFactory.newInteger(unpackLong()); + } + case FLOAT: + return ValueFactory.newFloat(unpackDouble()); + case STRING: { + int length = unpackRawStringHeader(); + return ValueFactory.newString(readPayload(length)); + } + case BINARY: { + int length = unpackBinaryHeader(); + return ValueFactory.newBinary(readPayload(length)); + } + case ARRAY: { + int size = unpackArrayHeader(); + Value[] array = new Value[size]; + for (int i = 0; i < size; i++) { + array[i] = unpackValue(); + } + return ValueFactory.newArray(array); } - case FLOAT: { - unpackFloat(holder.getFloatHolder()); - holder.setToFloat(); - break; + case MAP: { + int size = unpackMapHeader(); + Value[] kvs = new Value[size * 2]; + for (int i = 0; i < size * 2; ) { + kvs[i] = unpackValue(); + i++; + kvs[i] = unpackValue(); + i++; + } + return ValueFactory.newMap(kvs); + } + case EXTENSION: { + ExtensionTypeHeader extHeader = unpackExtensionTypeHeader(); + return ValueFactory.newExtension(extHeader.getType(), readPayload(extHeader.getLength())); + } + default: + throw new MessageNeverUsedFormatException("Unknown value type"); + } + } + + public Variable unpackValue(Variable var) + throws IOException + { + MessageFormat mf = getNextFormat(); + switch (mf.getValueType()) { + case NIL: + readByte(); + var.setNilValue(); + return var; + case BOOLEAN: + var.setBooleanValue(unpackBoolean()); + return var; + case INTEGER: + switch (mf) { + case UINT64: + var.setIntegerValue(unpackBigInteger()); + return var; + default: + var.setIntegerValue(unpackLong()); + return var; + } + case FLOAT: + var.setFloatValue(unpackDouble()); + return var; + case STRING: { + int length = unpackRawStringHeader(); + var.setStringValue(readPayload(length)); + return var; } - case STRING: - int strLen = unpackRawStringHeader(); - holder.setString(readPayloadAsReference(strLen)); - break; case BINARY: { - int binaryLen = unpackBinaryHeader(); - holder.setBinary(readPayloadAsReference(binaryLen)); - break; + int length = unpackBinaryHeader(); + var.setBinaryValue(readPayload(length)); + return var; + } + case ARRAY: { + int size = unpackArrayHeader(); + List list = new ArrayList(size); + for (int i = 0; i < size; i++) { + list.add(unpackValue()); + } + var.setArrayValue(list); + return var; } - case ARRAY: - holder.prepareArrayCursor(this); - break; - case MAP: - holder.prepareMapCursor(this); - break; - case EXTENDED: - ExtendedTypeHeader extHeader = unpackExtendedTypeHeader(); - holder.setExt(extHeader.getType(), readPayloadAsReference(extHeader.getLength())); - break; + case MAP: { + int size = unpackMapHeader(); + Map map = new HashMap(); + for (int i = 0; i < size; i++) { + Value k = unpackValue(); + Value v = unpackValue(); + map.put(k, v); + } + var.setMapValue(map); + return var; + } + case EXTENSION: { + ExtensionTypeHeader extHeader = unpackExtensionTypeHeader(); + var.setExtensionValue(extHeader.getType(), readPayload(extHeader.getLength())); + return var; + } + default: + throw new MessageFormatException("Unknown value type"); } - return mf; } - public Object unpackNil() throws IOException { - byte b = consume(); - if(b == Code.NIL) { - return null; + public void unpackNil() + throws IOException + { + byte b = readByte(); + if (b == Code.NIL) { + return; } throw unexpected("Nil", b); } - - public boolean unpackBoolean() throws IOException { - byte b = consume(); - if(b == Code.FALSE) { + public boolean unpackBoolean() + throws IOException + { + byte b = readByte(); + if (b == Code.FALSE) { return false; - } else if(b == Code.TRUE) { + } + else if (b == Code.TRUE) { return true; } - throw unexpected("boolean", b); } - public byte unpackByte() throws IOException { - byte b = consume(); - if(Code.isFixInt(b)) { + public byte unpackByte() + throws IOException + { + byte b = readByte(); + if (Code.isFixInt(b)) { return b; } - switch(b) { + switch (b) { case Code.UINT8: // unsigned int 8 byte u8 = readByte(); - if(u8 < (byte) 0) { + if (u8 < (byte) 0) { throw overflowU8(u8); } return u8; case Code.UINT16: // unsigned int 16 short u16 = readShort(); - if(u16 < 0 || u16 > Byte.MAX_VALUE) { + if (u16 < 0 || u16 > Byte.MAX_VALUE) { throw overflowU16(u16); } return (byte) u16; case Code.UINT32: // unsigned int 32 int u32 = readInt(); - if(u32 < 0 || u32 > Byte.MAX_VALUE) { + if (u32 < 0 || u32 > Byte.MAX_VALUE) { throw overflowU32(u32); } return (byte) u32; case Code.UINT64: // unsigned int 64 long u64 = readLong(); - if(u64 < 0L || u64 > Byte.MAX_VALUE) { + if (u64 < 0L || u64 > Byte.MAX_VALUE) { throw overflowU64(u64); } return (byte) u64; @@ -633,19 +652,19 @@ public byte unpackByte() throws IOException { return i8; case Code.INT16: // signed int 16 short i16 = readShort(); - if(i16 < Byte.MIN_VALUE || i16 > Byte.MAX_VALUE) { + if (i16 < Byte.MIN_VALUE || i16 > Byte.MAX_VALUE) { throw overflowI16(i16); } return (byte) i16; case Code.INT32: // signed int 32 int i32 = readInt(); - if(i32 < Byte.MIN_VALUE || i32 > Byte.MAX_VALUE) { + if (i32 < Byte.MIN_VALUE || i32 > Byte.MAX_VALUE) { throw overflowI32(i32); } return (byte) i32; case Code.INT64: // signed int 64 long i64 = readLong(); - if(i64 < Byte.MIN_VALUE || i64 > Byte.MAX_VALUE) { + if (i64 < Byte.MIN_VALUE || i64 > Byte.MAX_VALUE) { throw overflowI64(i64); } return (byte) i64; @@ -653,30 +672,32 @@ public byte unpackByte() throws IOException { throw unexpected("Integer", b); } - public short unpackShort() throws IOException { - byte b = consume(); - if(Code.isFixInt(b)) { + public short unpackShort() + throws IOException + { + byte b = readByte(); + if (Code.isFixInt(b)) { return (short) b; } - switch(b) { + switch (b) { case Code.UINT8: // unsigned int 8 byte u8 = readByte(); return (short) (u8 & 0xff); case Code.UINT16: // unsigned int 16 short u16 = readShort(); - if(u16 < (short) 0) { + if (u16 < (short) 0) { throw overflowU16(u16); } return u16; case Code.UINT32: // unsigned int 32 int u32 = readInt(); - if(u32 < 0 || u32 > Short.MAX_VALUE) { + if (u32 < 0 || u32 > Short.MAX_VALUE) { throw overflowU32(u32); } return (short) u32; case Code.UINT64: // unsigned int 64 long u64 = readLong(); - if(u64 < 0L || u64 > Short.MAX_VALUE) { + if (u64 < 0L || u64 > Short.MAX_VALUE) { throw overflowU64(u64); } return (short) u64; @@ -688,27 +709,28 @@ public short unpackShort() throws IOException { return i16; case Code.INT32: // signed int 32 int i32 = readInt(); - if(i32 < Short.MIN_VALUE || i32 > Short.MAX_VALUE) { + if (i32 < Short.MIN_VALUE || i32 > Short.MAX_VALUE) { throw overflowI32(i32); } return (short) i32; case Code.INT64: // signed int 64 long i64 = readLong(); - if(i64 < Short.MIN_VALUE || i64 > Short.MAX_VALUE) { + if (i64 < Short.MIN_VALUE || i64 > Short.MAX_VALUE) { throw overflowI64(i64); } return (short) i64; } throw unexpected("Integer", b); - } - public int unpackInt() throws IOException { - byte b = consume(); - if(Code.isFixInt(b)) { + public int unpackInt() + throws IOException + { + byte b = readByte(); + if (Code.isFixInt(b)) { return (int) b; } - switch(b) { + switch (b) { case Code.UINT8: // unsigned int 8 byte u8 = readByte(); return u8 & 0xff; @@ -717,13 +739,13 @@ public int unpackInt() throws IOException { return u16 & 0xffff; case Code.UINT32: // unsigned int 32 int u32 = readInt(); - if(u32 < 0) { + if (u32 < 0) { throw overflowU32(u32); } return u32; case Code.UINT64: // unsigned int 64 long u64 = readLong(); - if(u64 < 0L || u64 > (long) Integer.MAX_VALUE) { + if (u64 < 0L || u64 > (long) Integer.MAX_VALUE) { throw overflowU64(u64); } return (int) u64; @@ -738,21 +760,22 @@ public int unpackInt() throws IOException { return i32; case Code.INT64: // signed int 64 long i64 = readLong(); - if(i64 < (long) Integer.MIN_VALUE || i64 > (long) Integer.MAX_VALUE) { + if (i64 < (long) Integer.MIN_VALUE || i64 > (long) Integer.MAX_VALUE) { throw overflowI64(i64); } return (int) i64; } throw unexpected("Integer", b); - } - public long unpackLong() throws IOException { - byte b = consume(); - if(Code.isFixInt(b)) { + public long unpackLong() + throws IOException + { + byte b = readByte(); + if (Code.isFixInt(b)) { return (long) b; } - switch(b) { + switch (b) { case Code.UINT8: // unsigned int 8 byte u8 = readByte(); return (long) (u8 & 0xff); @@ -761,14 +784,15 @@ public long unpackLong() throws IOException { return (long) (u16 & 0xffff); case Code.UINT32: // unsigned int 32 int u32 = readInt(); - if(u32 < 0) { + if (u32 < 0) { return (long) (u32 & 0x7fffffff) + 0x80000000L; - } else { + } + else { return (long) u32; } case Code.UINT64: // unsigned int 64 long u64 = readLong(); - if(u64 < 0L) { + if (u64 < 0L) { throw overflowU64(u64); } return u64; @@ -786,15 +810,16 @@ public long unpackLong() throws IOException { return i64; } throw unexpected("Integer", b); - } - public BigInteger unpackBigInteger() throws IOException { - byte b = consume(); - if(Code.isFixInt(b)) { + public BigInteger unpackBigInteger() + throws IOException + { + byte b = readByte(); + if (Code.isFixInt(b)) { return BigInteger.valueOf((long) b); } - switch(b) { + switch (b) { case Code.UINT8: // unsigned int 8 byte u8 = readByte(); return BigInteger.valueOf((long) (u8 & 0xff)); @@ -803,17 +828,19 @@ public BigInteger unpackBigInteger() throws IOException { return BigInteger.valueOf((long) (u16 & 0xffff)); case Code.UINT32: // unsigned int 32 int u32 = readInt(); - if(u32 < 0) { + if (u32 < 0) { return BigInteger.valueOf((long) (u32 & 0x7fffffff) + 0x80000000L); - } else { + } + else { return BigInteger.valueOf((long) u32); } case Code.UINT64: // unsigned int 64 long u64 = readLong(); - if(u64 < 0L) { + if (u64 < 0L) { BigInteger bi = BigInteger.valueOf(u64 + Long.MAX_VALUE + 1L).setBit(63); return bi; - } else { + } + else { return BigInteger.valueOf(u64); } case Code.INT8: // signed int 8 @@ -832,76 +859,11 @@ public BigInteger unpackBigInteger() throws IOException { throw unexpected("Integer", b); } - - /** - * Unpack an integer, then store the read value to the given holder - * @param holder an integer holder to which the unpacked integer will be set. - * @throws IOException - */ - public void unpackInteger(IntegerHolder holder) throws IOException { - byte b = consume(); - - if(Code.isFixInt(b)) { - holder.setByte(b); - return; - } - - switch(b) { - case Code.INT8: // signed int 8 - holder.setByte(readByte()); - break; - case Code.INT16: - holder.setShort(readShort()); - break; - case Code.INT32: - holder.setInt(readInt()); - break; - case Code.INT64: // signed int 64 - holder.setLong(readLong()); - break; - case Code.UINT8: // unsigned int 8 - byte u8 = readByte(); - if(u8 < 0) { - holder.setShort((short) (u8 & 0xFF)); - } - else { - holder.setByte(u8); - } - break; - case Code.UINT16: // unsigned int 16 - short u16 = readShort(); - if(u16 < 0) { - holder.setInt(u16 & 0xFFFF); - } - else { - holder.setShort(u16); - } - break; - case Code.UINT32: // unsigned int 32 - int u32 = readInt(); - if(u32 < 0) { - holder.setLong((long) (u32 & 0x7fffffff) + 0x80000000L); - } else { - holder.setInt(u32); - } - break; - case Code.UINT64: // unsigned int 64 - long u64 = readLong(); - if(u64 < 0L) { - holder.setBigInteger(BigInteger.valueOf(u64 + Long.MAX_VALUE + 1L).setBit(63)); - } else { - holder.setLong(u64); - } - break; - default: - throw unexpected("Integer", b); - } - } - - - public float unpackFloat() throws IOException { - byte b = consume(); - switch(b) { + public float unpackFloat() + throws IOException + { + byte b = readByte(); + switch (b) { case Code.FLOAT32: // float float fv = readFloat(); return fv; @@ -912,9 +874,11 @@ public float unpackFloat() throws IOException { throw unexpected("Float", b); } - public double unpackDouble() throws IOException { - byte b = consume(); - switch(b) { + public double unpackDouble() + throws IOException + { + byte b = readByte(); + switch (b) { case Code.FLOAT32: // float float fv = readFloat(); return (double) fv; @@ -925,164 +889,249 @@ public double unpackDouble() throws IOException { throw unexpected("Float", b); } - public void unpackFloat(ValueHolder holder) throws IOException { - unpackFloat(holder.getFloatHolder()); - } + private static final String EMPTY_STRING = ""; - public void unpackFloat(FloatHolder holder) throws IOException { - byte b = consume(); - switch(b) { - case Code.FLOAT32: // float - float fv = readFloat(); - holder.setFloat(fv); - break; - case Code.FLOAT64: // double - double dv = readDouble(); - holder.setDouble(dv); - break; - default: - throw unexpected("Float", b); + private void resetDecoder() + { + if (decoder == null) { + decodeBuffer = CharBuffer.allocate(stringDecoderBufferSize); + decoder = MessagePack.UTF8.newDecoder() + .onMalformedInput(actionOnMalformedString) + .onUnmappableCharacter(actionOnUnmappableString); + } + else { + decoder.reset(); + } + if (decodeStringBuffer == null) { + decodeStringBuffer = new StringBuilder(); + } + else { + decodeStringBuffer.setLength(0); } } + public String unpackString() + throws IOException + { + int len = unpackRawStringHeader(); + if (len == 0) { + return EMPTY_STRING; + } + if (len > stringSizeLimit) { + throw new MessageSizeException(String.format("cannot unpack a String of size larger than %,d: %,d", stringSizeLimit, len), len); + } + if (buffer.size() - position >= len) { + return decodeStringFastPath(len); + } - private final static String EMPTY_STRING = ""; - - public String unpackString() throws IOException { - int strLen = unpackRawStringHeader(); - if(strLen > 0) { - if(strLen > config.getMaxUnpackStringSize()) { - throw new MessageSizeException(String.format("cannot unpack a String of size larger than %,d: %,d", config.getMaxUnpackStringSize(), strLen), strLen); - } - - prepareDecoder(); - assert(decoder != null); - - decoder.reset(); - - try { - int cursor = 0; - decodeBuffer.clear(); - StringBuilder sb = new StringBuilder(); - while(cursor < strLen) { - if (!ensure(strLen)) - throw new EOFException(); - - int readLen = Math.min(buffer.size() - position, strLen-cursor); - ByteBuffer bb = buffer.toByteBuffer(position, readLen); - - while(bb.hasRemaining()) { - boolean endOfInput = (cursor + readLen) >= strLen; - CoderResult cr = decoder.decode(bb, decodeBuffer, endOfInput); - - if(endOfInput && cr.isUnderflow()) - cr = decoder.flush(decodeBuffer); - - if(cr.isOverflow()) { - // The output CharBuffer has insufficient space - readLen = bb.limit() - bb.remaining(); - decoder.reset(); - } + resetDecoder(); - if(cr.isUnderflow() && bb.hasRemaining()) { - // input buffer doesn't have enough bytes for multi bytes characters - if(config.getActionOnMalFormedInput() == CodingErrorAction.REPORT) { - throw new MalformedInputException(strLen); + try { + int rawRemaining = len; + while (rawRemaining > 0) { + int bufferRemaining = buffer.size() - position; + if (bufferRemaining >= rawRemaining) { + decodeStringBuffer.append(decodeStringFastPath(rawRemaining)); + break; + } + else if (bufferRemaining == 0) { + nextBuffer(); + } + else { + ByteBuffer bb = buffer.sliceAsByteBuffer(position, bufferRemaining); + int bbStartPosition = bb.position(); + decodeBuffer.clear(); + + CoderResult cr = decoder.decode(bb, decodeBuffer, false); + int readLen = bb.position() - bbStartPosition; + position += readLen; + rawRemaining -= readLen; + decodeStringBuffer.append(decodeBuffer.flip()); + + if (cr.isError()) { + handleCoderError(cr); + } + if (cr.isUnderflow() && readLen < bufferRemaining) { + // handle incomplete multibyte character + int incompleteMultiBytes = utf8MultibyteCharacterSize(buffer.getByte(position)); + ByteBuffer multiByteBuffer = ByteBuffer.allocate(incompleteMultiBytes); + buffer.getBytes(position, buffer.size() - position, multiByteBuffer); + + // read until multiByteBuffer is filled + while (true) { + nextBuffer(); + + int more = multiByteBuffer.remaining(); + if (buffer.size() >= more) { + buffer.getBytes(0, more, multiByteBuffer); + position = more; + break; + } + else { + buffer.getBytes(0, buffer.size(), multiByteBuffer); + position = buffer.size(); } - // trash truncated bytes - while (bb.hasRemaining()) - bb.get(); } - - if(cr.isError()) { - if((cr.isMalformed() && config.getActionOnMalFormedInput() == CodingErrorAction.REPORT) || - (cr.isUnmappable() && config.getActionOnUnmappableCharacter() == CodingErrorAction.REPORT)) { + multiByteBuffer.position(0); + decodeBuffer.clear(); + cr = decoder.decode(multiByteBuffer, decodeBuffer, false); + if (cr.isError()) { + handleCoderError(cr); + } + if (cr.isOverflow() || (cr.isUnderflow() && multiByteBuffer.position() < multiByteBuffer.limit())) { + // isOverflow or isOverflow must not happen. if happened, throw exception + try { cr.throwException(); + throw new MessageFormatException("Unexpected UTF-8 multibyte sequence"); + } + catch (Exception ex) { + throw new MessageFormatException("Unexpected UTF-8 multibyte sequence", ex); } } - - decodeBuffer.flip(); - sb.append(decodeBuffer); - - decodeBuffer.clear(); - cursor += readLen; - consume(readLen); + rawRemaining -= multiByteBuffer.limit(); + decodeStringBuffer.append(decodeBuffer.flip()); } } + } + return decodeStringBuffer.toString(); + } + catch (CharacterCodingException e) { + throw new MessageStringCodingException(e); + } + } - return sb.toString(); + private void handleCoderError(CoderResult cr) + throws CharacterCodingException + { + if ((cr.isMalformed() && actionOnMalformedString == CodingErrorAction.REPORT) || + (cr.isUnmappable() && actionOnUnmappableString == CodingErrorAction.REPORT)) { + cr.throwException(); + } + } + + private String decodeStringFastPath(int length) + { + if (actionOnMalformedString == CodingErrorAction.REPLACE && + actionOnUnmappableString == CodingErrorAction.REPLACE) { + String s = new String(buffer.array(), buffer.arrayOffset() + position, length, MessagePack.UTF8); + position += length; + return s; + } + else { + resetDecoder(); + ByteBuffer bb = buffer.sliceAsByteBuffer(); + bb.limit(position + length); + bb.position(position); + CharBuffer cb; + try { + cb = decoder.decode(bb); } - catch(CharacterCodingException e) { + catch (CharacterCodingException e) { throw new MessageStringCodingException(e); } - } else - return EMPTY_STRING; + position += length; + return cb.toString(); + } } - - public int unpackArrayHeader() throws IOException { - byte b = consume(); - if(Code.isFixedArray(b)) { // fixarray + public int unpackArrayHeader() + throws IOException + { + byte b = readByte(); + if (Code.isFixedArray(b)) { // fixarray return b & 0x0f; } - switch(b) { - case Code.ARRAY16: // array 16 - return readNextLength16(); - case Code.ARRAY32: // array 32 - return readNextLength32(); + switch (b) { + case Code.ARRAY16: { // array 16 + int len = readNextLength16(); + return len; + } + case Code.ARRAY32: { // array 32 + int len = readNextLength32(); + return len; + } } throw unexpected("Array", b); } - public int unpackMapHeader() throws IOException { - byte b = consume(); - if(Code.isFixedMap(b)) { // fixmap + public int unpackMapHeader() + throws IOException + { + byte b = readByte(); + if (Code.isFixedMap(b)) { // fixmap return b & 0x0f; } - switch(b) { - case Code.MAP16: // map 16 - return readNextLength16(); - case Code.MAP32: // map 32 - return readNextLength32(); + switch (b) { + case Code.MAP16: { // map 16 + int len = readNextLength16(); + return len; + } + case Code.MAP32: { // map 32 + int len = readNextLength32(); + return len; + } } throw unexpected("Map", b); } - public ExtendedTypeHeader unpackExtendedTypeHeader() throws IOException { - byte b = consume(); - switch(b) { - case Code.FIXEXT1: - return new ExtendedTypeHeader(1, readByte()); - case Code.FIXEXT2: - return new ExtendedTypeHeader(2, readByte()); - case Code.FIXEXT4: - return new ExtendedTypeHeader(4, readByte()); - case Code.FIXEXT8: - return new ExtendedTypeHeader(8, readByte()); - case Code.FIXEXT16: - return new ExtendedTypeHeader(16, readByte()); + public ExtensionTypeHeader unpackExtensionTypeHeader() + throws IOException + { + byte b = readByte(); + switch (b) { + case Code.FIXEXT1: { + byte type = readByte(); + return new ExtensionTypeHeader(type, 1); + } + case Code.FIXEXT2: { + byte type = readByte(); + return new ExtensionTypeHeader(type, 2); + } + case Code.FIXEXT4: { + byte type = readByte(); + return new ExtensionTypeHeader(type, 4); + } + case Code.FIXEXT8: { + byte type = readByte(); + return new ExtensionTypeHeader(type, 8); + } + case Code.FIXEXT16: { + byte type = readByte(); + return new ExtensionTypeHeader(type, 16); + } case Code.EXT8: { - int len = readNextLength8(); - int t = readByte(); - return new ExtendedTypeHeader(len, t); + MessageBuffer numberBuffer = prepareNumberBuffer(2); + int u8 = numberBuffer.getByte(nextReadPosition); + int length = u8 & 0xff; + byte type = numberBuffer.getByte(nextReadPosition + 1); + return new ExtensionTypeHeader(type, length); } case Code.EXT16: { - int len = readNextLength16(); - int t = readByte(); - return new ExtendedTypeHeader(len, t); + MessageBuffer numberBuffer = prepareNumberBuffer(3); + int u16 = numberBuffer.getShort(nextReadPosition); + int length = u16 & 0xffff; + byte type = numberBuffer.getByte(nextReadPosition + 2); + return new ExtensionTypeHeader(type, length); } case Code.EXT32: { - int len = readNextLength32(); - int t = readByte(); - return new ExtendedTypeHeader(len, t); + MessageBuffer numberBuffer = prepareNumberBuffer(5); + int u32 = numberBuffer.getInt(nextReadPosition); + if (u32 < 0) { + throw overflowU32Size(u32); + } + int length = u32; + byte type = numberBuffer.getByte(nextReadPosition + 4); + return new ExtensionTypeHeader(type, length); } } throw unexpected("Ext", b); } - private int readStringHeader(byte b) throws IOException { - switch(b) { + private int tryReadStringHeader(byte b) + throws IOException + { + switch (b) { case Code.STR8: // str 8 return readNextLength8(); case Code.STR16: // str 16 @@ -1094,8 +1143,10 @@ private int readStringHeader(byte b) throws IOException { } } - private int readBinaryHeader(byte b) throws IOException { - switch(b) { + private int tryReadBinaryHeader(byte b) + throws IOException + { + switch (b) { case Code.BIN8: // bin 8 return readNextLength8(); case Code.BIN16: // bin 16 @@ -1107,59 +1158,104 @@ private int readBinaryHeader(byte b) throws IOException { } } - - public int unpackRawStringHeader() throws IOException { - byte b = consume(); - if(Code.isFixedRaw(b)) { // FixRaw + public int unpackRawStringHeader() + throws IOException + { + byte b = readByte(); + if (Code.isFixedRaw(b)) { // FixRaw return b & 0x1f; } - int len = readStringHeader(b); - if(len >= 0) + int len = tryReadStringHeader(b); + if (len >= 0) { return len; + } - if(config.isReadBinaryAsString()){ - len = readBinaryHeader(b); - if(len >= 0) + if (allowReadingBinaryAsString) { + len = tryReadBinaryHeader(b); + if (len >= 0) { return len; + } } throw unexpected("String", b); } - - public int unpackBinaryHeader() throws IOException { - byte b = consume(); - if(Code.isFixedRaw(b)) { // FixRaw + public int unpackBinaryHeader() + throws IOException + { + byte b = readByte(); + if (Code.isFixedRaw(b)) { // FixRaw return b & 0x1f; } - int len = readBinaryHeader(b); - if(len >= 0) + int len = tryReadBinaryHeader(b); + if (len >= 0) { return len; + } - if(config.isReadStringAsBinary()) { - len = readStringHeader(b); - if(len >= 0) + if (allowReadingStringAsBinary) { + len = tryReadStringHeader(b); + if (len >= 0) { return len; + } } throw unexpected("Binary", b); } - // TODO returns a buffer reference to the payload (zero-copy) + /** + * Skip reading the specified number of bytes. Use this method only if you know skipping data is safe. + * For simply skipping the next value, use {@link #skipValue()}. + * + * @param numBytes + * @throws IOException + */ + private void skipPayload(int numBytes) + throws IOException + { + while (true) { + int bufferRemaining = buffer.size() - position; + if (bufferRemaining >= numBytes) { + position += numBytes; + return; + } + else { + position += bufferRemaining; + numBytes -= bufferRemaining; + } + nextBuffer(); + } + } + public void readPayload(ByteBuffer dst) + throws IOException + { + while (true) { + int dstRemaining = dst.remaining(); + int bufferRemaining = buffer.size() - position; + if (bufferRemaining >= dstRemaining) { + buffer.getBytes(position, dstRemaining, dst); + position += dstRemaining; + return; + } + buffer.getBytes(position, bufferRemaining, dst); + position += bufferRemaining; - public void readPayload(ByteBuffer dst) throws IOException { - while(dst.remaining() > 0) { - if(!ensureBuffer()) - throw new EOFException(); - int l = Math.min(buffer.size() - position, dst.remaining()); - buffer.getBytes(position, l, dst); - consume(l); + nextBuffer(); } } - public void readPayload(byte[] dst) throws IOException { + public void readPayload(byte[] dst) + throws IOException + { readPayload(dst, 0, dst.length); } + public byte[] readPayload(int length) + throws IOException + { + byte[] newArray = new byte[length]; + readPayload(newArray); + return newArray; + } + /** * Read up to len bytes of data into the destination array * @@ -1168,91 +1264,105 @@ public void readPayload(byte[] dst) throws IOException { * @param len the number of bytes to read * @throws IOException */ - public void readPayload(byte[] dst, int off, int len) throws IOException { - int writtenLen = 0; - while(writtenLen < len) { - if(!ensureBuffer()) - throw new EOFException(); - int l = Math.min(buffer.size() - position, len - writtenLen); - buffer.getBytes(position, dst, off + writtenLen, l); - consume(l); - writtenLen += l; - } + public void readPayload(byte[] dst, int off, int len) + throws IOException + { + // TODO optimize + readPayload(ByteBuffer.wrap(dst, off, len)); } - public MessageBuffer readPayloadAsReference(int length) throws IOException { - checkArgument(length >= 0); - if(!ensure(length)) - throw new EOFException(); - - MessageBuffer ref = buffer.slice(position, length); - position += length; - return ref; + public MessageBuffer readPayloadAsReference(int length) + throws IOException + { + int bufferRemaining = buffer.size() - position; + if (bufferRemaining >= length) { + MessageBuffer slice = buffer.slice(position, length); + position += length; + return slice; + } + MessageBuffer dst = MessageBuffer.allocate(length); + readPayload(dst.sliceAsByteBuffer()); + return dst; } - - private int readNextLength8() throws IOException { + private int readNextLength8() + throws IOException + { byte u8 = readByte(); return u8 & 0xff; } - private int readNextLength16() throws IOException { + private int readNextLength16() + throws IOException + { short u16 = readShort(); return u16 & 0xffff; } - private int readNextLength32() throws IOException { + private int readNextLength32() + throws IOException + { int u32 = readInt(); - if(u32 < 0) { + if (u32 < 0) { throw overflowU32Size(u32); } return u32; } @Override - public void close() throws IOException { + public void close() + throws IOException + { + buffer = EMPTY_BUFFER; + position = 0; in.close(); } - private static MessageIntegerOverflowException overflowU8(byte u8) { + private static MessageIntegerOverflowException overflowU8(byte u8) + { BigInteger bi = BigInteger.valueOf((long) (u8 & 0xff)); return new MessageIntegerOverflowException(bi); } - private static MessageIntegerOverflowException overflowU16(short u16) { + private static MessageIntegerOverflowException overflowU16(short u16) + { BigInteger bi = BigInteger.valueOf((long) (u16 & 0xffff)); return new MessageIntegerOverflowException(bi); } - private static MessageIntegerOverflowException overflowU32(int u32) { + private static MessageIntegerOverflowException overflowU32(int u32) + { BigInteger bi = BigInteger.valueOf((long) (u32 & 0x7fffffff) + 0x80000000L); return new MessageIntegerOverflowException(bi); } - private static MessageIntegerOverflowException overflowU64(long u64) { + private static MessageIntegerOverflowException overflowU64(long u64) + { BigInteger bi = BigInteger.valueOf(u64 + Long.MAX_VALUE + 1L).setBit(63); return new MessageIntegerOverflowException(bi); } - private static MessageIntegerOverflowException overflowI16(short i16) { + private static MessageIntegerOverflowException overflowI16(short i16) + { BigInteger bi = BigInteger.valueOf((long) i16); return new MessageIntegerOverflowException(bi); } - private static MessageIntegerOverflowException overflowI32(int i32) { + private static MessageIntegerOverflowException overflowI32(int i32) + { BigInteger bi = BigInteger.valueOf((long) i32); return new MessageIntegerOverflowException(bi); } - private static MessageIntegerOverflowException overflowI64(long i64) { + private static MessageIntegerOverflowException overflowI64(long i64) + { BigInteger bi = BigInteger.valueOf(i64); return new MessageIntegerOverflowException(bi); } - private static MessageSizeException overflowU32Size(int u32) { + private static MessageSizeException overflowU32Size(int u32) + { long lv = (long) (u32 & 0x7fffffff) + 0x80000000L; return new MessageSizeException(lv); } - - } diff --git a/msgpack-core/src/main/java/org/msgpack/core/NonBlockingMessageUnpacker.java b/msgpack-core/src/main/java/org/msgpack/core/NonBlockingMessageUnpacker.java deleted file mode 100644 index 7ec3122be..000000000 --- a/msgpack-core/src/main/java/org/msgpack/core/NonBlockingMessageUnpacker.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.msgpack.core; - -/** - * MessageUnpacker implementation that supports event-driven I/O, which - * is necessary to implement an efficient RPC server that receives MessagePack data. - */ -public class NonBlockingMessageUnpacker { - - // TODO Impl - -} diff --git a/msgpack-core/src/main/java/org/msgpack/core/NumberUtil.java b/msgpack-core/src/main/java/org/msgpack/core/NumberUtil.java deleted file mode 100644 index 578833b43..000000000 --- a/msgpack-core/src/main/java/org/msgpack/core/NumberUtil.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.msgpack.core; - -import java.math.BigInteger; - -/** - * Utilities for numbers - */ -public class NumberUtil { - - private static final BigInteger BI_BYTE_MIN = BigInteger.valueOf(Byte.MIN_VALUE); - private static final BigInteger BI_BYTE_MAX = BigInteger.valueOf(Byte.MAX_VALUE); - private static final BigInteger BI_SHORT_MIN = BigInteger.valueOf(Short.MIN_VALUE); - private static final BigInteger BI_SHORT_MAX = BigInteger.valueOf(Short.MAX_VALUE); - private static final BigInteger BI_INT_MIN = BigInteger.valueOf(Integer.MIN_VALUE); - private static final BigInteger BI_INT_MAX = BigInteger.valueOf(Integer.MAX_VALUE); - private static final BigInteger BI_LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE); - private static final BigInteger BI_LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE); - - public static class LongUtil { - - public static boolean isValidByte(long v) { - return Byte.MIN_VALUE <= v && v <= Byte.MAX_VALUE; - } - - public static boolean isValidByte(BigInteger v) { - return v.compareTo(BI_BYTE_MIN) >= 0 && v.compareTo(BI_BYTE_MAX) <= 0; - } - - public static boolean isValidShort(long v) { - return Short.MIN_VALUE <= v && v <= Short.MAX_VALUE; - } - - public static boolean isValidShort(BigInteger v) { - return v.compareTo(BI_SHORT_MIN) >= 0 && v.compareTo(BI_SHORT_MAX) <= 0; - } - - public static boolean isValidInt(long v) { - return Integer.MIN_VALUE <= v && v <= Integer.MAX_VALUE; - } - public static boolean isValidInt(BigInteger v) { - return v.compareTo(BI_INT_MIN) >= 0 && v.compareTo(BI_INT_MAX) <= 0; - } - - public static boolean isValidLong(BigInteger v) { - return v.compareTo(BI_LONG_MIN) >= 0 && v.compareTo(BI_LONG_MAX) <= 0; - } - } - - - - -} diff --git a/msgpack-core/src/main/java/org/msgpack/core/Preconditions.java b/msgpack-core/src/main/java/org/msgpack/core/Preconditions.java index b2b72f1c4..e44d97d15 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/Preconditions.java +++ b/msgpack-core/src/main/java/org/msgpack/core/Preconditions.java @@ -1,3 +1,19 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + /* * Copyright (C) 2007 The Guava Authors * @@ -15,7 +31,6 @@ */ package org.msgpack.core; - import org.msgpack.core.annotations.Nullable; import org.msgpack.core.annotations.VisibleForTesting; @@ -56,7 +71,8 @@ * @author Kevin Bourrillion * @since 2.0 (imported from Google Collections Library) */ -public final class Preconditions { +public final class Preconditions +{ private Preconditions() {} /** @@ -66,7 +82,8 @@ private Preconditions() {} * @param expression a boolean expression * @throws IllegalArgumentException if {@code expression} is false */ - public static void checkArgument(boolean expression) { + public static void checkArgument(boolean expression) + { if (!expression) { throw new IllegalArgumentException(); } @@ -78,11 +95,12 @@ public static void checkArgument(boolean expression) { * * @param expression a boolean expression * @param errorMessage the exception message to use if the check fails; will - * be converted to a string using {@link String#valueOf(Object)} + * be converted to a string using {@link String#valueOf(Object)} * @throws IllegalArgumentException if {@code expression} is false */ public static void checkArgument( - boolean expression, @Nullable Object errorMessage) { + boolean expression, @Nullable Object errorMessage) + { if (!expression) { throw new IllegalArgumentException(String.valueOf(errorMessage)); } @@ -94,22 +112,23 @@ public static void checkArgument( * * @param expression a boolean expression * @param errorMessageTemplate a template for the exception message should the - * check fail. The message is formed by replacing each {@code %s} - * placeholder in the template with an argument. These are matched by - * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. - * Unmatched arguments will be appended to the formatted message in square - * braces. Unmatched placeholders will be left as-is. + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. * @param errorMessageArgs the arguments to be substituted into the message - * template. Arguments are converted to strings using - * {@link String#valueOf(Object)}. + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. * @throws IllegalArgumentException if {@code expression} is false * @throws NullPointerException if the check fails and either {@code - * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let - * this happen) + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) */ public static void checkArgument(boolean expression, - @Nullable String errorMessageTemplate, - @Nullable Object... errorMessageArgs) { + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) + { if (!expression) { throw new IllegalArgumentException( format(errorMessageTemplate, errorMessageArgs)); @@ -123,7 +142,8 @@ public static void checkArgument(boolean expression, * @param expression a boolean expression * @throws IllegalStateException if {@code expression} is false */ - public static void checkState(boolean expression) { + public static void checkState(boolean expression) + { if (!expression) { throw new IllegalStateException(); } @@ -135,11 +155,12 @@ public static void checkState(boolean expression) { * * @param expression a boolean expression * @param errorMessage the exception message to use if the check fails; will - * be converted to a string using {@link String#valueOf(Object)} + * be converted to a string using {@link String#valueOf(Object)} * @throws IllegalStateException if {@code expression} is false */ public static void checkState( - boolean expression, @Nullable Object errorMessage) { + boolean expression, @Nullable Object errorMessage) + { if (!expression) { throw new IllegalStateException(String.valueOf(errorMessage)); } @@ -151,22 +172,23 @@ public static void checkState( * * @param expression a boolean expression * @param errorMessageTemplate a template for the exception message should the - * check fail. The message is formed by replacing each {@code %s} - * placeholder in the template with an argument. These are matched by - * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. - * Unmatched arguments will be appended to the formatted message in square - * braces. Unmatched placeholders will be left as-is. + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. * @param errorMessageArgs the arguments to be substituted into the message - * template. Arguments are converted to strings using - * {@link String#valueOf(Object)}. + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. * @throws IllegalStateException if {@code expression} is false * @throws NullPointerException if the check fails and either {@code - * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let - * this happen) + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) */ public static void checkState(boolean expression, - @Nullable String errorMessageTemplate, - @Nullable Object... errorMessageArgs) { + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) + { if (!expression) { throw new IllegalStateException( format(errorMessageTemplate, errorMessageArgs)); @@ -181,7 +203,8 @@ public static void checkState(boolean expression, * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ - public static T checkNotNull(T reference) { + public static T checkNotNull(T reference) + { if (reference == null) { throw new NullPointerException(); } @@ -194,11 +217,12 @@ public static T checkNotNull(T reference) { * * @param reference an object reference * @param errorMessage the exception message to use if the check fails; will - * be converted to a string using {@link String#valueOf(Object)} + * be converted to a string using {@link String#valueOf(Object)} * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ - public static T checkNotNull(T reference, @Nullable Object errorMessage) { + public static T checkNotNull(T reference, @Nullable Object errorMessage) + { if (reference == null) { throw new NullPointerException(String.valueOf(errorMessage)); } @@ -211,20 +235,21 @@ public static T checkNotNull(T reference, @Nullable Object errorMessage) { * * @param reference an object reference * @param errorMessageTemplate a template for the exception message should the - * check fail. The message is formed by replacing each {@code %s} - * placeholder in the template with an argument. These are matched by - * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. - * Unmatched arguments will be appended to the formatted message in square - * braces. Unmatched placeholders will be left as-is. + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. * @param errorMessageArgs the arguments to be substituted into the message - * template. Arguments are converted to strings using - * {@link String#valueOf(Object)}. + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ public static T checkNotNull(T reference, - @Nullable String errorMessageTemplate, - @Nullable Object... errorMessageArgs) { + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) + { if (reference == null) { // If either of these parameters is null, the right thing happens anyway throw new NullPointerException( @@ -268,14 +293,15 @@ public static T checkNotNull(T reference, * inclusive, to {@code size}, exclusive. * * @param index a user-supplied index identifying an element of an array, list - * or string + * or string * @param size the size of that array, list or string * @return the value of {@code index} * @throws IndexOutOfBoundsException if {@code index} is negative or is not - * less than {@code size} + * less than {@code size} * @throws IllegalArgumentException if {@code size} is negative */ - public static int checkElementIndex(int index, int size) { + public static int checkElementIndex(int index, int size) + { return checkElementIndex(index, size, "index"); } @@ -285,16 +311,17 @@ public static int checkElementIndex(int index, int size) { * inclusive, to {@code size}, exclusive. * * @param index a user-supplied index identifying an element of an array, list - * or string + * or string * @param size the size of that array, list or string * @param desc the text to use to describe this index in an error message * @return the value of {@code index} * @throws IndexOutOfBoundsException if {@code index} is negative or is not - * less than {@code size} + * less than {@code size} * @throws IllegalArgumentException if {@code size} is negative */ public static int checkElementIndex( - int index, int size, @Nullable String desc) { + int index, int size, @Nullable String desc) + { // Carefully optimized for execution by hotspot (explanatory comment above) if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); @@ -302,12 +329,15 @@ public static int checkElementIndex( return index; } - private static String badElementIndex(int index, int size, String desc) { + private static String badElementIndex(int index, int size, String desc) + { if (index < 0) { return format("%s (%s) must not be negative", desc, index); - } else if (size < 0) { + } + else if (size < 0) { throw new IllegalArgumentException("negative size: " + size); - } else { // index >= size + } + else { // index >= size return format("%s (%s) must be less than size (%s)", desc, index, size); } } @@ -318,14 +348,15 @@ private static String badElementIndex(int index, int size, String desc) { * to {@code size}, inclusive. * * @param index a user-supplied index identifying a position in an array, list - * or string + * or string * @param size the size of that array, list or string * @return the value of {@code index} * @throws IndexOutOfBoundsException if {@code index} is negative or is - * greater than {@code size} + * greater than {@code size} * @throws IllegalArgumentException if {@code size} is negative */ - public static int checkPositionIndex(int index, int size) { + public static int checkPositionIndex(int index, int size) + { return checkPositionIndex(index, size, "index"); } @@ -335,16 +366,17 @@ public static int checkPositionIndex(int index, int size) { * to {@code size}, inclusive. * * @param index a user-supplied index identifying a position in an array, list - * or string + * or string * @param size the size of that array, list or string * @param desc the text to use to describe this index in an error message * @return the value of {@code index} * @throws IndexOutOfBoundsException if {@code index} is negative or is - * greater than {@code size} + * greater than {@code size} * @throws IllegalArgumentException if {@code size} is negative */ public static int checkPositionIndex( - int index, int size, @Nullable String desc) { + int index, int size, @Nullable String desc) + { // Carefully optimized for execution by hotspot (explanatory comment above) if (index < 0 || index > size) { throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); @@ -352,12 +384,15 @@ public static int checkPositionIndex( return index; } - private static String badPositionIndex(int index, int size, String desc) { + private static String badPositionIndex(int index, int size, String desc) + { if (index < 0) { return format("%s (%s) must not be negative", desc, index); - } else if (size < 0) { + } + else if (size < 0) { throw new IllegalArgumentException("negative size: " + size); - } else { // index > size + } + else { // index > size return format("%s (%s) must not be greater than size (%s)", desc, index, size); } @@ -369,22 +404,24 @@ private static String badPositionIndex(int index, int size, String desc) { * position index may range from zero to {@code size}, inclusive. * * @param start a user-supplied index identifying a starting position in an - * array, list or string + * array, list or string * @param end a user-supplied index identifying a ending position in an array, - * list or string + * list or string * @param size the size of that array, list or string * @throws IndexOutOfBoundsException if either index is negative or is - * greater than {@code size}, or if {@code end} is less than {@code start} + * greater than {@code size}, or if {@code end} is less than {@code start} * @throws IllegalArgumentException if {@code size} is negative */ - public static void checkPositionIndexes(int start, int end, int size) { + public static void checkPositionIndexes(int start, int end, int size) + { // Carefully optimized for execution by hotspot (explanatory comment above) if (start < 0 || end < start || end > size) { throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); } } - private static String badPositionIndexes(int start, int end, int size) { + private static String badPositionIndexes(int start, int end, int size) + { if (start < 0 || start > size) { return badPositionIndex(start, size, "start index"); } @@ -403,14 +440,15 @@ private static String badPositionIndexes(int start, int end, int size) { * be appended to the end of the formatted message in square braces. * * @param template a non-null string containing 0 or more {@code %s} - * placeholders. + * placeholders. * @param args the arguments to be substituted into the message - * template. Arguments are converted to strings using - * {@link String#valueOf(Object)}. Arguments can be null. + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. Arguments can be null. */ @VisibleForTesting static String format(String template, - @Nullable Object... args) { + @Nullable Object... args) + { template = String.valueOf(template); // null -> "null" // start substituting the arguments into the '%s' placeholders @@ -442,4 +480,4 @@ static String format(String template, return builder.toString(); } -} \ No newline at end of file +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/annotations/Insecure.java b/msgpack-core/src/main/java/org/msgpack/core/annotations/Insecure.java deleted file mode 100644 index 11d0fade3..000000000 --- a/msgpack-core/src/main/java/org/msgpack/core/annotations/Insecure.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.msgpack.core.annotations; - -/** - * Annotates a code which must be used carefully. - */ -public @interface Insecure { -} diff --git a/msgpack-core/src/main/java/org/msgpack/core/annotations/Nullable.java b/msgpack-core/src/main/java/org/msgpack/core/annotations/Nullable.java index 0871b6ec1..9e7c94fab 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/annotations/Nullable.java +++ b/msgpack-core/src/main/java/org/msgpack/core/annotations/Nullable.java @@ -1,7 +1,23 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.annotations; /** * Annotates a field which can be null */ -public @interface Nullable { +public @interface Nullable +{ } diff --git a/msgpack-core/src/main/java/org/msgpack/core/annotations/VisibleForTesting.java b/msgpack-core/src/main/java/org/msgpack/core/annotations/VisibleForTesting.java index 3c9876851..ddd4aa4c9 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/annotations/VisibleForTesting.java +++ b/msgpack-core/src/main/java/org/msgpack/core/annotations/VisibleForTesting.java @@ -1,7 +1,23 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.annotations; /** * Annotates a code which is used only for testing. */ -public @interface VisibleForTesting { +public @interface VisibleForTesting +{ } diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/ArrayBufferInput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/ArrayBufferInput.java index 2a8661fcc..36f0ad3c1 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/ArrayBufferInput.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/ArrayBufferInput.java @@ -1,59 +1,87 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; import java.io.IOException; -import static org.msgpack.core.Preconditions.*; + +import static org.msgpack.core.Preconditions.checkNotNull; /** * MessageBufferInput adapter for byte arrays */ -public class ArrayBufferInput implements MessageBufferInput { - +public class ArrayBufferInput + implements MessageBufferInput +{ private MessageBuffer buffer; private boolean isRead = false; - public ArrayBufferInput(MessageBuffer buf) { + public ArrayBufferInput(MessageBuffer buf) + { this.buffer = checkNotNull(buf, "input buffer is null"); } - public ArrayBufferInput(byte[] arr) { + public ArrayBufferInput(byte[] arr) + { this(arr, 0, arr.length); } - public ArrayBufferInput(byte[] arr, int offset, int length) { - checkArgument(offset + length <= arr.length); - this.buffer = MessageBuffer.wrap(checkNotNull(arr, "input array is null")).slice(offset, length); + public ArrayBufferInput(byte[] arr, int offset, int length) + { + this(MessageBuffer.wrap(checkNotNull(arr, "input array is null")).slice(offset, length)); } /** * Reset buffer. This method doesn't close the old resource. + * * @param buf new buffer * @return the old resource */ - public MessageBuffer reset(MessageBuffer buf) { + public MessageBuffer reset(MessageBuffer buf) + { MessageBuffer old = this.buffer; this.buffer = buf; this.isRead = false; return old; } - public void reset(byte[] arr) { + public void reset(byte[] arr) + { reset(MessageBuffer.wrap(checkNotNull(arr, "input array is null"))); } - public void reset(byte[] arr, int offset, int len) { + public void reset(byte[] arr, int offset, int len) + { reset(MessageBuffer.wrap(checkNotNull(arr, "input array is null")).slice(offset, len)); } @Override - public MessageBuffer next() throws IOException { - if(isRead) + public MessageBuffer next() + throws IOException + { + if (isRead) { return null; + } isRead = true; return buffer; } @Override - public void close() throws IOException { + public void close() + throws IOException + { buffer = null; isRead = false; } diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/ArrayBufferOutput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/ArrayBufferOutput.java new file mode 100644 index 000000000..186a579af --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/ArrayBufferOutput.java @@ -0,0 +1,136 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.core.buffer; + +import java.util.List; +import java.util.ArrayList; + +/** + * MessageBufferOutput adapter that packs data into list of byte arrays. + */ +public class ArrayBufferOutput + implements MessageBufferOutput +{ + private List list; + private MessageBuffer lastBuffer; + private int bufferSize; + + public ArrayBufferOutput() + { + this(8192); + } + + public ArrayBufferOutput(int bufferSize) + { + this.bufferSize = bufferSize; + this.list = new ArrayList(); + } + + public int getSize() + { + int size = 0; + for (MessageBuffer buffer : list) { + size += buffer.size(); + } + return size; + } + + public byte[] toByteArray() + { + byte[] data = new byte[getSize()]; + int off = 0; + for (MessageBuffer buffer : list) { + buffer.getBytes(0, data, off, buffer.size()); + off += buffer.size(); + } + return data; + } + + public MessageBuffer toMessageBuffer() + { + if (list.size() == 1) { + return list.get(0); + } + else if (list.isEmpty()) { + return MessageBuffer.allocate(0); + } + else { + return MessageBuffer.wrap(toByteArray()); + } + } + + public List toBufferList() + { + return new ArrayList(list); + } + + /** + * Clears the internal buffers + */ + public void clear() + { + list.clear(); + } + + @Override + public MessageBuffer next(int mimimumSize) + { + if (lastBuffer != null && lastBuffer.size() > mimimumSize) { + return lastBuffer; + } + else { + int size = Math.max(bufferSize, mimimumSize); + MessageBuffer buffer = MessageBuffer.allocate(size); + lastBuffer = buffer; + return buffer; + } + } + + @Override + public void writeBuffer(int length) + { + list.add(lastBuffer.slice(0, length)); + if (lastBuffer.size() - length > bufferSize / 4) { + lastBuffer = lastBuffer.slice(length, lastBuffer.size() - length); + } + else { + lastBuffer = null; + } + } + + @Override + public void write(byte[] buffer, int offset, int length) + { + MessageBuffer copy = MessageBuffer.allocate(length); + copy.putBytes(0, buffer, offset, length); + list.add(copy); + } + + @Override + public void add(byte[] buffer, int offset, int length) + { + MessageBuffer wrapped = MessageBuffer.wrap(buffer, offset, length); + list.add(wrapped); + } + + @Override + public void close() + { } + + @Override + public void flush() + { } +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/ByteBufferInput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/ByteBufferInput.java deleted file mode 100644 index 3335b2bb2..000000000 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/ByteBufferInput.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.msgpack.core.buffer; - -import java.io.IOException; -import java.nio.ByteBuffer; -import static org.msgpack.core.Preconditions.*; - -/** - * {@link MessageBufferInput} adapter for {@link java.nio.ByteBuffer} - */ -public class ByteBufferInput implements MessageBufferInput { - - private ByteBuffer input; - private boolean isRead = false; - - public ByteBufferInput(ByteBuffer input) { - this.input = checkNotNull(input, "input ByteBuffer is null"); - } - - /** - * Reset buffer. This method doesn't close the old resource. - * @param input new buffer - * @return the old resource - */ - public ByteBuffer reset(ByteBuffer input) { - ByteBuffer old = this.input; - this.input = input; - isRead = false; - return old; - } - - @Override - public MessageBuffer next() throws IOException { - if(isRead) - return null; - - isRead = true; - return MessageBuffer.wrap(input); - } - - - @Override - public void close() throws IOException { - // Nothing to do - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/ChannelBufferInput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/ChannelBufferInput.java index 409ea4cb3..f00cb0c30 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/ChannelBufferInput.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/ChannelBufferInput.java @@ -1,63 +1,81 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; -import static org.msgpack.core.Preconditions.*; +import static org.msgpack.core.Preconditions.checkArgument; +import static org.msgpack.core.Preconditions.checkNotNull; /** * {@link MessageBufferInput} adapter for {@link java.nio.channels.ReadableByteChannel} */ -public class ChannelBufferInput implements MessageBufferInput { - +public class ChannelBufferInput + implements MessageBufferInput +{ private ReadableByteChannel channel; - private boolean reachedEOF = false; - private final int bufferSize; + private final MessageBuffer buffer; - public ChannelBufferInput(ReadableByteChannel channel) { + public ChannelBufferInput(ReadableByteChannel channel) + { this(channel, 8192); } - public ChannelBufferInput(ReadableByteChannel channel, int bufferSize) { + public ChannelBufferInput(ReadableByteChannel channel, int bufferSize) + { this.channel = checkNotNull(channel, "input channel is null"); checkArgument(bufferSize > 0, "buffer size must be > 0: " + bufferSize); - this.bufferSize = bufferSize; + this.buffer = MessageBuffer.allocate(bufferSize); } /** * Reset channel. This method doesn't close the old resource. + * * @param channel new channel * @return the old resource */ - public ReadableByteChannel reset(ReadableByteChannel channel) throws IOException { + public ReadableByteChannel reset(ReadableByteChannel channel) + throws IOException + { ReadableByteChannel old = this.channel; this.channel = channel; - this.reachedEOF = false; return old; } @Override - public MessageBuffer next() throws IOException { - - if(reachedEOF) { - return null; - } - - MessageBuffer m = MessageBuffer.newBuffer(bufferSize); - ByteBuffer b = m.toByteBuffer(); - while(!reachedEOF && b.remaining() > 0) { + public MessageBuffer next() + throws IOException + { + ByteBuffer b = buffer.sliceAsByteBuffer(); + while (b.remaining() > 0) { int ret = channel.read(b); - if(ret == -1) { - reachedEOF = true; + if (ret == -1) { + break; } } b.flip(); - return b.remaining() == 0 ? null : m.slice(0, b.limit()); + return b.remaining() == 0 ? null : buffer.slice(0, b.limit()); } @Override - public void close() throws IOException { + public void close() + throws IOException + { channel.close(); } } diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/ChannelBufferOutput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/ChannelBufferOutput.java index eaf0ebffa..32969f29a 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/ChannelBufferOutput.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/ChannelBufferOutput.java @@ -1,3 +1,18 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; import java.io.IOException; @@ -9,43 +24,83 @@ /** * {@link MessageBufferOutput} adapter for {@link java.nio.channels.WritableByteChannel} */ -public class ChannelBufferOutput implements MessageBufferOutput { - +public class ChannelBufferOutput + implements MessageBufferOutput +{ private WritableByteChannel channel; private MessageBuffer buffer; - public ChannelBufferOutput(WritableByteChannel channel) { + public ChannelBufferOutput(WritableByteChannel channel) + { + this(channel, 8192); + } + + public ChannelBufferOutput(WritableByteChannel channel, int bufferSize) + { this.channel = checkNotNull(channel, "output channel is null"); + this.buffer = MessageBuffer.allocate(bufferSize); } /** - * Reset channel. This method doesn't close the old resource. + * Reset channel. This method doesn't close the old channel. + * * @param channel new channel - * @return the old resource + * @return the old channel */ - public WritableByteChannel reset(WritableByteChannel channel) throws IOException { + public WritableByteChannel reset(WritableByteChannel channel) + throws IOException + { WritableByteChannel old = this.channel; this.channel = channel; return old; } - @Override - public MessageBuffer next(int bufferSize) throws IOException { - if(buffer == null || buffer.size() != bufferSize) { - buffer = MessageBuffer.newBuffer(bufferSize); + public MessageBuffer next(int mimimumSize) + throws IOException + { + if (buffer.size() < mimimumSize) { + buffer = MessageBuffer.allocate(mimimumSize); } return buffer; } @Override - public void flush(MessageBuffer buf) throws IOException { - ByteBuffer bb = buf.toByteBuffer(); - channel.write(bb); + public void writeBuffer(int length) + throws IOException + { + ByteBuffer bb = buffer.sliceAsByteBuffer(0, length); + while (bb.hasRemaining()) { + channel.write(bb); + } + } + + @Override + public void write(byte[] buffer, int offset, int length) + throws IOException + { + ByteBuffer bb = ByteBuffer.wrap(buffer, offset, length); + while (bb.hasRemaining()) { + channel.write(bb); + } } @Override - public void close() throws IOException { + public void add(byte[] buffer, int offset, int length) + throws IOException + { + write(buffer, offset, length); + } + + @Override + public void close() + throws IOException + { channel.close(); } + + @Override + public void flush() + throws IOException + { } } diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/DirectBufferAccess.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/DirectBufferAccess.java new file mode 100644 index 000000000..ab86061d3 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/DirectBufferAccess.java @@ -0,0 +1,158 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.core.buffer; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; + +/** + * Wraps the difference of access methods to DirectBuffers between Android and others. + */ +class DirectBufferAccess +{ + private DirectBufferAccess() + {} + + enum DirectBufferConstructorType + { + ARGS_LONG_INT_REF, + ARGS_LONG_INT, + ARGS_INT_INT, + ARGS_MB_INT_INT + } + + static Method mGetAddress; + static Method mCleaner; + static Method mClean; + + // TODO We should use MethodHandle for efficiency, but it is not available in JDK6 + static Constructor byteBufferConstructor; + static Class directByteBufferClass; + static DirectBufferConstructorType directBufferConstructorType; + static Method memoryBlockWrapFromJni; + + static { + try { + // Find the hidden constructor for DirectByteBuffer + directByteBufferClass = ClassLoader.getSystemClassLoader().loadClass("java.nio.DirectByteBuffer"); + Constructor directByteBufferConstructor = null; + DirectBufferConstructorType constructorType = null; + Method mbWrap = null; + try { + // TODO We should use MethodHandle for Java7, which can avoid the cost of boxing with JIT optimization + directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(long.class, int.class, Object.class); + constructorType = DirectBufferConstructorType.ARGS_LONG_INT_REF; + } + catch (NoSuchMethodException e0) { + try { + // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/nio/DirectByteBuffer.java + // DirectByteBuffer(long address, int capacity) + directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(long.class, int.class); + constructorType = DirectBufferConstructorType.ARGS_LONG_INT; + } + catch (NoSuchMethodException e1) { + try { + directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(int.class, int.class); + constructorType = DirectBufferConstructorType.ARGS_INT_INT; + } + catch (NoSuchMethodException e2) { + Class aClass = Class.forName("java.nio.MemoryBlock"); + mbWrap = aClass.getDeclaredMethod("wrapFromJni", int.class, long.class); + mbWrap.setAccessible(true); + directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(aClass, int.class, int.class); + constructorType = DirectBufferConstructorType.ARGS_MB_INT_INT; + } + } + } + + byteBufferConstructor = directByteBufferConstructor; + directBufferConstructorType = constructorType; + memoryBlockWrapFromJni = mbWrap; + + if (byteBufferConstructor == null) { + throw new RuntimeException("Constructor of DirectByteBuffer is not found"); + } + byteBufferConstructor.setAccessible(true); + + mGetAddress = directByteBufferClass.getDeclaredMethod("address"); + mGetAddress.setAccessible(true); + + mCleaner = directByteBufferClass.getDeclaredMethod("cleaner"); + mCleaner.setAccessible(true); + + mClean = mCleaner.getReturnType().getDeclaredMethod("clean"); + mClean.setAccessible(true); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + static long getAddress(Object base) + { + try { + return (Long) mGetAddress.invoke(base); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + static void clean(Object base) + { + try { + Object cleaner = mCleaner.invoke(base); + mClean.invoke(cleaner); + } + catch (Throwable e) { + throw new RuntimeException(e); + } + } + + static boolean isDirectByteBufferInstance(Object s) + { + return directByteBufferClass.isInstance(s); + } + + static ByteBuffer newByteBuffer(long address, int index, int length, ByteBuffer reference) + { + try { + switch (directBufferConstructorType) { + case ARGS_LONG_INT_REF: + return (ByteBuffer) byteBufferConstructor.newInstance(address + index, length, reference); + case ARGS_LONG_INT: + return (ByteBuffer) byteBufferConstructor.newInstance(address + index, length); + case ARGS_INT_INT: + return (ByteBuffer) byteBufferConstructor.newInstance((int) address + index, length); + case ARGS_MB_INT_INT: + return (ByteBuffer) byteBufferConstructor.newInstance( + memoryBlockWrapFromJni.invoke(null, address + index, length), + length, 0); + default: + throw new IllegalStateException("Unexpected value"); + } + } + catch (Throwable e) { + // Convert checked exception to unchecked exception + throw new RuntimeException(e); + } + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/InputStreamBufferInput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/InputStreamBufferInput.java index 0e7e4626d..d605fec3a 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/InputStreamBufferInput.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/InputStreamBufferInput.java @@ -1,79 +1,88 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.channels.FileChannel; import static org.msgpack.core.Preconditions.checkNotNull; /** * {@link MessageBufferInput} adapter for {@link InputStream} */ -public class InputStreamBufferInput implements MessageBufferInput { - +public class InputStreamBufferInput + implements MessageBufferInput +{ private InputStream in; - private final int bufferSize; - private boolean reachedEOF = false; + private final byte[] buffer; - public static MessageBufferInput newBufferInput(InputStream in) { + public static MessageBufferInput newBufferInput(InputStream in) + { checkNotNull(in, "InputStream is null"); if (in instanceof FileInputStream) { - return new ChannelBufferInput(((FileInputStream) in).getChannel()); + FileChannel channel = ((FileInputStream) in).getChannel(); + if (channel != null) { + return new ChannelBufferInput(channel); + } } return new InputStreamBufferInput(in); } - public InputStreamBufferInput(InputStream in) { + public InputStreamBufferInput(InputStream in) + { this(in, 8192); } - public InputStreamBufferInput(InputStream in, int bufferSize) { + public InputStreamBufferInput(InputStream in, int bufferSize) + { this.in = checkNotNull(in, "input is null"); - this.bufferSize = bufferSize; + this.buffer = new byte[bufferSize]; } /** * Reset Stream. This method doesn't close the old resource. + * * @param in new stream * @return the old resource */ - public InputStream reset(InputStream in) throws IOException { + public InputStream reset(InputStream in) + throws IOException + { InputStream old = this.in; this.in = in; - reachedEOF = false; return old; } - @Override - public MessageBuffer next() throws IOException { - if(reachedEOF) + public MessageBuffer next() + throws IOException + { + int readLen = in.read(buffer); + if (readLen == -1) { return null; - - byte[] buffer = null; - int cursor = 0; - while(!reachedEOF && cursor < bufferSize) { - if(buffer == null) - buffer = new byte[bufferSize]; - - int readLen = in.read(buffer, cursor, bufferSize - cursor); - if(readLen == -1) { - reachedEOF = true; - break; - } - cursor += readLen; } - - return buffer == null ? null : MessageBuffer.wrap(buffer).slice(0, cursor); + return MessageBuffer.wrap(buffer, 0, readLen); } @Override - public void close() throws IOException { - try { - in.close(); - } - finally { - - } + public void close() + throws IOException + { + in.close(); } } diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBuffer.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBuffer.java index 3dffa9dd1..d4d5f2238 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBuffer.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBuffer.java @@ -1,16 +1,30 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; -import org.msgpack.core.annotations.Insecure; import sun.misc.Unsafe; import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; -import static org.msgpack.core.Preconditions.*; +import static org.msgpack.core.Preconditions.checkArgument; +import static org.msgpack.core.Preconditions.checkNotNull; /** * MessageBuffer class is an abstraction of memory for reading/writing message packed data. @@ -20,220 +34,125 @@ * implemented without using any interface method that produces invokeinterface call in JVM. * Compared to invokevirtual, invokeinterface is 30% slower in general because it needs to find a target function from the table. */ -public class MessageBuffer { - +public class MessageBuffer +{ + static final boolean isUniversalBuffer; static final Unsafe unsafe; - // TODO We should use MethodHandle for efficiency, but it is not available in JDK6 - static final Constructor byteBufferConstructor; - static final Class directByteBufferClass; - static final DirectBufferConstructorType directBufferConstructorType; - static final Method memoryBlockWrapFromJni; + /** + * Reference to MessageBuffer Constructors + */ + private static final Constructor mbArrConstructor; + + /** + * The offset from the object memory header to its byte array data + */ static final int ARRAY_BYTE_BASE_OFFSET; - static final int ARRAY_BYTE_INDEX_SCALE; - enum DirectBufferConstructorType { - ARGS_LONG_INT_REF, - ARGS_LONG_INT, - ARGS_INT_INT, - ARGS_MB_INT_INT - } + private static final String UNIVERSAL_MESSAGE_BUFFER = "org.msgpack.core.buffer.MessageBufferU"; + private static final String BIGENDIAN_MESSAGE_BUFFER = "org.msgpack.core.buffer.MessageBufferBE"; + private static final String DEFAULT_MESSAGE_BUFFER = "org.msgpack.core.buffer.MessageBuffer"; static { + boolean useUniversalBuffer = false; + Unsafe unsafeInstance = null; + int arrayByteBaseOffset = 16; + try { // Check java version String javaVersion = System.getProperty("java.specification.version", ""); int dotPos = javaVersion.indexOf('.'); boolean isJavaAtLeast7 = false; - if(dotPos == -1) { - isJavaAtLeast7 = false; - } - else { + if (dotPos != -1) { try { int major = Integer.parseInt(javaVersion.substring(0, dotPos)); int minor = Integer.parseInt(javaVersion.substring(dotPos + 1)); isJavaAtLeast7 = major > 1 || (major == 1 && minor >= 7); } - catch(NumberFormatException e) { + catch (NumberFormatException e) { e.printStackTrace(System.err); } } - // Detect android VM - boolean isAndroid = System.getProperty("java.runtime.name", "").toLowerCase().contains("android"); - // Fetch theUnsafe object for Orackle JDK and OpenJDK - Unsafe u; - if(!isAndroid) { - // Fetch theUnsafe object for Oracle JDK and OpenJDK - Field field = Unsafe.class.getDeclaredField("theUnsafe"); - field.setAccessible(true); - unsafe = (Unsafe) field.get(null); - } - else { - // Workaround for creating an Unsafe instance for Android OS - Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructor(); - unsafeConstructor.setAccessible(true); - unsafe = unsafeConstructor.newInstance(); - } - if(unsafe == null) { - throw new RuntimeException("Unsafe is unavailable"); - } - - ARRAY_BYTE_BASE_OFFSET = unsafe.arrayBaseOffset(byte[].class); - ARRAY_BYTE_INDEX_SCALE = unsafe.arrayIndexScale(byte[].class); - - // Make sure the VM thinks bytes are only one byte wide - if(ARRAY_BYTE_INDEX_SCALE != 1) { - throw new IllegalStateException("Byte array index scale must be 1, but is " + ARRAY_BYTE_INDEX_SCALE); - } - - // Find the hidden constructor for DirectByteBuffer - directByteBufferClass = ClassLoader.getSystemClassLoader().loadClass("java.nio.DirectByteBuffer"); - Constructor directByteBufferConstructor = null; - DirectBufferConstructorType constructorType = null; - Method mbWrap = null; + boolean hasUnsafe = false; try { - // TODO We should use MethodHandle for Java7, which can avoid the cost of boxing with JIT optimization - directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(long.class, int.class, Object.class); - constructorType = DirectBufferConstructorType.ARGS_LONG_INT_REF; + hasUnsafe = Class.forName("sun.misc.Unsafe") != null; } - catch(NoSuchMethodException e0) { - try { - // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/nio/DirectByteBuffer.java - // DirectByteBuffer(long address, int capacity) - directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(long.class, int.class); - constructorType = DirectBufferConstructorType.ARGS_LONG_INT; - } catch (NoSuchMethodException e1) { - try { - directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(int.class, int.class); - constructorType = DirectBufferConstructorType.ARGS_INT_INT; - } catch (NoSuchMethodException e2) { - Class aClass = Class.forName("java.nio.MemoryBlock"); - mbWrap = aClass.getDeclaredMethod("wrapFromJni", int.class, long.class); - mbWrap.setAccessible(true); - directByteBufferConstructor = directByteBufferClass.getDeclaredConstructor(aClass, int.class, int.class); - constructorType = DirectBufferConstructorType.ARGS_MB_INT_INT; - } - } - } - byteBufferConstructor = directByteBufferConstructor; - directBufferConstructorType = constructorType; - memoryBlockWrapFromJni = mbWrap; - if(byteBufferConstructor == null) - throw new RuntimeException("Constructor of DirectByteBuffer is not found"); - - byteBufferConstructor.setAccessible(true); - - // Check the endian of this CPU - boolean isLittleEndian = true; - byte[] a = new byte[8]; - // use Unsafe.putLong to an array since Unsafe.getByte is not available in Android - unsafe.putLong(a, (long) ARRAY_BYTE_BASE_OFFSET, 0x0102030405060708L); - switch(a[0]) { - case 0x01: - isLittleEndian = false; - break; - case 0x08: - isLittleEndian = true; - break; - default: - assert false; + catch (Exception e) { } - // We need to use reflection to find MessageBuffer implementation classes because - // importing these classes creates TypeProfile and adds some overhead to method calls. - String bufferClsName; - boolean useUniversalBuffer = Boolean.parseBoolean(System.getProperty("msgpack.universal-buffer", "false")); - if(!useUniversalBuffer && !isAndroid && isJavaAtLeast7) { - if(isLittleEndian) - bufferClsName = "org.msgpack.core.buffer.MessageBuffer"; - else - bufferClsName = "org.msgpack.core.buffer.MessageBufferBE"; - } - else { - bufferClsName = "org.msgpack.core.buffer.MessageBufferU"; - } + // Detect android VM + boolean isAndroid = System.getProperty("java.runtime.name", "").toLowerCase().contains("android"); - Class bufferCls = Class.forName(bufferClsName); - msgBufferClass = bufferCls; + // Is Google App Engine? + boolean isGAE = System.getProperty("com.google.appengine.runtime.version") != null; - Constructor mbArrCstr = bufferCls.getDeclaredConstructor(byte[].class); - mbArrCstr.setAccessible(true); - mbArrConstructor = mbArrCstr; + // For Java6, android and JVM that has no Unsafe class, use Universal MessageBuffer + useUniversalBuffer = + Boolean.parseBoolean(System.getProperty("msgpack.universal-buffer", "false")) + || isAndroid + || isGAE + || !isJavaAtLeast7 + || !hasUnsafe; - Constructor mbBBCstr = bufferCls.getDeclaredConstructor(ByteBuffer.class); - mbBBCstr.setAccessible(true); - mbBBConstructor = mbBBCstr; + if (!useUniversalBuffer) { + // Fetch theUnsafe object for Oracle and OpenJDK + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + unsafeInstance = (Unsafe) field.get(null); + if (unsafeInstance == null) { + throw new RuntimeException("Unsafe is unavailable"); + } + arrayByteBaseOffset = unsafeInstance.arrayBaseOffset(byte[].class); + int arrayByteIndexScale = unsafeInstance.arrayIndexScale(byte[].class); - // Requires Java7 - //newMsgBuffer = MethodHandles.lookup().unreflectConstructor(mbArrCstr).asType( - // MethodType.methodType(bufferCls, byte[].class) - //); + // Make sure the VM thinks bytes are only one byte wide + if (arrayByteIndexScale != 1) { + throw new IllegalStateException("Byte array index scale must be 1, but is " + arrayByteIndexScale); + } + } } - catch(Exception e) { + catch (Exception e) { e.printStackTrace(System.err); - throw new RuntimeException(e); + // Use MessageBufferU + useUniversalBuffer = true; } - } + finally { + // Initialize the static fields + unsafe = unsafeInstance; + ARRAY_BYTE_BASE_OFFSET = arrayByteBaseOffset; - /** - * Wraps the difference of access methods to DirectBuffers between Android and others. - */ - private static class DirectBufferAccess { - static Method mGetAddress; - static Method mCleaner; - static Method mClean; - - static { - try { - mGetAddress = directByteBufferClass.getDeclaredMethod("address"); - mGetAddress.setAccessible(true); - - mCleaner = directByteBufferClass.getDeclaredMethod("cleaner"); - mCleaner.setAccessible(true); - - mClean = mCleaner.getReturnType().getDeclaredMethod("clean"); - mClean.setAccessible(true); + // Switch MessageBuffer implementation according to the environment + isUniversalBuffer = useUniversalBuffer; + String bufferClsName; + if (isUniversalBuffer) { + bufferClsName = UNIVERSAL_MESSAGE_BUFFER; } - catch(NoSuchMethodException e) { - throw new RuntimeException(e); + else { + // Check the endian of this CPU + boolean isLittleEndian = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN; + bufferClsName = isLittleEndian ? DEFAULT_MESSAGE_BUFFER : BIGENDIAN_MESSAGE_BUFFER; } - } - static long getAddress(Object base) { try { - return (Long) mGetAddress.invoke(base); - } - catch(IllegalAccessException e) { - throw new RuntimeException(e); - } - catch(InvocationTargetException e) { - throw new RuntimeException(e); - } - } + // We need to use reflection here to find MessageBuffer implementation classes because + // importing these classes creates TypeProfile and adds some overhead to method calls. - static void clean(Object base) { - try { - Object cleaner = mCleaner.invoke(base); - mClean.invoke(cleaner); + // MessageBufferX (default, BE or U) class + Class bufferCls = Class.forName(bufferClsName); + + // MessageBufferX(byte[]) constructor + Constructor mbArrCstr = bufferCls.getDeclaredConstructor(byte[].class, int.class, int.class); + mbArrCstr.setAccessible(true); + mbArrConstructor = mbArrCstr; } - catch(Throwable e) { - throw new RuntimeException(e); + catch (Exception e) { + e.printStackTrace(System.err); + throw new RuntimeException(e); // No more fallback exists if MessageBuffer constructors are inaccessible } } } - /** - * MessageBuffer class to use. If this machine is big-endian, it uses MessageBufferBE, which overrides some methods in this class that translate endians. If not, uses MessageBuffer. - */ - private final static Class msgBufferClass; - - private final static Constructor mbArrConstructor; - private final static Constructor mbBBConstructor; - - // Requires Java7 - //private final static MethodHandle newMsgBuffer; - /** * Base object for resolving the relative address of the raw byte array. * If base == null, the address value is a raw memory address @@ -251,55 +170,19 @@ static void clean(Object base) { */ protected final int size; - /** - * Reference is used to hold a reference to an object that holds the underlying memory so that it cannot be - * released by the garbage collector. - */ - protected final ByteBuffer reference; - - // TODO life-time management of this buffer - //private AtomicInteger referenceCounter; - - - static MessageBuffer newOffHeapBuffer(int length) { - // This method is not available in Android OS - long address = unsafe.allocateMemory(length); - return new MessageBuffer(address, length); - } - - public static MessageBuffer newDirectBuffer(int length) { - ByteBuffer m = ByteBuffer.allocateDirect(length); - return newMessageBuffer(m); - } - - public static MessageBuffer newBuffer(int length) { - return newMessageBuffer(new byte[length]); + public static MessageBuffer allocate(int length) + { + return wrap(new byte[length]); } - public static MessageBuffer wrap(byte[] array) { - return newMessageBuffer(array); + public static MessageBuffer wrap(byte[] array) + { + return newMessageBuffer(array, 0, array.length); } - public static MessageBuffer wrap(ByteBuffer bb) { - return newMessageBuffer(bb).slice(bb.position(), bb.remaining()); - } - - /** - * Creates a new MessageBuffer instance backed by ByteBuffer - * - * @param bb - * @return - */ - private static MessageBuffer newMessageBuffer(ByteBuffer bb) { - checkNotNull(bb); - try { - // We need to use reflection to create MessageBuffer instances in order to prevent TypeProfile generation for getInt method. TypeProfile will be - // generated to resolve one of the method references when two or more classes overrides the method. - return (MessageBuffer) mbBBConstructor.newInstance(bb); - } - catch(Exception e) { - throw new RuntimeException(e); - } + public static MessageBuffer wrap(byte[] array, int offset, int length) + { + return newMessageBuffer(array, offset, length); } /** @@ -308,84 +191,47 @@ private static MessageBuffer newMessageBuffer(ByteBuffer bb) { * @param arr * @return */ - private static MessageBuffer newMessageBuffer(byte[] arr) { + private static MessageBuffer newMessageBuffer(byte[] arr, int off, int len) + { checkNotNull(arr); try { - return (MessageBuffer) mbArrConstructor.newInstance(arr); + return (MessageBuffer) mbArrConstructor.newInstance(arr, off, len); } - catch(Throwable e) { + catch (Throwable e) { throw new RuntimeException(e); } } - public static void releaseBuffer(MessageBuffer buffer) { - if(buffer.base instanceof byte[]) { + public static void releaseBuffer(MessageBuffer buffer) + { + if (isUniversalBuffer || buffer.base instanceof byte[]) { // We have nothing to do. Wait until the garbage-collector collects this array object } - else if(directByteBufferClass.isInstance(buffer.base)) { - DirectBufferAccess.clean(buffer.base); - } else { // Maybe cannot reach here unsafe.freeMemory(buffer.address); } } - /** - * Create a MessageBuffer instance from a given memory address and length - * - * @param address - * @param length - */ - MessageBuffer(long address, int length) { - this.base = null; - this.address = address; - this.size = length; - this.reference = null; - } - - /** - * Create a MessageBuffer instance from a given ByteBuffer instance - * - * @param bb - */ - MessageBuffer(ByteBuffer bb) { - if(bb.isDirect()) { - // Direct buffer or off-heap memory - this.base = null; - this.address = DirectBufferAccess.getAddress(bb); - this.size = bb.capacity(); - this.reference = bb; - } - else if(bb.hasArray()) { - this.base = bb.array(); - this.address = ARRAY_BYTE_BASE_OFFSET; - this.size = bb.array().length; - this.reference = null; - } - else { - throw new IllegalArgumentException("Only the array-backed ByteBuffer or DirectBuffer are supported"); - } - } - - /** * Create a MessageBuffer instance from an java heap array * * @param arr + * @param offset + * @param length */ - MessageBuffer(byte[] arr) { + MessageBuffer(byte[] arr, int offset, int length) + { this.base = arr; - this.address = ARRAY_BYTE_BASE_OFFSET; - this.size = arr.length; - this.reference = null; + this.address = ARRAY_BYTE_BASE_OFFSET + offset; + this.size = length; } - MessageBuffer(Object base, long address, int length, ByteBuffer reference) { + protected MessageBuffer(Object base, long address, int length) + { this.base = base; this.address = address; this.size = length; - this.reference = reference; } /** @@ -393,29 +239,35 @@ else if(bb.hasArray()) { * * @return */ - public int size() { + public int size() + { return size; } - public MessageBuffer slice(int offset, int length) { + public MessageBuffer slice(int offset, int length) + { // TODO ensure deleting this slice does not collapse this MessageBuffer - if(offset == 0 && length == size()) + if (offset == 0 && length == size()) { return this; + } else { checkArgument(offset + length <= size()); - return new MessageBuffer(base, address + offset, length, reference); + return new MessageBuffer(base, address + offset, length); } } - public byte getByte(int index) { + public byte getByte(int index) + { return unsafe.getByte(base, address + index); } - public boolean getBoolean(int index) { + public boolean getBoolean(int index) + { return unsafe.getBoolean(base, address + index); } - public short getShort(int index) { + public short getShort(int index) + { short v = unsafe.getShort(base, address + index); return Short.reverseBytes(v); } @@ -426,46 +278,56 @@ public short getShort(int index) { * @param index * @return */ - public int getInt(int index) { + public int getInt(int index) + { // Reading little-endian value int i = unsafe.getInt(base, address + index); // Reversing the endian return Integer.reverseBytes(i); } - public float getFloat(int index) { + public float getFloat(int index) + { return Float.intBitsToFloat(getInt(index)); } - public long getLong(int index) { + public long getLong(int index) + { long l = unsafe.getLong(base, address + index); return Long.reverseBytes(l); } - public double getDouble(int index) { + public double getDouble(int index) + { return Double.longBitsToDouble(getLong(index)); } - public void getBytes(int index, byte[] dst, int dstOffset, int length) { + public void getBytes(int index, byte[] dst, int dstOffset, int length) + { unsafe.copyMemory(base, address + index, dst, ARRAY_BYTE_BASE_OFFSET + dstOffset, length); } - public void getBytes(int index, int len, ByteBuffer dst) { - if(dst.remaining() > len) + public void getBytes(int index, int len, ByteBuffer dst) + { + if (dst.remaining() < len) { throw new BufferOverflowException(); - ByteBuffer src = toByteBuffer(index, len); + } + ByteBuffer src = sliceAsByteBuffer(index, len); dst.put(src); } - public void putByte(int index, byte v) { + public void putByte(int index, byte v) + { unsafe.putByte(base, address + index, v); } - public void putBoolean(int index, boolean v) { + public void putBoolean(int index, boolean v) + { unsafe.putBoolean(base, address + index, v); } - public void putShort(int index, short v) { + public void putShort(int index, short v) + { v = Short.reverseBytes(v); unsafe.putShort(base, address + index, v); } @@ -476,51 +338,59 @@ public void putShort(int index, short v) { * @param index * @param v */ - public void putInt(int index, int v) { + public void putInt(int index, int v) + { // Reversing the endian v = Integer.reverseBytes(v); unsafe.putInt(base, address + index, v); } - public void putFloat(int index, float v) { + public void putFloat(int index, float v) + { putInt(index, Float.floatToRawIntBits(v)); } - public void putLong(int index, long l) { + public void putLong(int index, long l) + { // Reversing the endian l = Long.reverseBytes(l); unsafe.putLong(base, address + index, l); } - public void putDouble(int index, double v) { + public void putDouble(int index, double v) + { putLong(index, Double.doubleToRawLongBits(v)); } - public void putBytes(int index, byte[] src, int srcOffset, int length) { + public void putBytes(int index, byte[] src, int srcOffset, int length) + { unsafe.copyMemory(src, ARRAY_BYTE_BASE_OFFSET + srcOffset, base, address + index, length); } - public void putByteBuffer(int index, ByteBuffer src, int len) { + public void putByteBuffer(int index, ByteBuffer src, int len) + { assert (len <= src.remaining()); + assert (!isUniversalBuffer); - if(src.isDirect()) { + if (src.isDirect()) { unsafe.copyMemory(null, DirectBufferAccess.getAddress(src) + src.position(), base, address + index, len); + src.position(src.position() + len); } - else if(src.hasArray()) { + else if (src.hasArray()) { byte[] srcArray = src.array(); unsafe.copyMemory(srcArray, ARRAY_BYTE_BASE_OFFSET + src.position(), base, address + index, len); + src.position(src.position() + len); } else { - if(base != null) { + if (base != null) { src.get((byte[]) base, index, len); } else { - for(int i = 0; i < len; ++i) { + for (int i = 0; i < len; ++i) { unsafe.putByte(base, address + index, src.get()); } } } - src.position(src.position() + len); } /** @@ -530,36 +400,9 @@ else if(src.hasArray()) { * @param length * @return */ - public ByteBuffer toByteBuffer(int index, int length) { - if(hasArray()) { - return ByteBuffer.wrap((byte[]) base, (int) ((address - ARRAY_BYTE_BASE_OFFSET) + index), length); - } - try { - ByteBuffer bb = null; - switch (directBufferConstructorType) { - case ARGS_LONG_INT_REF: - bb = (ByteBuffer) byteBufferConstructor.newInstance(address + index, length, reference); - break; - case ARGS_LONG_INT: - bb = (ByteBuffer) byteBufferConstructor.newInstance(address + index, length); - break; - case ARGS_INT_INT: - bb = (ByteBuffer) byteBufferConstructor.newInstance((int)address + index, length); - break; - case ARGS_MB_INT_INT: - bb = (ByteBuffer) byteBufferConstructor.newInstance( - memoryBlockWrapFromJni.invoke(null, address + index, length), - length, 0); - break; - default: - throw new IllegalStateException("Unexpected value"); - } - return bb; - } - catch(Throwable e) { - // Convert checked exception to unchecked exception - throw new RuntimeException(e); - } + public ByteBuffer sliceAsByteBuffer(int index, int length) + { + return ByteBuffer.wrap((byte[]) base, (int) ((address - ARRAY_BYTE_BASE_OFFSET) + index), length); } /** @@ -567,8 +410,9 @@ public ByteBuffer toByteBuffer(int index, int length) { * * @return */ - public ByteBuffer toByteBuffer() { - return toByteBuffer(0, size()); + public ByteBuffer sliceAsByteBuffer() + { + return sliceAsByteBuffer(0, size()); } /** @@ -576,45 +420,21 @@ public ByteBuffer toByteBuffer() { * * @return */ - public byte[] toByteArray() { + public byte[] toByteArray() + { byte[] b = new byte[size()]; unsafe.copyMemory(base, address, b, ARRAY_BYTE_BASE_OFFSET, size()); return b; } - @Insecure - public boolean hasArray() { - return base instanceof byte[]; - } - - @Insecure - public byte[] getArray() { + public byte[] array() + { return (byte[]) base; } - @Insecure - public Object getBase() { - return base; - } - - @Insecure - public long getAddress() { - return address; - } - - @Insecure - public int offset() { - if(hasArray()) { - return (int) address - ARRAY_BYTE_BASE_OFFSET; - } - else { - return 0; - } - } - - @Insecure - public ByteBuffer getReference() { - return reference; + public int arrayOffset() + { + return (int) address - ARRAY_BYTE_BASE_OFFSET; } /** @@ -625,18 +445,20 @@ public ByteBuffer getReference() { * @param offset * @param length */ - public void copyTo(int index, MessageBuffer dst, int offset, int length) { + public void copyTo(int index, MessageBuffer dst, int offset, int length) + { unsafe.copyMemory(base, address + index, dst.base, dst.address + offset, length); } - public String toHexString(int offset, int length) { + public String toHexString(int offset, int length) + { StringBuilder s = new StringBuilder(); - for(int i = offset; i < length; ++i) { - if(i != offset) + for (int i = offset; i < length; ++i) { + if (i != offset) { s.append(" "); + } s.append(String.format("%02x", getByte(i))); } return s.toString(); } - } diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferBE.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferBE.java index e879e4c81..1326b396e 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferBE.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferBE.java @@ -1,7 +1,20 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; -import java.nio.ByteBuffer; - import static org.msgpack.core.Preconditions.checkArgument; /** @@ -9,70 +22,82 @@ * The specification of Message Pack demands writing short/int/float/long/double values in the big-endian format. * In the big-endian machine, we do not need to swap the byte order. */ -public class MessageBufferBE extends MessageBuffer { - - MessageBufferBE(ByteBuffer bb) { - super(bb); +public class MessageBufferBE + extends MessageBuffer +{ + MessageBufferBE(byte[] arr, int offset, int length) + { + super(arr, offset, length); } - private MessageBufferBE(Object base, long address, int length, ByteBuffer reference) { - super(base, address, length, reference); + private MessageBufferBE(Object base, long address, int length) + { + super(base, address, length); } @Override - public MessageBufferBE slice(int offset, int length) { - if(offset == 0 && length == size()) + public MessageBufferBE slice(int offset, int length) + { + if (offset == 0 && length == size()) { return this; + } else { checkArgument(offset + length <= size()); - return new MessageBufferBE(base, address + offset, length, reference); + return new MessageBufferBE(base, address + offset, length); } } @Override - public short getShort(int index) { + public short getShort(int index) + { return unsafe.getShort(base, address + index); } @Override - public int getInt(int index) { + public int getInt(int index) + { // We can simply return the integer value as big-endian value return unsafe.getInt(base, address + index); } - public long getLong(int index) { + public long getLong(int index) + { return unsafe.getLong(base, address + index); } @Override - public float getFloat(int index) { + public float getFloat(int index) + { return unsafe.getFloat(base, address + index); } @Override - public double getDouble(int index) { + public double getDouble(int index) + { return unsafe.getDouble(base, address + index); } @Override - public void putShort(int index, short v) { + public void putShort(int index, short v) + { unsafe.putShort(base, address + index, v); } @Override - public void putInt(int index, int v) { + public void putInt(int index, int v) + { unsafe.putInt(base, address + index, v); } @Override - public void putLong(int index, long v) { + public void putLong(int index, long v) + { unsafe.putLong(base, address + index, v); } @Override - public void putDouble(int index, double v) { + public void putDouble(int index, double v) + { unsafe.putDouble(base, address + index, v); } - - } diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferInput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferInput.java index 447c71798..46eea243e 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferInput.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferInput.java @@ -1,3 +1,18 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; import java.io.Closeable; @@ -6,16 +21,17 @@ /** * Provides a sequence of MessageBuffers that contains message packed data. */ -public interface MessageBufferInput extends Closeable { - +public interface MessageBufferInput + extends Closeable +{ /** * Get a next buffer to read. - * @return the next MessageBuffer, or null if no more buffer is available. + *

+ * When this method is called, the formally allocated buffer can be safely discarded. + * + * @return the next MessageBuffer, or return null if no more buffer is available. * @throws IOException when error occurred when reading the data */ - public MessageBuffer next() throws IOException; - - + MessageBuffer next() + throws IOException; } - - diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferOutput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferOutput.java index 4b529c44f..024414bae 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferOutput.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferOutput.java @@ -1,33 +1,76 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; - import java.io.Closeable; import java.io.IOException; +import java.io.Flushable; /** - * Provides a sequence of MessageBuffers for packing the input data + * Provides a buffered output stream for packing objects */ -public interface MessageBufferOutput extends Closeable { - +public interface MessageBufferOutput + extends Closeable, Flushable +{ /** - * Retrieves the next buffer for writing message packed data + * Allocates the next buffer for writing message packed data. + * If the previously allocated buffer is not flushed yet, this next method should discard + * it without writing it. * - * @param bufferSize the buffer size to retrieve + * @param minimumSize the mimium required buffer size to allocate * @return * @throws IOException */ - public MessageBuffer next(int bufferSize) throws IOException; + MessageBuffer next(int minimumSize) + throws IOException; /** - * Output the buffer contents. If you need to output a part of the - * buffer use {@link MessageBuffer#slice(int, int)} - * @param buf + * Flushes the previously allocated buffer. + * This method is not always called because next method also flushes previously allocated buffer. + * This method is called when write method is called or application wants to control the timing of flush. + * + * @param length the size of buffer to flush * @throws IOException */ - public void flush(MessageBuffer buf) throws IOException; - -} - - + void writeBuffer(int length) + throws IOException; + /** + * Writes an external payload data. + * This method should follow semantics of OutputStream. + * + * @param buffer the data to write + * @param offset the start offset in the data + * @param length the number of bytes to write + * @return + * @throws IOException + */ + void write(byte[] buffer, int offset, int length) + throws IOException; + /** + * Writes an external payload data. + * This buffer is given - this MessageBufferOutput owns the buffer and may modify contents of the buffer. Contents of this buffer won't be modified by the caller. + * + * @param buffer the data to add + * @param offset the start offset in the data + * @param length the number of bytes to add + * @return + * @throws IOException + */ + void add(byte[] buffer, int offset, int length) + throws IOException; +} diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferU.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferU.java index 8ce58aa03..1e8783738 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferU.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferU.java @@ -1,36 +1,60 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; - import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import static org.msgpack.core.Preconditions.*; +import static org.msgpack.core.Preconditions.checkArgument; /** * Universal MessageBuffer implementation supporting Java6 and Android. * This buffer always uses ByteBuffer-based memory access */ -public class MessageBufferU extends MessageBuffer { +public class MessageBufferU + extends MessageBuffer +{ + private final ByteBuffer wrap; - MessageBufferU(ByteBuffer bb) { - super(null, 0L, bb.capacity(), bb.order(ByteOrder.BIG_ENDIAN)); - checkNotNull(reference); + MessageBufferU(byte[] arr, int offset, int length) + { + super(arr, offset, length); + ByteBuffer bb = ByteBuffer.wrap(arr); + bb.position(offset); + bb.limit(offset + length); + this.wrap = bb.slice(); } - MessageBufferU(byte[] arr) { - this(ByteBuffer.wrap(arr)); + private MessageBufferU(Object base, long address, int length, ByteBuffer wrap) + { + super(base, address, length); + this.wrap = wrap; } @Override - public MessageBufferU slice(int offset, int length) { - if(offset == 0 && length == size()) + public MessageBufferU slice(int offset, int length) + { + if (offset == 0 && length == size()) { return this; + } else { checkArgument(offset + length <= size()); try { - reference.position(offset); - reference.limit(offset + length); - return new MessageBufferU(reference.slice()); + wrap.position(offset); + wrap.limit(offset + length); + return new MessageBufferU(base, address + offset, length, wrap.slice()); } finally { resetBufferPosition(); @@ -38,99 +62,134 @@ public MessageBufferU slice(int offset, int length) { } } - private void resetBufferPosition() { - reference.position(0); - reference.limit(size); + private void resetBufferPosition() + { + wrap.position(0); + wrap.limit(size); } @Override - public byte getByte(int index) { - return reference.get(index); + public byte getByte(int index) + { + return wrap.get(index); } + @Override - public boolean getBoolean(int index) { - return reference.get(index) != 0; + public boolean getBoolean(int index) + { + return wrap.get(index) != 0; } + @Override - public short getShort(int index) { - return reference.getShort(index); + public short getShort(int index) + { + return wrap.getShort(index); } + @Override - public int getInt(int index) { - return reference.getInt(index); + public int getInt(int index) + { + return wrap.getInt(index); } + @Override - public float getFloat(int index) { - return reference.getFloat(index); + public float getFloat(int index) + { + return wrap.getFloat(index); } + @Override - public long getLong(int index) { - return reference.getLong(index); + public long getLong(int index) + { + return wrap.getLong(index); } + @Override - public double getDouble(int index) { - return reference.getDouble(index); + public double getDouble(int index) + { + return wrap.getDouble(index); } + @Override - public void getBytes(int index, int len, ByteBuffer dst) { + public void getBytes(int index, int len, ByteBuffer dst) + { try { - reference.position(index); - reference.limit(index + len); - dst.put(reference); + wrap.position(index); + wrap.limit(index + len); + dst.put(wrap); } finally { resetBufferPosition(); } } + @Override - public void putByte(int index, byte v) { - reference.put(index, v); + public void putByte(int index, byte v) + { + wrap.put(index, v); } + @Override - public void putBoolean(int index, boolean v) { - reference.put(index, v ? (byte) 1 : (byte) 0); + public void putBoolean(int index, boolean v) + { + wrap.put(index, v ? (byte) 1 : (byte) 0); } + @Override - public void putShort(int index, short v) { - reference.putShort(index, v); + public void putShort(int index, short v) + { + wrap.putShort(index, v); } + @Override - public void putInt(int index, int v) { - reference.putInt(index, v); + public void putInt(int index, int v) + { + wrap.putInt(index, v); } + @Override - public void putFloat(int index, float v) { - reference.putFloat(index, v); + public void putFloat(int index, float v) + { + wrap.putFloat(index, v); } + @Override - public void putLong(int index, long l) { - reference.putLong(index, l); + public void putLong(int index, long l) + { + wrap.putLong(index, l); } + @Override - public void putDouble(int index, double v) { - reference.putDouble(index, v); + public void putDouble(int index, double v) + { + wrap.putDouble(index, v); } + @Override - public ByteBuffer toByteBuffer(int index, int length) { + public ByteBuffer sliceAsByteBuffer(int index, int length) + { try { - reference.position(index); - reference.limit(index + length); - return reference.slice(); + wrap.position(index); + wrap.limit(index + length); + return wrap.slice(); } finally { resetBufferPosition(); } } + @Override - public ByteBuffer toByteBuffer() { - return toByteBuffer(0, size); + public ByteBuffer sliceAsByteBuffer() + { + return sliceAsByteBuffer(0, size); } @Override - public void getBytes(int index, byte[] dst, int dstOffset, int length) { + public void getBytes(int index, byte[] dst, int dstOffset, int length) + { try { - reference.position(index); - reference.get(dst, dstOffset, length); + wrap.position(index); + wrap.get(dst, dstOffset, length); } finally { resetBufferPosition(); @@ -138,18 +197,20 @@ public void getBytes(int index, byte[] dst, int dstOffset, int length) { } @Override - public void putByteBuffer(int index, ByteBuffer src, int len) { + public void putByteBuffer(int index, ByteBuffer src, int len) + { assert (len <= src.remaining()); - if(src.hasArray()) { - putBytes(index, src.array(), src.position(), len); + if (src.hasArray()) { + putBytes(index, src.array(), src.position() + src.arrayOffset(), len); src.position(src.position() + len); } else { int prevSrcLimit = src.limit(); try { src.limit(src.position() + len); - reference.put(src); + wrap.position(index); + wrap.put(src); } finally { src.limit(prevSrcLimit); @@ -158,10 +219,11 @@ public void putByteBuffer(int index, ByteBuffer src, int len) { } @Override - public void putBytes(int index, byte[] src, int srcOffset, int length) { + public void putBytes(int index, byte[] src, int srcOffset, int length) + { try { - reference.position(index); - reference.put(src, srcOffset, length); + wrap.position(index); + wrap.put(src, srcOffset, length); } finally { resetBufferPosition(); @@ -169,17 +231,20 @@ public void putBytes(int index, byte[] src, int srcOffset, int length) { } @Override - public void copyTo(int index, MessageBuffer dst, int offset, int length) { + public void copyTo(int index, MessageBuffer dst, int offset, int length) + { try { - reference.position(index); - dst.putByteBuffer(offset, reference, length); + wrap.position(index); + dst.putByteBuffer(offset, wrap, length); } finally { resetBufferPosition(); } } + @Override - public byte[] toByteArray() { + public byte[] toByteArray() + { byte[] b = new byte[size()]; getBytes(0, b, 0, b.length); return b; diff --git a/msgpack-core/src/main/java/org/msgpack/core/buffer/OutputStreamBufferOutput.java b/msgpack-core/src/main/java/org/msgpack/core/buffer/OutputStreamBufferOutput.java index 768c3621d..08fd3960b 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/buffer/OutputStreamBufferOutput.java +++ b/msgpack-core/src/main/java/org/msgpack/core/buffer/OutputStreamBufferOutput.java @@ -1,3 +1,18 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer; import java.io.IOException; @@ -8,59 +23,79 @@ /** * MessageBufferOutput adapter for {@link java.io.OutputStream}. */ -public class OutputStreamBufferOutput implements MessageBufferOutput { - +public class OutputStreamBufferOutput + implements MessageBufferOutput +{ private OutputStream out; private MessageBuffer buffer; - private byte[] tmpBuf; - public OutputStreamBufferOutput(OutputStream out) { + public OutputStreamBufferOutput(OutputStream out) + { + this(out, 8192); + } + + public OutputStreamBufferOutput(OutputStream out, int bufferSize) + { this.out = checkNotNull(out, "output is null"); + this.buffer = MessageBuffer.allocate(bufferSize); } /** - * Reset Stream. This method doesn't close the old resource. + * Reset Stream. This method doesn't close the old stream. + * * @param out new stream - * @return the old resource + * @return the old stream */ - public OutputStream reset(OutputStream out) throws IOException { + public OutputStream reset(OutputStream out) + throws IOException + { OutputStream old = this.out; this.out = out; return old; } @Override - public MessageBuffer next(int bufferSize) throws IOException { - if(buffer == null || buffer.size != bufferSize) { - return buffer = MessageBuffer.newBuffer(bufferSize); - } - else { - return buffer; + public MessageBuffer next(int mimimumSize) + throws IOException + { + if (buffer.size() < mimimumSize) { + buffer = MessageBuffer.allocate(mimimumSize); } + return buffer; } @Override - public void flush(MessageBuffer buf) throws IOException { - int writeLen = buf.size(); - if(buf.hasArray()) { - out.write(buf.getArray(), buf.offset(), writeLen); - } - else { - if(tmpBuf == null || tmpBuf.length < writeLen) { - tmpBuf = new byte[writeLen]; - } - buf.getBytes(0, tmpBuf, 0, writeLen); - out.write(tmpBuf, 0, writeLen); - } + public void writeBuffer(int length) + throws IOException + { + write(buffer.array(), buffer.arrayOffset(), length); } @Override - public void close() throws IOException { - try { - out.flush(); - } - finally { - out.close(); - } + public void write(byte[] buffer, int offset, int length) + throws IOException + { + out.write(buffer, offset, length); + } + + @Override + public void add(byte[] buffer, int offset, int length) + throws IOException + { + write(buffer, offset, length); + } + + @Override + public void close() + throws IOException + { + out.close(); + } + + @Override + public void flush() + throws IOException + { + out.flush(); } } diff --git a/msgpack-core/src/main/java/org/msgpack/value/AbstractValueVisitor.java b/msgpack-core/src/main/java/org/msgpack/value/AbstractValueVisitor.java deleted file mode 100644 index 06697d3c5..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/AbstractValueVisitor.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.msgpack.value; - -/** - * Empty visitor that does nothing - */ -public class AbstractValueVisitor implements ValueVisitor { - - @Override - public void visitNil() { - - } - @Override - public void visitBoolean(boolean v) { - - } - @Override - public void visitInteger(IntegerValue v) { - - } - @Override - public void visitFloat(FloatValue v) { - - } - @Override - public void visitBinary(BinaryValue v) { - - } - @Override - public void visitString(StringValue v) { - - } - @Override - public void visitArray(ArrayValue v) { - - } - @Override - public void visitMap(MapValue v) { - - } - @Override - public void visitExtended(ExtendedValue v) { - - } - @Override - public void onError(Exception e) { - - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ArrayCursor.java b/msgpack-core/src/main/java/org/msgpack/value/ArrayCursor.java deleted file mode 100644 index d491ba9c8..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/ArrayCursor.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.msgpack.value; - -import java.util.Iterator; - -/** - * Created on 6/16/14. - */ -public interface ArrayCursor extends ValueRef, Iterable { - public int size(); - - public boolean hasNext(); - public ValueRef next(); - public void skip(); - - /** - * Skips all of the remaining values - */ - public void skipAll(); - - public Iterator iterator(); - - public ArrayValue toValue(); - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ArrayValue.java b/msgpack-core/src/main/java/org/msgpack/value/ArrayValue.java index 1c15df7c0..4d7a6e8c2 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/ArrayValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/ArrayValue.java @@ -1,20 +1,57 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; +import java.util.Iterator; +import java.util.List; + /** - * Value interface for array type data. + * The interface {@code ArrayValue} represents MessagePack's Array type. * - * Implementation note: We do not implement List interface here, because - * we cannot reuse AbstractList and AbstractValue implementations simultaneously since - * Java does not support mixin of classes. Instead, it provides {@link #iterator} or - * {@link #toValueArray()} methods to traverse the array contents. + * MessagePack's Array type can represent sequence of values. */ -public interface ArrayValue extends Value, ArrayCursor { +public interface ArrayValue + extends Value, Iterable +{ + /** + * Returns number of elements in this array. + */ + int size(); - public Value[] toValueArray(); + /** + * Returns the element at the specified position in this array. + * + * @throws IndexOutOfBoundsException If the index is out of range + * (index < 0 || index >= size()) + */ + Value get(int index); - public Value get(int index); - public Value apply(int index); + /** + * Returns the element at the specified position in this array. + * This method returns an ImmutableNilValue if the index is out of range. + */ + Value getOrNilValue(int index); - public ArrayValue toValue(); + /** + * Returns an iterator over elements. + */ + Iterator iterator(); + /** + * Returns the value as {@code List}. + */ + List list(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/BinaryValue.java b/msgpack-core/src/main/java/org/msgpack/value/BinaryValue.java index 808de6f9b..c66f7a67c 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/BinaryValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/BinaryValue.java @@ -1,8 +1,28 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; /** -* Created on 5/30/14. -*/ -public interface BinaryValue extends RawValue { - BinaryValue toValue(); + * The interface {@code BinaryValue} represents MessagePack's Binary type. + * + * MessagePack's Binary type can represent a byte array at most 264-1 bytes. + * + * @see org.msgpack.value.RawValue + */ +public interface BinaryValue + extends RawValue +{ } diff --git a/msgpack-core/src/main/java/org/msgpack/value/BooleanValue.java b/msgpack-core/src/main/java/org/msgpack/value/BooleanValue.java index 2d517469e..6ccf5c9ef 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/BooleanValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/BooleanValue.java @@ -1,9 +1,30 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; /** -* Created on 5/30/14. -*/ -public interface BooleanValue extends Value { - public boolean toBoolean(); - public BooleanValue toValue(); + * The interface {@code BooleanValue} represents MessagePack's Boolean type. + * + * MessagePack's Boolean type can represent {@code true} or {@code false}. + */ +public interface BooleanValue + extends Value +{ + /** + * Returns the value as a {@code boolean}. + */ + boolean getBoolean(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/Cursor.java b/msgpack-core/src/main/java/org/msgpack/value/Cursor.java deleted file mode 100644 index 09fa50e5f..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/Cursor.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.msgpack.value; - - -import java.io.Closeable; -import java.util.Iterator; - -/** - * Cursor for traversing a stream of message-packed values - */ -public interface Cursor extends Iterator, Closeable { - - /** - * Tests whether there is a next element. - * @return true if there is a next element, or false if there is no more element. - */ - public boolean hasNext(); - - /** - * Returns a reference to the value, then proceeds the cursor. - * The returned reference is valid until {@link #hasNext()} is called. - * @return - */ - public ValueRef nextRef(); - - /** - * Returns the materialized value of the referenced value, then proceeds the cursor. - * @return - */ - public Value next(); - - /** - * Skip reading the current value. - */ - public void skip(); - - /** - * Returns the number of the read bytes - * @return the number of the read bytes - */ - public long getReadBytes(); - - public static interface Function { - public Out apply(Value input) throws Exception; - } - - /** - * Applies a function f to the referenced value, then returns the result of the function. - * @param f a function that receives the referenced value. - * @param the result type of the function - * @return the result of the function - */ - public Out apply(Function f); - - public boolean isNilValue(); - public boolean isBooleanValue(); - public boolean isNumberValue(); - public boolean isIntegerValue(); - public boolean isFloatValue(); - public boolean isBinaryValue(); - public boolean isStringValue(); - public boolean isRawValue(); - public boolean isArrayValue(); - public boolean isMapValue(); - public boolean isExtendedValue(); - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ExtendedValue.java b/msgpack-core/src/main/java/org/msgpack/value/ExtendedValue.java deleted file mode 100644 index 9d4b47a16..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/ExtendedValue.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.msgpack.value; - -/** -* Created on 5/30/14. -*/ -public interface ExtendedValue extends RawValue { - public int getExtType(); - public ExtendedValue toValue(); -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ValueVisitor.java b/msgpack-core/src/main/java/org/msgpack/value/ExtensionValue.java similarity index 55% rename from msgpack-core/src/main/java/org/msgpack/value/ValueVisitor.java rename to msgpack-core/src/main/java/org/msgpack/value/ExtensionValue.java index 22436d30a..5a076ecbc 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/ValueVisitor.java +++ b/msgpack-core/src/main/java/org/msgpack/value/ExtensionValue.java @@ -16,19 +16,17 @@ package org.msgpack.value; /** - * Interface for implementing the visitor pattern on message-packed values + * The interface {@code ExtensionValue} represents MessagePack's Extension type. + * + * MessagePack's Extension type can represent represents a tuple of type information and a byte array where type information is an + * integer whose meaning is defined by applications. + * + * As the type information, applications can use 0 to 127 as the application-specific types. -1 to -128 is reserved for MessagePack's future extension. */ -public interface ValueVisitor { +public interface ExtensionValue + extends Value +{ + byte getType(); - public void visitNil(); - public void visitBoolean(boolean v); - public void visitInteger(IntegerValue v); - public void visitFloat(FloatValue v); - public void visitBinary(BinaryValue v); - public void visitString(StringValue v); - public void visitArray(ArrayValue v); - public void visitMap(MapValue v); - public void visitExtended(ExtendedValue v); - - public void onError(Exception e); + byte[] getData(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/FloatValue.java b/msgpack-core/src/main/java/org/msgpack/value/FloatValue.java index 3e5dda745..dbaf73a18 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/FloatValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/FloatValue.java @@ -1,8 +1,28 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; /** -* Created on 5/30/14. -*/ -public interface FloatValue extends NumberValue { - FloatValue toValue(); + * The interface {@code FloatValue} represents MessagePack's Float type. + * + * MessagePack's Float type can represent IEEE 754 double precision floating point numbers including NaN and infinity. This is same with Java's {@code double} type. + * + * @see org.msgpack.value.NumberValue + */ +public interface FloatValue + extends NumberValue +{ } diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableArrayValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableArrayValue.java new file mode 100644 index 000000000..9301c2eb8 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableArrayValue.java @@ -0,0 +1,35 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +import java.util.Iterator; +import java.util.List; + +public interface ImmutableArrayValue + extends ArrayValue, ImmutableValue +{ + /** + * Returns an iterator over elements. + * Returned Iterator does not support {@code remove()} method since the value is immutable. + */ + Iterator iterator(); + + /** + * Returns the value as {@code List}. + * Returned List is immutable. It does not support {@code put()}, {@code clear()}, or other methods that modify the value. + */ + List list(); +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableBinaryValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableBinaryValue.java new file mode 100644 index 000000000..475241a57 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableBinaryValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableBinaryValue + extends BinaryValue, ImmutableRawValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableBooleanValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableBooleanValue.java new file mode 100644 index 000000000..dd2afad43 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableBooleanValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableBooleanValue + extends BooleanValue, ImmutableValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableExtensionValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableExtensionValue.java new file mode 100644 index 000000000..5e984db05 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableExtensionValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableExtensionValue + extends ExtensionValue, ImmutableValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableFloatValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableFloatValue.java new file mode 100644 index 000000000..7105483d1 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableFloatValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableFloatValue + extends FloatValue, ImmutableNumberValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableIntegerValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableIntegerValue.java new file mode 100644 index 000000000..3482583ff --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableIntegerValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableIntegerValue + extends IntegerValue, ImmutableNumberValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableMapValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableMapValue.java new file mode 100644 index 000000000..cc3122f03 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableMapValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableMapValue + extends MapValue, ImmutableValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableNilValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableNilValue.java new file mode 100644 index 000000000..8a7857287 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableNilValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableNilValue + extends NilValue, ImmutableValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableNumberValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableNumberValue.java new file mode 100644 index 000000000..42afcf304 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableNumberValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableNumberValue + extends NumberValue, ImmutableValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableRawValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableRawValue.java new file mode 100644 index 000000000..36698dbeb --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableRawValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableRawValue + extends RawValue, ImmutableValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableStringValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableStringValue.java new file mode 100644 index 000000000..6e3f95360 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableStringValue.java @@ -0,0 +1,21 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableStringValue + extends StringValue, ImmutableRawValue +{ +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ImmutableValue.java b/msgpack-core/src/main/java/org/msgpack/value/ImmutableValue.java new file mode 100644 index 000000000..6a5740029 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/ImmutableValue.java @@ -0,0 +1,47 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +public interface ImmutableValue + extends Value +{ + @Override + public ImmutableNilValue asNilValue(); + + @Override + public ImmutableBooleanValue asBooleanValue(); + + @Override + public ImmutableIntegerValue asIntegerValue(); + + @Override + public ImmutableFloatValue asFloatValue(); + + @Override + public ImmutableArrayValue asArrayValue(); + + @Override + public ImmutableMapValue asMapValue(); + + @Override + public ImmutableRawValue asRawValue(); + + @Override + public ImmutableBinaryValue asBinaryValue(); + + @Override + public ImmutableStringValue asStringValue(); +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/IntegerValue.java b/msgpack-core/src/main/java/org/msgpack/value/IntegerValue.java index 4299b42ad..8480751c4 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/IntegerValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/IntegerValue.java @@ -1,8 +1,88 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; +import org.msgpack.core.MessageFormat; + +import java.math.BigInteger; + /** -* Created on 5/30/14. -*/ -public interface IntegerValue extends NumberValue { - IntegerValue toValue(); + * The interface {@code IntegerValue} represents MessagePack's Integer type. + * + * MessagePack's Integer type can represent from -263 to 264-1. + */ +public interface IntegerValue + extends NumberValue +{ + /** + * Returns true if the value is in the range of [-27 to 27-1]. + */ + boolean isInByteRange(); + + /** + * Returns true if the value is in the range of [-215 to 215-1] + */ + boolean isInShortRange(); + + /** + * Returns true if the value is in the range of [-231 to 231-1] + */ + boolean isInIntRange(); + + /** + * Returns true if the value is in the range of [-263 to 263-1] + */ + boolean isInLongRange(); + + /** + * Returns the most succinct MessageFormat type to represent this integer value. + * @return + */ + MessageFormat mostSuccinctMessageFormat(); + + /** + * Returns the value as a {@code byte}, otherwise throws an exception. + * + * @throws MessageIntegerOverflowException If the value does not fit in the range of {@code byte} type. + */ + byte asByte(); + + /** + * Returns the value as a {@code short}, otherwise throws an exception. + * + * @throws MessageIntegerOverflowException If the value does not fit in the range of {@code short} type. + */ + short asShort(); + + /** + * Returns the value as an {@code int}, otherwise throws an exception. + * + * @throws MessageIntegerOverflowException If the value does not fit in the range of {@code int} type. + */ + int asInt(); + + /** + * Returns the value as a {@code long}, otherwise throws an exception. + * + * @throws MessageIntegerOverflowException If the value does not fit in the range of {@code long} type. + */ + long asLong(); + + /** + * Returns the value as a {@code BigInteger}. + */ + BigInteger asBigInteger(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/MapCursor.java b/msgpack-core/src/main/java/org/msgpack/value/MapCursor.java deleted file mode 100644 index 17ac2fec2..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/MapCursor.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.msgpack.value; - -/** - * Cursor for traversing map value entries. This cursor reports a sequence of key and value pairs. - */ -public interface MapCursor extends ValueRef { - public int size(); - - /** - * Test whether this cursor can point to a next key or value. - * @return - */ - public boolean hasNext(); - - /** - * Retrieves a reference to the next key or value. - * @return - */ - public ValueRef nextKeyOrValue(); - - /** - * Skips a next key or value - */ - public void skipKeyOrValue(); - - /** - * Skips all of the remaining keys and values. - */ - public void skipAll(); - - MapValue toValue(); -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/MapValue.java b/msgpack-core/src/main/java/org/msgpack/value/MapValue.java index ea67fdad6..fa7f55c8d 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/MapValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/MapValue.java @@ -1,13 +1,54 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; +import java.util.Collection; import java.util.Map; +import java.util.Set; /** -* Created on 5/30/14. -*/ -public interface MapValue extends Value, MapCursor { - public Value[] toKeyValueSeq(); - public Map toMap(); + * The interface {@code ArrayValue} represents MessagePack's Map type. + * + * MessagePack's Map type can represent sequence of key-value pairs. + */ +public interface MapValue + extends Value +{ + /** + * Returns number of key-value pairs in this array. + */ + int size(); - public MapValue toValue(); + Set keySet(); + + Set> entrySet(); + + Collection values(); + + /** + * Returns the value as {@code Map}. + */ + Map map(); + + /** + * Returns the key-value pairs as an array of {@code Value}. + * + * Odd elements are keys. Next element of an odd element is a value corresponding to the key. + * + * For example, if this value represents {"k1": "v1", "k2": "v2"}, this method returns ["k1", "v1", "k2", "v2"]. + */ + Value[] getKeyValueArray(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/NilValue.java b/msgpack-core/src/main/java/org/msgpack/value/NilValue.java index 4c8c39beb..8f5835001 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/NilValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/NilValue.java @@ -1,8 +1,24 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; /** - * References to values + * The interface {@code NilValue} represents MessagePack's Nil type. */ -public interface NilValue extends Value { - NilValue toValue(); +public interface NilValue + extends Value +{ } diff --git a/msgpack-core/src/main/java/org/msgpack/value/NumberValue.java b/msgpack-core/src/main/java/org/msgpack/value/NumberValue.java index 38952fcc5..3e5bd75e0 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/NumberValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/NumberValue.java @@ -1,106 +1,65 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; -import org.msgpack.core.MessageOverflowException; - import java.math.BigInteger; /** -* Created on 5/30/14. -*/ -public interface NumberValue extends Value { + * The base interface {@code NumberValue} of {@code IntegerValue} and {@code FloatValue}. To extract primitive type values, call toXXX methods, which may lose some information by rounding or truncation. + * + * @see org.msgpack.value.IntegerValue + * @see org.msgpack.value.FloatValue + */ +public interface NumberValue + extends Value +{ + /** + * Represent this value as a byte value, which may involve rounding or truncation of the original value. + * the value. + */ + byte toByte(); /** - * Check whether this value is a valid byte value. - * @return true if this value has no fractional part, and is within the range of {@link Byte#MIN_VALUE} and {@link Byte#MAX_VALUE}; otherwise returns false + * Represent this value as a short value, which may involve rounding or truncation of the original value. */ - public boolean isValidByte(); + short toShort(); /** - * Check whether this value is a valid short value. - * @return true if this value has no fractional part, and is within the range of {@link Short#MIN_VALUE} and {@link Short#MAX_VALUE}; otherwise returns false + * Represent this value as an int value, which may involve rounding or truncation of the original value. + * value. */ - public boolean isValidShort(); + int toInt(); /** - * Check whether this value is a valid integer value. - * @return true if this value has no fractional part, and is within the range of {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}; otherwise returns false + * Represent this value as a long value, which may involve rounding or truncation of the original value. */ - public boolean isValidInt(); + long toLong(); /** - * Check whether this value is a valid long value. - * @return true if this value has no fractional part, and is within the range of {@link Long#MIN_VALUE} and {@link Long#MAX_VALUE}; otherwise returns false + * Represent this value as a BigInteger, which may involve rounding or truncation of the original value. */ - public boolean isValidLong(); + BigInteger toBigInteger(); /** - * Returns true if this number has no decimal component - * @return true if this number has no decimal component, otherwise false (float, double values); + * Represent this value as a 32-bit float value, which may involve rounding or truncation of the original value. */ - public boolean isWhole(); + float toFloat(); /** - * Convert this value into a byte value. If this value is not within the range of Byte value, it will truncate or round the value. - */ - public byte toByte(); - /** - * Convert this value into a short value. If this value is not within the range of Short value, it will truncate or round the value. - */ - public short toShort(); - /** - * Convert this value into an int value. If this value is not within the range of Int value, it will truncate or round the value. - */ - public int toInt(); - /** - * Convert this value into a long value. If this value is not within the range of Long value, it will truncate or round the value. + * Represent this value as a 64-bit double value, which may involve rounding or truncation of the original value. */ - public long toLong(); - /** - * Convert this value into a BigInteger - */ - public BigInteger toBigInteger(); - /** - * Convert this value into a float value - */ - public float toFloat(); - /** - * Convert this value into a double value - */ - public double toDouble(); - - /** - * Convert this value into a byte value. If this value is not within the range of Byte value, it throws an exception. - * @return - * @throws org.msgpack.core.MessageOverflowException when the value is not within the range of {@link Byte#MIN_VALUE} and {@link Byte#MAX_VALUE}; - */ - public byte asByte() throws MessageOverflowException; - - /** - * Convert this value into a short value. If this value is not within the range of Short value, it throws an exception. - * @return - * @throws org.msgpack.core.MessageOverflowException when the value is not within the range of {@link Short#MIN_VALUE} and {@link Short#MAX_VALUE} - */ - public short asShort() throws MessageOverflowException; - - /** - * Convert this value into an int value. If this value is not within the range of Integer value, it throws an exception. - * @return - * @throws org.msgpack.core.MessageOverflowException when the value is not within the range of {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE} - */ - public int asInt() throws MessageOverflowException; - - /** - * Convert this value into a long value. If this value is not within the range of Long value, it throws an exception. - * @return - * @throws org.msgpack.core.MessageOverflowException when the value is not within the range of {@link Long#MIN_VALUE} and {@link Long#MAX_VALUE} - */ - public long asLong() throws MessageOverflowException; - - /** - * Convert this value into a BigInteger value. - * @return - * @throws org.msgpack.core.MessageOverflowException - */ - public BigInteger asBigInteger() throws MessageOverflowException; - + double toDouble(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/RawValue.java b/msgpack-core/src/main/java/org/msgpack/value/RawValue.java index 8db9e34b1..8857b5340 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/RawValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/RawValue.java @@ -1,19 +1,61 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; -import org.msgpack.core.buffer.MessageBuffer; - import java.nio.ByteBuffer; /** - * Base type of StringValue, BinaryValue and ExtendedValue + * The interface {@code RawValue} represents MessagePack's Raw type, which means Binary or String type. + *

+ * MessagePack's Raw type can represent a byte array at most 264-1 bytes. + * + * @see org.msgpack.value.StringValue + * @see org.msgpack.value.BinaryValue */ -public interface RawValue extends Value { - public byte[] toByteArray(); - public ByteBuffer toByteBuffer(); - public MessageBuffer toMessageBuffer(); +public interface RawValue + extends Value +{ + /** + * Returns the value as {@code byte[]}. + * + * This method copies the byte array. + */ + byte[] asByteArray(); + + /** + * Returns the value as {@code ByteBuffer}. + * + * Returned ByteBuffer is read-only. See {@code#asReadOnlyBuffer()}. + * This method doesn't copy the byte array as much as possible. + */ + ByteBuffer asByteBuffer(); - @Override - public String toString(); + /** + * Returns the value as {@code String}. + * + * This method throws an exception if the value includes invalid UTF-8 byte sequence. + * + * @throws MessageStringCodingException If this value includes invalid UTF-8 byte sequence. + */ + String asString(); - public RawValue toValue(); + /** + * Returns the value as {@code String}. + * + * This method replaces an invalid UTF-8 byte sequence with U+FFFD replacement character. + */ + String toString(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/StringValue.java b/msgpack-core/src/main/java/org/msgpack/value/StringValue.java index 35315f14d..0c812d58d 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/StringValue.java +++ b/msgpack-core/src/main/java/org/msgpack/value/StringValue.java @@ -1,8 +1,30 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value; /** - * Created on 5/30/14. + * The interface {@code StringValue} represents MessagePack's String type. + * + * MessagePack's String type can represent a UTF-8 string at most 264-1 bytes. + * + * Note that the value could include invalid byte sequences. {@code asString()} method throws {@code MessageTypeStringCodingException} if the value includes invalid byte sequence. {@code toJson()} method replaces an invalid byte sequence with U+FFFD replacement character. + * + * @see org.msgpack.value.RawValue */ -public interface StringValue extends RawValue { - public StringValue toValue(); +public interface StringValue + extends RawValue +{ } diff --git a/msgpack-core/src/main/java/org/msgpack/value/Value.java b/msgpack-core/src/main/java/org/msgpack/value/Value.java index 0cf02453c..2fae838b4 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/Value.java +++ b/msgpack-core/src/main/java/org/msgpack/value/Value.java @@ -15,15 +15,248 @@ // package org.msgpack.value; +import org.msgpack.core.MessagePacker; -import org.msgpack.core.*; +import java.io.IOException; /** - * Value is a holder of a message-packed value. + * Value is an implementation of MessagePack type system. To retrieve values from a Value object, + * You need to check its {@link ValueType} then call an appropriate asXXXValue method. + * + * + * */ -public interface Value extends ValueRef { +public interface Value +{ + /** + * Returns type of this value. + * + * Note that you can't use instanceof to check type of a value because type of a mutable value is variable. + */ + ValueType getValueType(); - public ArrayValue asArrayValue() throws MessageTypeException; - public MapValue asMapValue() throws MessageTypeException; + /** + * Returns immutable copy of this value. + * + * This method simply returns this without copying the value if this value is already immutable. + */ + ImmutableValue immutableValue(); + /** + * Returns true if type of this value is Nil. + * + * If this method returns true, {@code asNilValue} never throws exceptions. + * Note that you can't use instanceof or cast ((NilValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isNilValue(); + + /** + * Returns true if type of this value is Boolean. + * + * If this method returns true, {@code asBooleanValue} never throws exceptions. + * Note that you can't use instanceof or cast ((BooleanValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isBooleanValue(); + + /** + * Returns true if type of this value is Integer or Float. + * + * If this method returns true, {@code asNumberValue} never throws exceptions. + * Note that you can't use instanceof or cast ((NumberValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isNumberValue(); + + /** + * Returns true if type of this value is Integer. + * + * If this method returns true, {@code asIntegerValue} never throws exceptions. + * Note that you can't use instanceof or cast ((IntegerValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isIntegerValue(); + + /** + * Returns true if type of this value is Float. + * + * If this method returns true, {@code asFloatValue} never throws exceptions. + * Note that you can't use instanceof or cast ((FloatValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isFloatValue(); + + /** + * Returns true if type of this value is String or Binary. + * + * If this method returns true, {@code asRawValue} never throws exceptions. + * Note that you can't use instanceof or cast ((RawValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isRawValue(); + + /** + * Returns true if type of this value is Binary. + * + * If this method returns true, {@code asBinaryValue} never throws exceptions. + * Note that you can't use instanceof or cast ((BinaryValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isBinaryValue(); + + /** + * Returns true if type of this value is String. + * + * If this method returns true, {@code asStringValue} never throws exceptions. + * Note that you can't use instanceof or cast ((StringValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isStringValue(); + + /** + * Returns true if type of this value is Array. + * + * If this method returns true, {@code asArrayValue} never throws exceptions. + * Note that you can't use instanceof or cast ((ArrayValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isArrayValue(); + + /** + * Returns true if type of this value is Map. + * + * If this method returns true, {@code asMapValue} never throws exceptions. + * Note that you can't use instanceof or cast ((MapValue) thisValue) to check type of a value because type of a mutable value is variable. + */ + boolean isMapValue(); + + /** + * Returns true if type of this an Extension. + * + * If this method returns true, {@code asExtensionValue} never throws exceptions. + * Note that you can't use instanceof or cast ((ExtensionValue) thisValue) to check type of a value because + * type of a mutable value is variable. + */ + boolean isExtensionValue(); + + /** + * Returns the value as {@code NilValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((NilValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Nil. + */ + NilValue asNilValue(); + + /** + * Returns the value as {@code BooleanValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((BooleanValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Boolean. + */ + BooleanValue asBooleanValue(); + + /** + * Returns the value as {@code NumberValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((NumberValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Integer or Float. + */ + NumberValue asNumberValue(); + + /** + * Returns the value as {@code IntegerValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((IntegerValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Integer. + */ + IntegerValue asIntegerValue(); + + /** + * Returns the value as {@code FloatValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((FloatValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Float. + */ + FloatValue asFloatValue(); + + /** + * Returns the value as {@code RawValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((RawValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Binary or String. + */ + RawValue asRawValue(); + + /** + * Returns the value as {@code BinaryValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((BinaryValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Binary. + */ + BinaryValue asBinaryValue(); + + /** + * Returns the value as {@code StringValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((StringValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not String. + */ + StringValue asStringValue(); + + /** + * Returns the value as {@code ArrayValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((ArrayValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Array. + */ + ArrayValue asArrayValue(); + + /** + * Returns the value as {@code MapValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((MapValue) thisValue) to check type of a value because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not Map. + */ + MapValue asMapValue(); + + /** + * Returns the value as {@code ExtensionValue}. Otherwise throws {@code MessageTypeCastException}. + * + * Note that you can't use instanceof or cast ((ExtensionValue) thisValue) to check type of a value + * because type of a mutable value is variable. + * + * @throws MessageTypeCastException If type of this value is not an Extension. + */ + ExtensionValue asExtensionValue(); + + /** + * Serializes the value using the specified {@code MessagePacker} + * + * @see MessagePacker + */ + void writeTo(MessagePacker pk) + throws IOException; + + /** + * Compares this value to the specified object. + * + * This method returns {@code true} if type and value are equivalent. + * If this value is {@code MapValue} or {@code ArrayValue}, this method check equivalence of elements recursively. + */ + boolean equals(Object obj); + + /** + * Returns json representation of this Value. + * + * Following behavior is not configurable at this release and they might be changed at future releases: + * + * * if a key of MapValue is not string, the key is converted to a string using toString method. + * * NaN and Infinity of DoubleValue are converted to null. + * * ExtensionValue is converted to a 2-element array where first element is a number and second element is the data encoded in hex. + * * BinaryValue is converted to a string using UTF-8 encoding. Invalid byte sequence is replaced with U+FFFD replacement character. + * * Invalid UTF-8 byte sequences in StringValue is replaced with U+FFFD replacement character + */ + String toJson(); } diff --git a/msgpack-core/src/main/java/org/msgpack/value/ValueFactory.java b/msgpack-core/src/main/java/org/msgpack/value/ValueFactory.java index 084d325da..b0ffc932a 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/ValueFactory.java +++ b/msgpack-core/src/main/java/org/msgpack/value/ValueFactory.java @@ -15,135 +15,213 @@ // package org.msgpack.value; -import org.msgpack.value.impl.*; +import org.msgpack.value.impl.ImmutableArrayValueImpl; +import org.msgpack.value.impl.ImmutableBigIntegerValueImpl; +import org.msgpack.value.impl.ImmutableBinaryValueImpl; +import org.msgpack.value.impl.ImmutableBooleanValueImpl; +import org.msgpack.value.impl.ImmutableDoubleValueImpl; +import org.msgpack.value.impl.ImmutableExtensionValueImpl; +import org.msgpack.value.impl.ImmutableLongValueImpl; +import org.msgpack.value.impl.ImmutableMapValueImpl; +import org.msgpack.value.impl.ImmutableNilValueImpl; +import org.msgpack.value.impl.ImmutableStringValueImpl; +import java.math.BigInteger; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.math.BigInteger; -import java.nio.ByteBuffer; -/** - * Factory for creting Value instances - */ -public class ValueFactory { - public static NilValue nilValue() { - return NilValueImpl.getInstance(); +public final class ValueFactory +{ + private ValueFactory() + { } - public static BooleanValue newBoolean(boolean v) { - return v ? BooleanValueImpl.TRUE : BooleanValueImpl.FALSE; + public static ImmutableNilValue newNil() + { + return ImmutableNilValueImpl.get(); } - public static IntegerValue newByte(byte v) { - return new IntegerValueImpl((int) v); + public static ImmutableBooleanValue newBoolean(boolean v) + { + return v ? ImmutableBooleanValueImpl.TRUE : ImmutableBooleanValueImpl.FALSE; } - public static IntegerValue newShort(short v) { - return new IntegerValueImpl((int) v); + public static ImmutableIntegerValue newInteger(byte v) + { + return new ImmutableLongValueImpl(v); } - public static IntegerValue newInt(int v) { - return new IntegerValueImpl(v); + public static ImmutableIntegerValue newInteger(short v) + { + return new ImmutableLongValueImpl(v); } - public static IntegerValue newLong(long v) { - return new LongValueImpl(v); + public static ImmutableIntegerValue newInteger(int v) + { + return new ImmutableLongValueImpl(v); } - public static IntegerValue newBigInteger(BigInteger v) { - return new BigIntegerValueImpl(v); + public static ImmutableIntegerValue newInteger(long v) + { + return new ImmutableLongValueImpl(v); } - public static FloatValue newFloat(float v) { - return new FloatValueImpl(v); + public static ImmutableIntegerValue newInteger(BigInteger v) + { + return new ImmutableBigIntegerValueImpl(v); } - public static FloatValue newDouble(double v) { - return new DoubleValueImpl(v); + public static ImmutableFloatValue newFloat(float v) + { + return new ImmutableDoubleValueImpl(v); } - public static BinaryValue newBinary(byte[] b) { - return new BinaryValueImpl(ByteBuffer.wrap(b)); + public static ImmutableFloatValue newFloat(double v) + { + return new ImmutableDoubleValueImpl(v); } - public static BinaryValue newBinary(byte[] b, int off, int len) { - return new BinaryValueImpl(ByteBuffer.wrap(b, off, len)); + public static ImmutableBinaryValue newBinary(byte[] b) + { + return new ImmutableBinaryValueImpl(b); } - public static BinaryValue newBinary(ByteBuffer bb) { - return new BinaryValueImpl(bb.duplicate()); + public static ImmutableBinaryValue newBinary(byte[] b, int off, int len) + { + return new ImmutableBinaryValueImpl(Arrays.copyOfRange(b, off, len)); } - public static StringValue newString(String s) { - return new StringValueImpl(s); + public static ImmutableStringValue newString(String s) + { + return new ImmutableStringValueImpl(s); } - public static StringValue newRawString(byte[] b) { - return new RawStringValueImpl(ByteBuffer.wrap(b)); + public static ImmutableStringValue newString(byte[] b) + { + return new ImmutableStringValueImpl(b); } - public static StringValue newRawString(byte[] b, int off, int len) { - return new RawStringValueImpl(ByteBuffer.wrap(b, off, len)); + public static ImmutableStringValue newString(byte[] b, int off, int len) + { + return new ImmutableStringValueImpl(Arrays.copyOfRange(b, off, len)); } - public static StringValue newRawString(ByteBuffer bb) { - return new RawStringValueImpl(bb.duplicate()); - } - - public static ArrayValue newArrayFrom(List list) { + public static ImmutableArrayValue newArray(List list) + { if (list.isEmpty()) { - return ArrayValueImpl.empty(); + return ImmutableArrayValueImpl.empty(); } Value[] array = list.toArray(new Value[list.size()]); - return new ArrayValueImpl(array); + return new ImmutableArrayValueImpl(array); } - public static ArrayValue newArray(Value... array) { + public static ImmutableArrayValue newArray(Value... array) + { if (array.length == 0) { - return ArrayValueImpl.empty(); + return ImmutableArrayValueImpl.empty(); } - return new ArrayValueImpl(array); + return new ImmutableArrayValueImpl(Arrays.copyOf(array, array.length)); } - public static ArrayValue emptyArray() { - return ArrayValueImpl.empty(); + public static ImmutableArrayValue emptyArray() + { + return ImmutableArrayValueImpl.empty(); } - public static MapValue newMap(Map map) { - Value[] keyValueSequence = new Value[map.size() * 2]; + public static + ImmutableMapValue newMap(Map map) + { + Value[] kvs = new Value[map.size() * 2]; Iterator> ite = map.entrySet().iterator(); int index = 0; while (ite.hasNext()) { Map.Entry pair = ite.next(); - keyValueSequence[index++] = pair.getKey(); - keyValueSequence[index++] = pair.getValue(); + kvs[index] = pair.getKey(); + index++; + kvs[index] = pair.getValue(); + index++; + } + return newMap(kvs); + } + + public static ImmutableMapValue newMap(Value[] kvs) + { + if (kvs.length == 0) { + return ImmutableMapValueImpl.empty(); } - return newMap(keyValueSequence); + return new ImmutableMapValueImpl(Arrays.copyOf(kvs, kvs.length)); } - public static MapValue newMap(Value[] keyValueSequence) { - if (keyValueSequence.length == 0) { - return MapValueImpl.empty(); + public static ImmutableMapValue emptyMap() + { + return ImmutableMapValueImpl.empty(); + } + + public static MapValue newMap(Map.Entry... pairs) + { + MapBuilder b = new MapBuilder(); + for (Map.Entry p : pairs) { + b.put(p); } - return new MapValueImpl(keyValueSequence); + return b.build(); } - public static MapValue emptyMap() { - return MapValueImpl.empty(); + public static MapBuilder newMapBuilder() + { + return new MapBuilder(); } - public static ExtendedValue newExtendedValue(int extType, byte[] extData) { - return newExtendedValue(extType, ByteBuffer.wrap(extData)); + public static Map.Entry newMapEntry(Value key, Value value) + { + return new AbstractMap.SimpleEntry(key, value); } - public static ExtendedValue newExtendedValue(int extType, ByteBuffer extData) { - return new ExtendedValueImpl(extType, extData); + public static class MapBuilder + { + private final Map map = new HashMap(); + + public MapBuilder() {} + + public MapValue build() + { + return newMap(map); + } + + public MapBuilder put(Map.Entry pair) + { + put(pair.getKey(), pair.getValue()); + return this; + } + + public MapBuilder put(Value key, Value value) + { + map.put(key, value); + return this; + } + + public MapBuilder putAll(Iterable> entries) + { + for (Map.Entry entry : entries) { + put(entry.getKey(), entry.getValue()); + } + return this; + } + + public MapBuilder putAll(Map map) + { + for (Map.Entry entry : map.entrySet()) { + put(entry); + } + return this; + } } - /** - * Hide the default constructor to forbid instantiation of this class - */ - protected ValueFactory() { + public static ImmutableExtensionValue newExtension(byte type, byte[] data) + { + return new ImmutableExtensionValueImpl(type, data); } } diff --git a/msgpack-core/src/main/java/org/msgpack/value/ValueRef.java b/msgpack-core/src/main/java/org/msgpack/value/ValueRef.java deleted file mode 100644 index 0043e4392..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/ValueRef.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.msgpack.value; - -import org.msgpack.core.MessagePacker; -import org.msgpack.core.MessageTypeException; - -import java.io.IOException; - -/** - * Reference to the value - */ -public interface ValueRef { - public ValueType getValueType(); - - public NilValue asNil() throws MessageTypeException; - public BooleanValue asBoolean() throws MessageTypeException; - public NumberValue asNumber() throws MessageTypeException; - public IntegerValue asInteger() throws MessageTypeException; - public FloatValue asFloat() throws MessageTypeException; - public BinaryValue asBinary() throws MessageTypeException; - public StringValue asString() throws MessageTypeException; - public RawValue asRaw() throws MessageTypeException; - public ExtendedValue asExtended() throws MessageTypeException; - - public ArrayCursor getArrayCursor() throws MessageTypeException; - public MapCursor getMapCursor() throws MessageTypeException; - - public boolean isNil(); - public boolean isBoolean(); - public boolean isNumber(); - public boolean isInteger(); - public boolean isFloat(); - public boolean isBinary(); - public boolean isString(); - public boolean isRaw(); - public boolean isArray(); - public boolean isMap(); - public boolean isExtended(); - - public void writeTo(MessagePacker packer) throws IOException; - - public void accept(ValueVisitor visitor); - - /** - * Create an immutable value from this reference - * @return - */ - public Value toValue(); - - /** - * Test whether this value is a reference of not. - * @return true if this value is reference, otherwise false. - */ - public boolean isRef(); -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/ValueType.java b/msgpack-core/src/main/java/org/msgpack/value/ValueType.java index 5e1b27b99..715ebbd18 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/ValueType.java +++ b/msgpack-core/src/main/java/org/msgpack/value/ValueType.java @@ -15,13 +15,11 @@ // package org.msgpack.value; -import org.msgpack.core.MessageFormat; - /** * MessageTypeFamily is a group of {@link org.msgpack.core.MessageFormat}s */ -public enum ValueType { - +public enum ValueType +{ NIL(false, false), BOOLEAN(false, false), INTEGER(true, false), @@ -30,77 +28,69 @@ public enum ValueType { BINARY(false, true), ARRAY(false, false), MAP(false, false), - EXTENDED(false, true); - + EXTENSION(false, true); private final boolean numberType; private final boolean rawType; - private final int bitMask; - private ValueType(boolean numberType, boolean rawType) { + private ValueType(boolean numberType, boolean rawType) + { this.numberType = numberType; this.rawType = rawType; - this.bitMask = 1 << this.ordinal(); - } - - public int getBitMask() { - return bitMask; - } - - public boolean isTypeOf(int bitMask) { - return (this.bitMask & bitMask) != 0; } - public boolean isNilType() { + public boolean isNilType() + { return this == NIL; } - public boolean isBooleanType() { + public boolean isBooleanType() + { return this == BOOLEAN; } - public boolean isNumberType() { + public boolean isNumberType() + { return numberType; } - public boolean isIntegerType() { + public boolean isIntegerType() + { return this == INTEGER; } - public boolean isFloatType() { + public boolean isFloatType() + { return this == FLOAT; } - public boolean isRawType() { + public boolean isRawType() + { return rawType; } - public boolean isStringType() { + public boolean isStringType() + { return this == STRING; } - public boolean isBinaryType() { + public boolean isBinaryType() + { return this == BINARY; } - public boolean isArrayType() { + public boolean isArrayType() + { return this == ARRAY; } - public boolean isMapType() { + public boolean isMapType() + { return this == MAP; } - public boolean isExtendedType() { - return this == EXTENDED; - } - - public static ValueType valueOf(byte b) { - return MessageFormat.valueOf(b).getValueType(); + public boolean isExtensionType() + { + return this == EXTENSION; } - - public String toTypeName() { - return this.name().substring(0, 1) + this.name().substring(1).toLowerCase(); - } - } diff --git a/msgpack-core/src/main/java/org/msgpack/value/Variable.java b/msgpack-core/src/main/java/org/msgpack/value/Variable.java new file mode 100644 index 000000000..59e6930cb --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/Variable.java @@ -0,0 +1,1235 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value; + +import org.msgpack.core.MessageFormat; +import org.msgpack.core.MessageIntegerOverflowException; +import org.msgpack.core.MessagePack; +import org.msgpack.core.MessagePacker; +import org.msgpack.core.MessageStringCodingException; +import org.msgpack.core.MessageTypeCastException; +import org.msgpack.value.impl.ImmutableBigIntegerValueImpl; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class Variable + implements Value +{ + private abstract class AbstractValueAccessor + implements Value + { + @Override + public boolean isNilValue() + { + return getValueType().isNilType(); + } + + @Override + public boolean isBooleanValue() + { + return getValueType().isBooleanType(); + } + + @Override + public boolean isNumberValue() + { + return getValueType().isNumberType(); + } + + @Override + public boolean isIntegerValue() + { + return getValueType().isIntegerType(); + } + + @Override + public boolean isFloatValue() + { + return getValueType().isFloatType(); + } + + @Override + public boolean isRawValue() + { + return getValueType().isRawType(); + } + + @Override + public boolean isBinaryValue() + { + return getValueType().isBinaryType(); + } + + @Override + public boolean isStringValue() + { + return getValueType().isStringType(); + } + + @Override + public boolean isArrayValue() + { + return getValueType().isArrayType(); + } + + @Override + public boolean isMapValue() + { + return getValueType().isMapType(); + } + + @Override + public boolean isExtensionValue() + { + return getValueType().isExtensionType(); + } + + @Override + public NilValue asNilValue() + { + throw new MessageTypeCastException(); + } + + @Override + public BooleanValue asBooleanValue() + { + throw new MessageTypeCastException(); + } + + @Override + public NumberValue asNumberValue() + { + throw new MessageTypeCastException(); + } + + @Override + public IntegerValue asIntegerValue() + { + throw new MessageTypeCastException(); + } + + @Override + public FloatValue asFloatValue() + { + throw new MessageTypeCastException(); + } + + @Override + public RawValue asRawValue() + { + throw new MessageTypeCastException(); + } + + @Override + public BinaryValue asBinaryValue() + { + throw new MessageTypeCastException(); + } + + @Override + public StringValue asStringValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ArrayValue asArrayValue() + { + throw new MessageTypeCastException(); + } + + @Override + public MapValue asMapValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ExtensionValue asExtensionValue() + { + throw new MessageTypeCastException(); + } + + @Override + public boolean equals(Object obj) + { + return Variable.this.equals(obj); + } + + @Override + public int hashCode() + { + return Variable.this.hashCode(); + } + + @Override + public String toJson() + { + return Variable.this.toJson(); + } + + @Override + public String toString() + { + return Variable.this.toString(); + } + } + + public static enum Type + { + NULL(ValueType.NIL), + BOOLEAN(ValueType.BOOLEAN), + LONG(ValueType.INTEGER), + BIG_INTEGER(ValueType.INTEGER), + DOUBLE(ValueType.FLOAT), + BYTE_ARRAY(ValueType.BINARY), + RAW_STRING(ValueType.STRING), + LIST(ValueType.ARRAY), + MAP(ValueType.MAP), + EXTENSION(ValueType.EXTENSION); + + private final ValueType valueType; + + private Type(ValueType valueType) + { + this.valueType = valueType; + } + + public ValueType getValueType() + { + return valueType; + } + } + + private final NilValueAccessor nilAccessor = new NilValueAccessor(); + private final BooleanValueAccessor booleanAccessor = new BooleanValueAccessor(); + private final IntegerValueAccessor integerAccessor = new IntegerValueAccessor(); + private final FloatValueAccessor floatAccessor = new FloatValueAccessor(); + private final BinaryValueAccessor binaryAccessor = new BinaryValueAccessor(); + private final StringValueAccessor stringAccessor = new StringValueAccessor(); + private final ArrayValueAccessor arrayAccessor = new ArrayValueAccessor(); + private final MapValueAccessor mapAccessor = new MapValueAccessor(); + private final ExtensionValueAccessor extensionAccessor = new ExtensionValueAccessor(); + + private Type type; + + private long longValue; + private double doubleValue; + private Object objectValue; + + private AbstractValueAccessor accessor; + + public Variable() + { + setNilValue(); + } + + //// + // NilValue + // + + public Variable setNilValue() + { + this.type = Type.NULL; + this.accessor = nilAccessor; + return this; + } + + private class NilValueAccessor + extends AbstractValueAccessor + implements NilValue + { + @Override + public ValueType getValueType() + { + return ValueType.NIL; + } + + @Override + public NilValue asNilValue() + { + return this; + } + + @Override + public ImmutableNilValue immutableValue() + { + return ValueFactory.newNil(); + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packNil(); + } + } + + //// + // BooleanValue + // + + public Variable setBooleanValue(boolean v) + { + this.type = Type.BOOLEAN; + this.accessor = booleanAccessor; + this.longValue = (v ? 1L : 0L); + return this; + } + + private class BooleanValueAccessor + extends AbstractValueAccessor + implements BooleanValue + { + @Override + public ValueType getValueType() + { + return ValueType.BOOLEAN; + } + + @Override + public BooleanValue asBooleanValue() + { + return this; + } + + @Override + public ImmutableBooleanValue immutableValue() + { + return ValueFactory.newBoolean(getBoolean()); + } + + @Override + public boolean getBoolean() + { + return longValue == 1L; + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packBoolean(longValue == 1L); + } + } + + //// + // NumberValue + // IntegerValue + // FloatValue + // + + private static final BigInteger LONG_MIN = BigInteger.valueOf((long) Long.MIN_VALUE); + private static final BigInteger LONG_MAX = BigInteger.valueOf((long) Long.MAX_VALUE); + private static final long BYTE_MIN = (long) Byte.MIN_VALUE; + private static final long BYTE_MAX = (long) Byte.MAX_VALUE; + private static final long SHORT_MIN = (long) Short.MIN_VALUE; + private static final long SHORT_MAX = (long) Short.MAX_VALUE; + private static final long INT_MIN = (long) Integer.MIN_VALUE; + private static final long INT_MAX = (long) Integer.MAX_VALUE; + + private abstract class AbstractNumberValueAccessor + extends AbstractValueAccessor + implements NumberValue + { + @Override + public NumberValue asNumberValue() + { + return this; + } + + @Override + public byte toByte() + { + if (type == Type.BIG_INTEGER) { + return ((BigInteger) objectValue).byteValue(); + } + return (byte) longValue; + } + + @Override + public short toShort() + { + if (type == Type.BIG_INTEGER) { + return ((BigInteger) objectValue).shortValue(); + } + return (short) longValue; + } + + @Override + public int toInt() + { + if (type == Type.BIG_INTEGER) { + return ((BigInteger) objectValue).intValue(); + } + return (int) longValue; + } + + @Override + public long toLong() + { + if (type == Type.BIG_INTEGER) { + return ((BigInteger) objectValue).longValue(); + } + return (long) longValue; + } + + @Override + public BigInteger toBigInteger() + { + if (type == Type.BIG_INTEGER) { + return (BigInteger) objectValue; + } + else if (type == Type.DOUBLE) { + return new BigDecimal(doubleValue).toBigInteger(); + } + return BigInteger.valueOf(longValue); + } + + @Override + public float toFloat() + { + if (type == Type.BIG_INTEGER) { + return ((BigInteger) objectValue).floatValue(); + } + else if (type == Type.DOUBLE) { + return (float) doubleValue; + } + return (float) longValue; + } + + @Override + public double toDouble() + { + if (type == Type.BIG_INTEGER) { + return ((BigInteger) objectValue).doubleValue(); + } + else if (type == Type.DOUBLE) { + return doubleValue; + } + return (double) longValue; + } + } + + //// + // IntegerValue + // + + public Variable setIntegerValue(long v) + { + this.type = Type.LONG; + this.accessor = integerAccessor; + this.longValue = v; + return this; + } + + public Variable setIntegerValue(BigInteger v) + { + if (0 <= v.compareTo(LONG_MIN) && v.compareTo(LONG_MAX) <= 0) { + this.type = Type.LONG; + this.accessor = integerAccessor; + this.longValue = v.longValue(); + } + else { + this.type = Type.BIG_INTEGER; + this.accessor = integerAccessor; + this.objectValue = v; + } + return this; + } + + private class IntegerValueAccessor + extends AbstractNumberValueAccessor + implements IntegerValue + { + @Override + public ValueType getValueType() + { + return ValueType.INTEGER; + } + + @Override + public IntegerValue asIntegerValue() + { + return this; + } + + @Override + public ImmutableIntegerValue immutableValue() + { + if (type == Type.BIG_INTEGER) { + return ValueFactory.newInteger((BigInteger) objectValue); + } + return ValueFactory.newInteger(longValue); + } + + @Override + public boolean isInByteRange() + { + if (type == Type.BIG_INTEGER) { + return false; + } + return BYTE_MIN <= longValue && longValue <= BYTE_MAX; + } + + @Override + public boolean isInShortRange() + { + if (type == Type.BIG_INTEGER) { + return false; + } + return SHORT_MIN <= longValue && longValue <= SHORT_MAX; + } + + @Override + public boolean isInIntRange() + { + if (type == Type.BIG_INTEGER) { + return false; + } + return INT_MIN <= longValue && longValue <= INT_MAX; + } + + @Override + public boolean isInLongRange() + { + if (type == Type.BIG_INTEGER) { + return false; + } + return true; + } + + @Override + public MessageFormat mostSuccinctMessageFormat() + { + return ImmutableBigIntegerValueImpl.mostSuccinctMessageFormat(this); + } + + @Override + public byte asByte() + { + if (!isInByteRange()) { + throw new MessageIntegerOverflowException(longValue); + } + return (byte) longValue; + } + + @Override + public short asShort() + { + if (!isInByteRange()) { + throw new MessageIntegerOverflowException(longValue); + } + return (short) longValue; + } + + @Override + public int asInt() + { + if (!isInIntRange()) { + throw new MessageIntegerOverflowException(longValue); + } + return (int) longValue; + } + + @Override + public long asLong() + { + if (!isInLongRange()) { + throw new MessageIntegerOverflowException(longValue); + } + return longValue; + } + + @Override + public BigInteger asBigInteger() + { + if (type == Type.BIG_INTEGER) { + return (BigInteger) objectValue; + } + else { + return BigInteger.valueOf(longValue); + } + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + if (type == Type.BIG_INTEGER) { + pk.packBigInteger((BigInteger) objectValue); + } + else { + pk.packLong(longValue); + } + } + } + + //// + // FloatValue + // + + public Variable setFloatValue(double v) + { + this.type = Type.DOUBLE; + this.accessor = floatAccessor; + this.doubleValue = v; + this.longValue = (long) v; // AbstractNumberValueAccessor uses toLong + return this; + } + + public Variable setFloatValue(float v) + { + this.type = Type.DOUBLE; + this.accessor = floatAccessor; + this.longValue = (long) v; // AbstractNumberValueAccessor uses toLong + return this; + } + + private class FloatValueAccessor + extends AbstractNumberValueAccessor + implements FloatValue + { + @Override + public FloatValue asFloatValue() + { + return this; + } + + @Override + public ImmutableFloatValue immutableValue() + { + return ValueFactory.newFloat(doubleValue); + } + + @Override + public ValueType getValueType() + { + return ValueType.FLOAT; + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packDouble(doubleValue); + } + } + + //// + // RawValue + // BinaryValue + // StringValue + // + + private abstract class AbstractRawValueAccessor + extends AbstractValueAccessor + implements RawValue + { + @Override + public RawValue asRawValue() + { + return this; + } + + @Override + public byte[] asByteArray() + { + return (byte[]) objectValue; + } + + @Override + public ByteBuffer asByteBuffer() + { + return ByteBuffer.wrap(asByteArray()); + } + + @Override + public String asString() + { + byte[] raw = (byte[]) objectValue; + try { + CharsetDecoder reportDecoder = MessagePack.UTF8.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + return reportDecoder.decode(ByteBuffer.wrap(raw)).toString(); + } + catch (CharacterCodingException ex) { + throw new MessageStringCodingException(ex); + } + } + + // override for performance optimization + @Override + public String toString() + { + byte[] raw = (byte[]) objectValue; + try { + CharsetDecoder reportDecoder = MessagePack.UTF8.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + return reportDecoder.decode(ByteBuffer.wrap(raw)).toString(); + } + catch (CharacterCodingException ex) { + throw new MessageStringCodingException(ex); + } + } + } + + //// + // BinaryValue + // + + public Variable setBinaryValue(byte[] v) + { + this.type = Type.BYTE_ARRAY; + this.accessor = binaryAccessor; + this.objectValue = v; + return this; + } + + private class BinaryValueAccessor + extends AbstractRawValueAccessor + implements BinaryValue + { + @Override + public ValueType getValueType() + { + return ValueType.BINARY; + } + + @Override + public BinaryValue asBinaryValue() + { + return this; + } + + @Override + public ImmutableBinaryValue immutableValue() + { + return ValueFactory.newBinary(asByteArray()); + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + byte[] data = (byte[]) objectValue; + pk.packBinaryHeader(data.length); + pk.writePayload(data); + } + } + + //// + // StringValue + // + + public Variable setStringValue(String v) + { + return setStringValue(v.getBytes(MessagePack.UTF8)); + } + + public Variable setStringValue(byte[] v) + { + this.type = Type.RAW_STRING; + this.accessor = stringAccessor; + this.objectValue = v; + return this; + } + + private class StringValueAccessor + extends AbstractRawValueAccessor + implements StringValue + { + @Override + public ValueType getValueType() + { + return ValueType.STRING; + } + + @Override + public StringValue asStringValue() + { + return this; + } + + @Override + public ImmutableStringValue immutableValue() + { + return ValueFactory.newString((byte[]) objectValue); + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + byte[] data = (byte[]) objectValue; + pk.packRawStringHeader(data.length); + pk.writePayload(data); + } + } + + //// + // ArrayValue + // + + public Variable setArrayValue(List v) + { + this.type = Type.LIST; + this.accessor = arrayAccessor; + this.objectValue = v; + return this; + } + + private class ArrayValueAccessor + extends AbstractValueAccessor + implements ArrayValue + { + @Override + public ValueType getValueType() + { + return ValueType.ARRAY; + } + + @Override + public ArrayValue asArrayValue() + { + return this; + } + + @Override + public ImmutableArrayValue immutableValue() + { + return ValueFactory.newArray(list()); + } + + @Override + public int size() + { + return list().size(); + } + + @Override + public Value get(int index) + { + return list().get(index); + } + + @Override + public Value getOrNilValue(int index) + { + List l = list(); + if (l.size() < index && index >= 0) { + return ValueFactory.newNil(); + } + return l.get(index); + } + + @Override + public Iterator iterator() + { + return list().iterator(); + } + + @Override + @SuppressWarnings("unchecked") + public List list() + { + return (List) objectValue; + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + List l = list(); + pk.packArrayHeader(l.size()); + for (Value e : l) { + e.writeTo(pk); + } + } + } + + //// + // MapValue + // + + public Variable setMapValue(Map v) + { + this.type = Type.MAP; + this.accessor = mapAccessor; + this.objectValue = v; + return this; + } + + private class MapValueAccessor + extends AbstractValueAccessor + implements MapValue + { + @Override + public ValueType getValueType() + { + return ValueType.MAP; + } + + @Override + public MapValue asMapValue() + { + return this; + } + + @Override + public ImmutableMapValue immutableValue() + { + return ValueFactory.newMap(map()); + } + + @Override + public int size() + { + return map().size(); + } + + @Override + public Set keySet() + { + return map().keySet(); + } + + @Override + public Set> entrySet() + { + return map().entrySet(); + } + + @Override + public Collection values() + { + return map().values(); + } + + @Override + public Value[] getKeyValueArray() + { + Map v = map(); + Value[] kvs = new Value[v.size() * 2]; + Iterator> ite = v.entrySet().iterator(); + int i = 0; + while (ite.hasNext()) { + Map.Entry pair = ite.next(); + kvs[i] = pair.getKey(); + i++; + kvs[i] = pair.getValue(); + i++; + } + return kvs; + } + + @SuppressWarnings("unchecked") + public Map map() + { + return (Map) objectValue; + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + Map m = map(); + pk.packArrayHeader(m.size()); + for (Map.Entry pair : m.entrySet()) { + pair.getKey().writeTo(pk); + pair.getValue().writeTo(pk); + } + } + } + + //// + // ExtensionValue + // + public Variable setExtensionValue(byte type, byte[] data) + { + this.type = Type.EXTENSION; + this.accessor = extensionAccessor; + this.objectValue = ValueFactory.newExtension(type, data); + return this; + } + + private class ExtensionValueAccessor + extends AbstractValueAccessor + implements ExtensionValue + { + @Override + public ValueType getValueType() + { + return ValueType.EXTENSION; + } + + @Override + public ExtensionValue asExtensionValue() + { + return this; + } + + @Override + public ImmutableExtensionValue immutableValue() + { + return (ImmutableExtensionValue) objectValue; + } + + @Override + public byte getType() + { + return ((ImmutableExtensionValue) objectValue).getType(); + } + + @Override + public byte[] getData() + { + return ((ImmutableExtensionValue) objectValue).getData(); + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + ((ImmutableExtensionValue) objectValue).writeTo(pk); + } + } + + //// + // Value + // + + @Override + public ImmutableValue immutableValue() + { + return accessor.immutableValue(); + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + accessor.writeTo(pk); + } + + @Override + public int hashCode() + { + return immutableValue().hashCode(); // TODO optimize + } + + @Override + public boolean equals(Object o) + { + return immutableValue().equals(o); // TODO optimize + } + + @Override + public String toJson() + { + return immutableValue().toJson(); // TODO optimize + } + + @Override + public String toString() + { + return immutableValue().toString(); // TODO optimize + } + + @Override + public ValueType getValueType() + { + return type.getValueType(); + } + + @Override + public boolean isNilValue() + { + return getValueType().isNilType(); + } + + @Override + public boolean isBooleanValue() + { + return getValueType().isBooleanType(); + } + + @Override + public boolean isNumberValue() + { + return getValueType().isNumberType(); + } + + @Override + public boolean isIntegerValue() + { + return getValueType().isIntegerType(); + } + + @Override + public boolean isFloatValue() + { + return getValueType().isFloatType(); + } + + @Override + public boolean isRawValue() + { + return getValueType().isRawType(); + } + + @Override + public boolean isBinaryValue() + { + return getValueType().isBinaryType(); + } + + @Override + public boolean isStringValue() + { + return getValueType().isStringType(); + } + + @Override + public boolean isArrayValue() + { + return getValueType().isArrayType(); + } + + @Override + public boolean isMapValue() + { + return getValueType().isMapType(); + } + + @Override + public boolean isExtensionValue() + { + return getValueType().isExtensionType(); + } + + @Override + public NilValue asNilValue() + { + if (!isNilValue()) { + throw new MessageTypeCastException(); + } + return (NilValue) accessor; + } + + @Override + public BooleanValue asBooleanValue() + { + if (!isBooleanValue()) { + throw new MessageTypeCastException(); + } + return (BooleanValue) accessor; + } + + @Override + public NumberValue asNumberValue() + { + if (!isNumberValue()) { + throw new MessageTypeCastException(); + } + return (NumberValue) accessor; + } + + @Override + public IntegerValue asIntegerValue() + { + if (!isIntegerValue()) { + throw new MessageTypeCastException(); + } + return (IntegerValue) accessor; + } + + @Override + public FloatValue asFloatValue() + { + if (!isFloatValue()) { + throw new MessageTypeCastException(); + } + return (FloatValue) accessor; + } + + @Override + public RawValue asRawValue() + { + if (!isRawValue()) { + throw new MessageTypeCastException(); + } + return (RawValue) accessor; + } + + @Override + public BinaryValue asBinaryValue() + { + if (!isBinaryValue()) { + throw new MessageTypeCastException(); + } + return (BinaryValue) accessor; + } + + @Override + public StringValue asStringValue() + { + if (!isStringValue()) { + throw new MessageTypeCastException(); + } + return (StringValue) accessor; + } + + @Override + public ArrayValue asArrayValue() + { + if (!isArrayValue()) { + throw new MessageTypeCastException(); + } + return (ArrayValue) accessor; + } + + @Override + public MapValue asMapValue() + { + if (!isMapValue()) { + throw new MessageTypeCastException(); + } + return (MapValue) accessor; + } + + @Override + public ExtensionValue asExtensionValue() + { + if (!isExtensionValue()) { + throw new MessageTypeCastException(); + } + return (ExtensionValue) accessor; + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/holder/ExtHolder.java b/msgpack-core/src/main/java/org/msgpack/value/holder/ExtHolder.java deleted file mode 100644 index 104e74210..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/holder/ExtHolder.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.msgpack.value.holder; - -import org.msgpack.core.MessagePack; -import org.msgpack.core.MessagePacker; -import org.msgpack.core.MessageStringCodingException; -import org.msgpack.core.buffer.MessageBuffer; -import org.msgpack.value.*; -import org.msgpack.value.impl.AbstractValueRef; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Created on 6/13/14. - */ -public class ExtHolder extends AbstractValueRef implements ExtendedValue { - - private int extType; - private MessageBuffer buffer; - - - public void setExtType(int extType, MessageBuffer buffer) { - this.extType = extType; - this.buffer = buffer; - } - - @Override - public int getExtType() { - return extType; - } - - @Override - public ValueType getValueType() { - return ValueType.EXTENDED; - } - - @Override - public void writeTo(MessagePacker packer) throws IOException { - packer.packExtendedTypeHeader(extType, buffer.size()).writePayload(buffer.toByteBuffer()); - } - - @Override - public void accept(ValueVisitor visitor) { - visitor.visitExtended(this); - } - @Override - public ExtendedValue toValue() { - // clone the buffer contents - return ValueFactory.newExtendedValue(extType, buffer.toByteArray()); - } - - @Override - public byte[] toByteArray() { - return buffer.toByteArray(); - } - @Override - public ByteBuffer toByteBuffer() { - return buffer.toByteBuffer(); - } - @Override - public MessageBuffer toMessageBuffer() { - return buffer; - } - - @Override - public String toString() throws MessageStringCodingException { - return new String(buffer.toByteArray(), MessagePack.UTF8); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/holder/FloatHolder.java b/msgpack-core/src/main/java/org/msgpack/value/holder/FloatHolder.java deleted file mode 100644 index fb369afdb..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/holder/FloatHolder.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.msgpack.value.holder; - -import org.msgpack.core.MessageFloatOverflowException; -import org.msgpack.core.MessageOverflowException; -import org.msgpack.core.MessagePacker; -import org.msgpack.value.*; -import org.msgpack.value.impl.AbstractValueRef; - -import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; - -/** - * Created on 6/3/14. - */ -public class FloatHolder extends AbstractValueRef implements FloatValue { - - public static enum Type { - FLOAT, - DOUBLE - } - - private Type tpe; - private double value; - - @Override - public boolean isValidByte() { - return ((double) ((byte) value)) == value; - } - @Override - public boolean isValidShort() { - return ((double) ((short) value)) == value; - } - @Override - public boolean isValidInt() { - return ((double) ((int) value)) == value; - } - @Override - public boolean isValidLong() { - long l = (long) value; - return ((double) l) == value && l != Long.MAX_VALUE; - } - @Override - public boolean isWhole() { - long l = (long) value; - return ((double) l == value) || l == Long.MAX_VALUE && value < Double.POSITIVE_INFINITY || l == Long.MIN_VALUE && value > Double.NEGATIVE_INFINITY; - } - @Override - public byte toByte() { - return (byte) value; - } - - @Override - public short toShort() { - return (short) value; - } - - @Override - public int toInt() { - return (int) value; - } - - @Override - public long toLong() { - return (long) value; - } - - @Override - public BigInteger toBigInteger() { - return new BigDecimal(value).toBigInteger(); - } - - @Override - public float toFloat() { - return (float) value; - } - - @Override - public double toDouble() { - return value; - } - @Override - public byte asByte() throws MessageOverflowException { - if(!isValidByte()) - throw new MessageFloatOverflowException(value); - return (byte) value; - } - @Override - public short asShort() throws MessageOverflowException { - if(!isValidShort()) - throw new MessageFloatOverflowException(value); - return (short) value; - } - @Override - public int asInt() throws MessageOverflowException { - if(!isValidInt()) - throw new MessageFloatOverflowException(value); - return (int) value; - } - @Override - public long asLong() throws MessageOverflowException { - if(!isValidLong()) - throw new MessageFloatOverflowException(value); - return (long) value; - } - @Override - public BigInteger asBigInteger() throws MessageOverflowException { - if(!isWhole()) - throw new MessageFloatOverflowException(value); - return new BigDecimal(value).toBigInteger(); - } - - @Override - public ValueType getValueType() { - return ValueType.FLOAT; - } - @Override - public void writeTo(MessagePacker pk) throws IOException { - switch(tpe) { - case FLOAT: - pk.packFloat(toFloat()); - break; - case DOUBLE: - pk.packDouble(value); - break; - } - } - - @Override - public void accept(ValueVisitor visitor) { - visitor.visitFloat(this); - } - @Override - public FloatValue toValue() { - switch(tpe) { - case FLOAT: - return ValueFactory.newFloat(toFloat()); - case DOUBLE: - return ValueFactory.newDouble(toDouble()); - default: - throw new IllegalStateException("cannot reach here"); - } - } - - public Type getType() { - return tpe; - } - - public void setFloat(float v) { - tpe = Type.FLOAT; - value = v; - } - - public void setDouble(double v) { - tpe = Type.DOUBLE; - value = v; - } - - @Override - public int hashCode() { - long v = Double.doubleToLongBits(value); - return (int) (v ^ (v >>> 32)); - } - - @Override - public String toString() { - switch(tpe) { - case FLOAT: - return Float.toString((float) value); - case DOUBLE: - return Double.toString(value); - default: - throw new IllegalStateException("cannot reach here"); - } - } - - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/holder/IntegerHolder.java b/msgpack-core/src/main/java/org/msgpack/value/holder/IntegerHolder.java deleted file mode 100644 index f7aab07fc..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/holder/IntegerHolder.java +++ /dev/null @@ -1,268 +0,0 @@ -package org.msgpack.value.holder; - -import org.msgpack.core.MessageIntegerOverflowException; -import org.msgpack.core.MessagePacker; -import org.msgpack.core.MessageTypeException; -import org.msgpack.value.*; -import org.msgpack.value.impl.AbstractValueRef; - -import java.io.IOException; -import java.math.BigInteger; -import static org.msgpack.core.NumberUtil.*; - -/** - * Union of integer values - */ -public class IntegerHolder extends AbstractValueRef implements IntegerValue { - - @Override - public ValueType getValueType() { - return ValueType.INTEGER; - } - @Override - public void writeTo(MessagePacker packer) throws IOException { - switch(tpe) { - case BIG_INTEGER: - packer.packBigInteger(biValue); - break; - default: - packer.packLong(longValue); - break; - } - } - - @Override - public IntegerValue asInteger() throws MessageTypeException { - return this; - } - - @Override - public void accept(ValueVisitor visitor) { - visitor.visitInteger(this); - } - - @Override - public IntegerValue toValue() { - switch(tpe){ - case BYTE: - return ValueFactory.newByte(toByte()); - case SHORT: - return ValueFactory.newShort(toShort()); - case INT: - return ValueFactory.newInt(toInt()); - case LONG: - return ValueFactory.newLong(toLong()); - case BIG_INTEGER: - return ValueFactory.newBigInteger(toBigInteger()); - default: - throw new IllegalStateException("cannot reach here"); - } - } - - public static enum Type { - BYTE, - SHORT, - INT, - LONG, - BIG_INTEGER - } - - private Type tpe; - private long longValue; - private BigInteger biValue; - - public Type getType() { - return tpe; - } - - public void setByte(byte v){ - tpe = Type.BYTE; - longValue = v; - } - public void setShort(short v) { - tpe = Type.SHORT; - longValue = v; - } - public void setInt(int v) { - tpe = Type.INT; - longValue = v; - } - public void setLong(long v) { - tpe = Type.LONG; - longValue = v; - } - public void setBigInteger(BigInteger v) { - tpe = Type.BIG_INTEGER; - biValue = v; - } - - private RuntimeException failure() { - return new IllegalStateException(); - } - - public boolean isBigInteger() { - return tpe == Type.BIG_INTEGER; - } - - @Override - public boolean isValidByte() { - return tpe == Type.BYTE; - } - @Override - public boolean isValidShort() { - return tpe.ordinal() <= Type.SHORT.ordinal(); - } - @Override - public boolean isValidInt() { - return tpe.ordinal() <= Type.INT.ordinal(); - } - @Override - public boolean isValidLong() { - return tpe.ordinal() <= Type.LONG.ordinal(); - } - - @Override - public boolean isWhole() { - return true; - } - - public byte toByte() { - return isBigInteger() ? biValue.byteValue() : (byte) longValue; - } - - public short toShort() { - return isBigInteger() ? biValue.shortValue() : (short) longValue; - } - - public int toInt() { - return isBigInteger() ? biValue.intValue() : (int) longValue; - } - - public long toLong(){ - return isBigInteger() ? biValue.longValue() : longValue; - } - - public BigInteger toBigInteger() { - return isBigInteger() ? biValue : BigInteger.valueOf(longValue); - } - @Override - public float toFloat() { - return isBigInteger() ? biValue.floatValue() : (float) longValue; - } - @Override - public double toDouble() { - return isBigInteger() ? biValue.doubleValue() : (double) longValue; - } - - - @Override - public byte asByte() throws MessageIntegerOverflowException { - switch(tpe) { - case BYTE: - return (byte) longValue; - case SHORT: - case INT: - case LONG: - if(LongUtil.isValidByte(longValue)) { - return (byte) longValue; - } - else { - throw new MessageIntegerOverflowException(longValue); - } - case BIG_INTEGER: - if(LongUtil.isValidByte(biValue)) { - return biValue.byteValue(); - } - else { - throw new MessageIntegerOverflowException(biValue); - } - default: - throw failure(); - } - } - - - @Override - public short asShort() throws MessageIntegerOverflowException { - switch(tpe) { - case BYTE: - case SHORT: - return (short) longValue; - case INT: - case LONG: - if(LongUtil.isValidShort(longValue)) { - return (short) longValue; - } - else { - throw new MessageIntegerOverflowException(longValue); - } - case BIG_INTEGER: - if(LongUtil.isValidShort(biValue)) { - return biValue.shortValue(); - } - else { - throw new MessageIntegerOverflowException(biValue); - } - default: - throw failure(); - } - } - - - @Override - public int asInt() throws MessageIntegerOverflowException { - switch(tpe) { - case BYTE: - case SHORT: - case INT: - return (int) longValue; - case LONG: - if(LongUtil.isValidInt(longValue)) { - return (int) longValue; - } - else { - throw new MessageIntegerOverflowException(longValue); - } - case BIG_INTEGER: - if(LongUtil.isValidInt(biValue)) { - return biValue.intValue(); - } - else { - throw new MessageIntegerOverflowException(biValue); - } - default: - throw failure(); - } - } - - @Override - public long asLong() throws MessageIntegerOverflowException { - if(isBigInteger()){ - if(LongUtil.isValidLong(biValue)) { - return biValue.longValue(); - } else { - throw new MessageIntegerOverflowException(biValue); - } - } - return longValue; - } - - @Override - public BigInteger asBigInteger() { - return toBigInteger(); - } - - - @Override - public int hashCode() { - return isBigInteger() ? biValue.hashCode() : (int) longValue; - } - - @Override - public String toString() { - return isBigInteger() ? biValue.toString() : Long.toString(longValue); - } - - - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/holder/RawHolder.java b/msgpack-core/src/main/java/org/msgpack/value/holder/RawHolder.java deleted file mode 100644 index 371589957..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/holder/RawHolder.java +++ /dev/null @@ -1,270 +0,0 @@ -package org.msgpack.value.holder; - -import org.msgpack.core.MessagePack; -import org.msgpack.core.MessagePacker; -import org.msgpack.core.MessageStringCodingException; -import org.msgpack.core.buffer.MessageBuffer; -import org.msgpack.value.*; -import org.msgpack.value.impl.AbstractValueRef; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import static org.msgpack.core.MessagePackException.UNREACHABLE; - - -class RawHolderImpl extends AbstractValueRef implements RawValue { - - public static enum Type { - STRING, - BINARY - } - - protected Type tpe; - protected MessageBuffer buf; - - public void setString(MessageBuffer buf) { - this.tpe = Type.STRING; - this.buf = buf; - } - - public void setBinary(MessageBuffer buf) { - this.tpe = Type.BINARY; - this.buf = buf; - } - - public MessageBuffer getBuffer() { return buf; } - - @Override - public byte[] toByteArray() { - switch(tpe) { - case STRING: - case BINARY: - return buf.toByteArray(); - default: - throw UNREACHABLE; - } - } - - @Override - public ByteBuffer toByteBuffer() { - switch(tpe) { - case STRING: - return buf.toByteBuffer(); - default: - throw UNREACHABLE; - } - } - - @Override - public MessageBuffer toMessageBuffer() { - return buf; - } - - @Override - public String toString() throws MessageStringCodingException { - switch(tpe) { - case STRING: - return new String(buf.toByteArray(), MessagePack.UTF8); - case BINARY: - return buf.toHexString(0, buf.size()); - default: - throw UNREACHABLE; - } - } - - - @Override - public ValueType getValueType() { - switch(tpe) { - case STRING: - return ValueType.STRING; - case BINARY: - return ValueType.BINARY; - default: - throw UNREACHABLE; - } - } - - @Override - public void writeTo(MessagePacker packer) throws IOException { - switch(tpe) { - case STRING: - packer.packRawStringHeader(buf.size()).writePayload(buf.toByteBuffer()); - break; - case BINARY: - packer.packBinaryHeader(buf.size()).writePayload(buf.toByteBuffer()); - break; - default: - throw UNREACHABLE; - } - } - - @Override - public void accept(ValueVisitor visitor) { - switch(tpe) { - case STRING: - visitor.visitString(this.asString()); - break; - case BINARY: - visitor.visitBinary(this.asBinary()); - break; - default: - throw UNREACHABLE; - } - } - - @Override - public RawValue toValue() { - switch(tpe) { - case STRING: - return ValueFactory.newRawString(buf.toByteArray()); - case BINARY: - return ValueFactory.newBinary(buf.toByteArray()); - default: - throw UNREACHABLE; - } - } - -} - - -/** - * Holder of the raw values - */ -public class RawHolder extends RawHolderImpl { - - private static class StringValueWrap extends RawHolderImpl implements StringValue { - public StringValue toValue() { - return ValueFactory.newRawString(buf.toByteArray()); - } - } - - private static class BinaryValueWrap extends RawHolderImpl implements BinaryValue { - public BinaryValue toValue() { - return ValueFactory.newBinary(buf.toByteArray()); - } - } - - private StringValueWrap stringWrap = new StringValueWrap(); - private BinaryValueWrap binaryWrap = new BinaryValueWrap(); - - @Override - public void setString(MessageBuffer buf) { - this.tpe = Type.STRING; - this.buf = buf; - stringWrap.setString(buf); - } - - @Override - public void setBinary(MessageBuffer buf) { - this.tpe = Type.BINARY; - this.buf = buf; - binaryWrap.setBinary(buf); - } - - public MessageBuffer getBuffer() { return buf; } - - @Override - public byte[] toByteArray() { - switch(tpe) { - case STRING: - case BINARY: - return buf.toByteArray(); - default: - throw UNREACHABLE; - } - } - - @Override - public ByteBuffer toByteBuffer() { - switch(tpe) { - case STRING: - return buf.toByteBuffer(); - default: - throw UNREACHABLE; - } - } - - @Override - public MessageBuffer toMessageBuffer() { - return buf; - } - - @Override - public String toString() throws MessageStringCodingException { - switch(tpe) { - case STRING: - return new String(buf.toByteArray(), MessagePack.UTF8); - case BINARY: - return buf.toHexString(0, buf.size()); - default: - throw UNREACHABLE; - } - } - - - @Override - public ValueType getValueType() { - switch(tpe) { - case STRING: - return ValueType.STRING; - case BINARY: - return ValueType.BINARY; - default: - throw UNREACHABLE; - } - } - - @Override - public void writeTo(MessagePacker packer) throws IOException { - switch(tpe) { - case STRING: - packer.packRawStringHeader(buf.size()).writePayload(buf.toByteBuffer()); - break; - case BINARY: - packer.packBinaryHeader(buf.size()).writePayload(buf.toByteBuffer()); - break; - default: - throw UNREACHABLE; - } - } - - @Override - public void accept(ValueVisitor visitor) { - switch(tpe) { - case STRING: - visitor.visitString(this.asString()); - break; - case BINARY: - visitor.visitBinary(this.asBinary()); - break; - default: - throw UNREACHABLE; - } - } - - @Override - public RawValue toValue() { - switch(tpe) { - case STRING: - return ValueFactory.newRawString(buf.toByteArray()); - case BINARY: - return ValueFactory.newBinary(buf.toByteArray()); - default: - throw UNREACHABLE; - } - } - - - @Override - public StringValue asString() { - return stringWrap; - } - - @Override - public BinaryValue asBinary() { - return binaryWrap; - } - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/holder/ValueHolder.java b/msgpack-core/src/main/java/org/msgpack/value/holder/ValueHolder.java deleted file mode 100644 index fc0a99272..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/holder/ValueHolder.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.msgpack.value.holder; - -import org.msgpack.core.MessageUnpacker; -import org.msgpack.core.buffer.MessageBuffer; -import org.msgpack.value.ValueRef; -import org.msgpack.value.impl.ArrayCursorImpl; -import org.msgpack.value.Value; -import org.msgpack.value.ValueFactory; -import org.msgpack.value.ValueType; -import org.msgpack.value.impl.MapCursorImpl; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import static org.msgpack.core.MessagePackException.UNREACHABLE; - -/** - * This class can hold any message packed value. - */ -public class ValueHolder { - - private ValueType vt; - private IntegerHolder integerHolder = new IntegerHolder(); - private FloatHolder floatHolder = new FloatHolder(); - private RawHolder rawHolder = new RawHolder(); - private ExtHolder extHolder = new ExtHolder(); - private ArrayCursorImpl arrayCursor; - private MapCursorImpl mapCursor; - private ValueRef currentRef; - - public ValueRef getRef() { - if(currentRef == null) { - throw new IllegalStateException("no value is set to this holder"); - } - - return currentRef; - } - - public Value get() { - switch(vt) { - case NIL: - case BOOLEAN: - case INTEGER: - case FLOAT: - case ARRAY: - case MAP: - case EXTENDED: - return getRef().toValue(); - case STRING: - return ValueFactory.newRawString(cloneBuffer(rawHolder.getBuffer())); - case BINARY: - return ValueFactory.newBinary(cloneBuffer(rawHolder.getBuffer())); - default: - throw UNREACHABLE; - } - } - - - private static ByteBuffer cloneBuffer(MessageBuffer buffer) { - return ByteBuffer.wrap(buffer.toByteArray()); - } - - public IntegerHolder getIntegerHolder() { - return integerHolder; - } - - public FloatHolder getFloatHolder() { - return floatHolder; - } - - public void setBoolean(boolean v) { - vt = ValueType.BOOLEAN; - currentRef = ValueFactory.newBoolean(v); - } - - public void setNil() { - vt = ValueType.NIL; - currentRef = ValueFactory.nilValue(); - } - - public void setString(MessageBuffer rawString) { - vt = ValueType.STRING; - rawHolder.setString(rawString); - currentRef = rawHolder.asString(); - } - - public void setBinary(MessageBuffer b) { - vt = ValueType.BINARY; - rawHolder.setBinary(b); - currentRef = rawHolder.asBinary(); - } - - public void setToInteger() { - vt = ValueType.INTEGER; - currentRef = integerHolder; - } - - public void setToFloat() { - vt = ValueType.FLOAT; - currentRef = floatHolder; - } - - public void setExt(int extType, MessageBuffer b) { - vt = ValueType.EXTENDED; - extHolder.setExtType(extType, b); - currentRef = extHolder; - } - - public void prepareArrayCursor(MessageUnpacker unpacker) throws IOException { - vt = ValueType.ARRAY; - - // TODO reusing cursor instances - arrayCursor = new ArrayCursorImpl(new ValueHolder()); - arrayCursor.reset(unpacker); - currentRef = arrayCursor; - } - - public void prepareMapCursor(MessageUnpacker unpacker) throws IOException { - vt = ValueType.MAP; - - // TODO reusing cursor instances - mapCursor = new MapCursorImpl(new ValueHolder()); - mapCursor.reset(unpacker); - currentRef = mapCursor; - } - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableRawValue.java b/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableRawValue.java new file mode 100644 index 000000000..31adb5b1a --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableRawValue.java @@ -0,0 +1,185 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePack; +import org.msgpack.core.MessageStringCodingException; +import org.msgpack.value.ImmutableRawValue; + +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.util.Arrays; + +public abstract class AbstractImmutableRawValue + extends AbstractImmutableValue + implements ImmutableRawValue +{ + protected final byte[] data; + private volatile String decodedStringCache; + private volatile CharacterCodingException codingException; + + public AbstractImmutableRawValue(byte[] data) + { + this.data = data; + } + + public AbstractImmutableRawValue(String string) + { + this.decodedStringCache = string; + this.data = string.getBytes(MessagePack.UTF8); // TODO + } + + @Override + public ImmutableRawValue asRawValue() + { + return this; + } + + @Override + public byte[] asByteArray() + { + return Arrays.copyOf(data, data.length); + } + + @Override + public ByteBuffer asByteBuffer() + { + return ByteBuffer.wrap(data).asReadOnlyBuffer(); + } + + @Override + public String asString() + { + if (decodedStringCache == null) { + decodeString(); + } + if (codingException != null) { + throw new MessageStringCodingException(codingException); + } + else { + return decodedStringCache; + } + } + + @Override + public String toJson() + { + StringBuilder sb = new StringBuilder(); + appendJsonString(sb, toString()); + return sb.toString(); + } + + private void decodeString() + { + synchronized (data) { + if (decodedStringCache != null) { + return; + } + try { + CharsetDecoder reportDecoder = MessagePack.UTF8.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + this.decodedStringCache = reportDecoder.decode(asByteBuffer()).toString(); + } + catch (CharacterCodingException ex) { + try { + CharsetDecoder replaceDecoder = MessagePack.UTF8.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + this.decodedStringCache = replaceDecoder.decode(asByteBuffer()).toString(); + } + catch (CharacterCodingException neverThrown) { + throw new MessageStringCodingException(neverThrown); + } + this.codingException = ex; + } + } + } + + @Override + public String toString() + { + if (decodedStringCache == null) { + decodeString(); + } + return decodedStringCache; + } + + static void appendJsonString(StringBuilder sb, String string) + { + sb.append("\""); + for (int i = 0; i < string.length(); i++) { + char ch = string.charAt(i); + if (ch < 0x20) { + switch (ch) { + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + case '\f': + sb.append("\\f"); + break; + case '\b': + sb.append("\\b"); + break; + default: + // control chars + escapeChar(sb, ch); + break; + } + } + else if (ch <= 0x7f) { + switch (ch) { + case '\\': + sb.append("\\\\"); + break; + case '"': + sb.append("\\\""); + break; + default: + sb.append(ch); + break; + } + } + else if (ch >= 0xd800 && ch <= 0xdfff) { + // surrogates + escapeChar(sb, ch); + } + else { + sb.append(ch); + } + } + sb.append("\""); + } + + private static final char[] HEX_TABLE = "0123456789ABCDEF".toCharArray(); + + private static void escapeChar(StringBuilder sb, int ch) + { + sb.append("\\u"); + sb.append(HEX_TABLE[(ch >> 12) & 0x0f]); + sb.append(HEX_TABLE[(ch >> 8) & 0x0f]); + sb.append(HEX_TABLE[(ch >> 4) & 0x0f]); + sb.append(HEX_TABLE[ch & 0x0f]); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableValue.java b/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableValue.java new file mode 100644 index 000000000..1dae99cf2 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractImmutableValue.java @@ -0,0 +1,166 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessageTypeCastException; +import org.msgpack.value.ImmutableArrayValue; +import org.msgpack.value.ImmutableBinaryValue; +import org.msgpack.value.ImmutableBooleanValue; +import org.msgpack.value.ImmutableExtensionValue; +import org.msgpack.value.ImmutableFloatValue; +import org.msgpack.value.ImmutableIntegerValue; +import org.msgpack.value.ImmutableMapValue; +import org.msgpack.value.ImmutableNilValue; +import org.msgpack.value.ImmutableNumberValue; +import org.msgpack.value.ImmutableRawValue; +import org.msgpack.value.ImmutableStringValue; +import org.msgpack.value.ImmutableValue; + +abstract class AbstractImmutableValue + implements ImmutableValue +{ + @Override + public boolean isNilValue() + { + return getValueType().isNilType(); + } + + @Override + public boolean isBooleanValue() + { + return getValueType().isBooleanType(); + } + + @Override + public boolean isNumberValue() + { + return getValueType().isNumberType(); + } + + @Override + public boolean isIntegerValue() + { + return getValueType().isIntegerType(); + } + + @Override + public boolean isFloatValue() + { + return getValueType().isFloatType(); + } + + @Override + public boolean isRawValue() + { + return getValueType().isRawType(); + } + + @Override + public boolean isBinaryValue() + { + return getValueType().isBinaryType(); + } + + @Override + public boolean isStringValue() + { + return getValueType().isStringType(); + } + + @Override + public boolean isArrayValue() + { + return getValueType().isArrayType(); + } + + @Override + public boolean isMapValue() + { + return getValueType().isMapType(); + } + + @Override + public boolean isExtensionValue() + { + return getValueType().isExtensionType(); + } + + @Override + public ImmutableNilValue asNilValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableBooleanValue asBooleanValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableNumberValue asNumberValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableIntegerValue asIntegerValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableFloatValue asFloatValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableRawValue asRawValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableBinaryValue asBinaryValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableStringValue asStringValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableArrayValue asArrayValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableMapValue asMapValue() + { + throw new MessageTypeCastException(); + } + + @Override + public ImmutableExtensionValue asExtensionValue() + { + throw new MessageTypeCastException(); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractValue.java b/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractValue.java deleted file mode 100644 index 6408ed67a..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractValue.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.value.*; - -/** -* Base implementation of MessagePackValue -*/ -public abstract class AbstractValue extends AbstractValueRef implements Value { - - @Override - public boolean isRef() { return false; } - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractValueRef.java b/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractValueRef.java deleted file mode 100644 index 1e67a6b3c..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/AbstractValueRef.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessageTypeException; -import org.msgpack.value.*; - -/** - * Base implementation of message pack values - */ -public abstract class AbstractValueRef implements ValueRef { - - public boolean isRef() { return true; } - - protected static int NUMBER_TYPE_MASK = (1 << ValueType.INTEGER.ordinal()) - | (1 << ValueType.FLOAT.ordinal()); - protected static int RAW_TYPE_MASK = (1 << ValueType.STRING.ordinal()) - | (1 << ValueType.BINARY.ordinal()); - - protected E as(Class valueClass, ValueType vt) { - return as(valueClass, 1 << vt.ordinal()); - } - protected E as(Class valueClass, int bitMask) { - if(this.getValueType() == null) - throw new MessageTypeException("This value points to nothing"); - if(!this.getValueType().isTypeOf(bitMask)) - throw new MessageTypeException(String.format("Expected %s, but %s", valueClass.getSimpleName(), this.getValueType())); - return valueClass.cast(this); - } - - public NilValue asNil() throws MessageTypeException { return as(NilValue.class, ValueType.NIL); } - public BooleanValue asBoolean() throws MessageTypeException{ return as(BooleanValue.class, ValueType.BOOLEAN); } - public NumberValue asNumber() throws MessageTypeException { return as(NumberValue.class, NUMBER_TYPE_MASK); } - public IntegerValue asInteger() throws MessageTypeException { return as(IntegerValue.class, ValueType.INTEGER); } - public FloatValue asFloat() throws MessageTypeException { return as(FloatValue.class, ValueType.FLOAT); } - public BinaryValue asBinary() throws MessageTypeException { return as(BinaryValue.class, ValueType.BINARY); } - public StringValue asString() throws MessageTypeException { return as(StringValue.class, ValueType.STRING); } - public RawValue asRaw() throws MessageTypeException { return as(RawValue.class, RAW_TYPE_MASK); } - public ArrayValue asArrayValue() throws MessageTypeException { return as(ArrayValue.class, ValueType.ARRAY); } - public MapValue asMapValue() throws MessageTypeException { return as(MapValue.class, ValueType.MAP); } - public ExtendedValue asExtended() throws MessageTypeException { return as(ExtendedValue.class, ValueType.EXTENDED); } - - @Override - public ArrayCursor getArrayCursor() throws MessageTypeException { - throw new MessageTypeException("This value is not an array type"); - } - @Override - public MapCursor getMapCursor() throws MessageTypeException { - throw new MessageTypeException("This value is not a map type"); - } - - public boolean isNil() { return getValueType().isNilType(); } - public boolean isBoolean() { return getValueType().isBooleanType(); } - public boolean isNumber() { return getValueType().isNumberType(); } - public boolean isInteger() { return getValueType().isIntegerType(); } - public boolean isFloat() { return getValueType().isFloatType(); } - public boolean isBinary() { return getValueType().isBinaryType(); } - public boolean isString() { return getValueType().isStringType(); } - public boolean isRaw() { return getValueType().isRawType(); } - public boolean isArray() { return getValueType().isArrayType(); } - public boolean isMap() { return getValueType().isMapType(); } - public boolean isExtended() { return getValueType().isExtendedType(); } - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ArrayCursorImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ArrayCursorImpl.java deleted file mode 100644 index 5a5a1676e..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/ArrayCursorImpl.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.*; -import org.msgpack.value.*; -import org.msgpack.value.holder.ValueHolder; -import static org.msgpack.core.MessagePackException.UNSUPPORTED; - -import java.io.IOException; -import java.util.Iterator; - -/** - * Created on 6/16/14. - */ -public class ArrayCursorImpl extends AbstractValueRef implements ArrayCursor { - - private final ValueHolder valueHolder; - private MessageUnpacker unpacker; - private int cursor = 0; - private int arraySize; - - public ArrayCursorImpl(ValueHolder valueHolder) { - this.valueHolder = valueHolder; - } - - public void reset(MessageUnpacker unpacker) throws IOException { - this.unpacker = unpacker; - this.arraySize = unpacker.unpackArrayHeader(); - this.cursor = 0; - } - - @Override - public int size() { - return arraySize; - } - - @Override - public Iterator iterator() { - return new Iterator() { - @Override - public boolean hasNext() { - return ArrayCursorImpl.this.hasNext(); - } - @Override - public ValueRef next() { - return ArrayCursorImpl.this.next(); - } - @Override - public void remove() { - throw UNSUPPORTED("remove"); - } - }; - } - - public boolean hasNext() { - return cursor < arraySize; - } - - public ValueRef next() { - try { - unpacker.unpackValue(valueHolder); - cursor++; - return valueHolder.getRef(); - } - catch(IOException e) { - throw new MessageFormatException(e); - } - } - - @Override - public void skip() { - try { - unpacker.skipValue(); - cursor++; - } - catch(IOException e) { - throw new MessageFormatException(e); - } - } - - @Override - public void skipAll() { - while(hasNext()) { - skip(); - } - } - - - private void ensureNotTraversed() { - if(cursor != 0) - throw UNSUPPORTED("ArrayCursor is already traversed"); - } - - @Override - public ValueType getValueType() { - return ValueType.ARRAY; - } - - @Override - public ArrayCursor getArrayCursor() throws MessageTypeException { - return this; - } - - @Override - public void writeTo(MessagePacker packer) throws IOException { - ensureNotTraversed(); - packer.packArrayHeader(arraySize); - for(ValueRef v : this) { - packer.packValue(v.toValue()); - } - } - - @Override - public void accept(ValueVisitor visitor) { - visitor.visitArray(toValue()); - } - - @Override - public ArrayValue toValue() { - Value[] arr = new Value[arraySize]; - int i = 0; - for(ValueRef v : this) { - arr[i++] = v.toValue(); - } - return ValueFactory.newArray(arr); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ArrayValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ArrayValueImpl.java deleted file mode 100644 index c54df5992..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/ArrayValueImpl.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessagePacker; -import org.msgpack.core.MessageTypeException; -import org.msgpack.value.*; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Iterator; - -/** -* Created on 5/30/14. -*/ -public class ArrayValueImpl extends AbstractValue implements ArrayValue { - private static ArrayValueImpl EMPTY = new ArrayValueImpl(new Value[0]); - - public static ArrayValue empty() { - return EMPTY; - } - - private int cursor = 0; - private final Value[] array; - - public ArrayValueImpl(Value[] array) { - this.array = array; - } - - public Value get(int index) { - return array[index]; - } - - public Value apply(int index) { - return array[index]; - } - - @Override - public ValueType getValueType() { - return ValueType.ARRAY; - } - - - @Override - public ArrayCursor getArrayCursor() throws MessageTypeException { - return this; - } - - @Override - public void writeTo(MessagePacker pk) throws IOException { - pk.packArrayHeader(array.length); - for(int i = 0; i < array.length; i++) { - array[i].writeTo(pk); - } - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitArray(this); - } - @Override - public ArrayValue toValue() { - return this; - } - - @Override - public boolean equals(Object o) { - if(o == this) { - return true; - } - if(!(o instanceof Value)) { - return false; - } - Value v = (Value) o; - if(!v.isArray()) { - return false; - } - Value[] other = v.asArrayValue().toValueArray(); - if(array.length != other.length) - return false; - - for(int i = 0; i < array.length; i++) { - if(!array[i].equals(other[i])) { - return false; - } - } - return true; - } - - @Override - public int hashCode() { - int h = 1; - for(int i = 0; i < array.length; i++) { - Value obj = array[i]; - h = 31 * h + obj.hashCode(); - } - return h; - } - - @Override - public String toString() { - return toString(new StringBuilder()).toString(); - } - - private StringBuilder toString(StringBuilder sb) { - if(array.length == 0) { - return sb.append("[]"); - } - sb.append("["); - sb.append(array[0]); - for(int i = 1; i < array.length; i++) { - sb.append(","); - sb.append(array[i].toString()); - } - sb.append("]"); - return sb; - } - - @Override - public int size() { - return array.length; - } - - @Override - public boolean hasNext() { - return cursor < array.length; - } - @Override - public ValueRef next() { - return array[cursor++]; - } - @Override - public void skip() { - cursor++; - } - @Override - public void skipAll() { - while(hasNext()) { - skip(); - } - } - - public Value[] toValueArray() { - return Arrays.copyOf(array, array.length); - } - - @Override - public Iterator iterator() { - return new Iterator() { - int cursor = 0; - @Override - public boolean hasNext() { - return cursor < array.length; - } - @Override - public ValueRef next() { - return array[cursor++]; - } - @Override - public void remove() { - throw new UnsupportedOperationException("remove"); - } - }; - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/BinaryValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/BinaryValueImpl.java deleted file mode 100644 index 8b992f023..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/BinaryValueImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.value.ValueType; -import org.msgpack.value.BinaryValue; -import org.msgpack.value.ValueVisitor; - -import java.nio.ByteBuffer; - -/** -* Created on 5/30/14. -*/ -public class BinaryValueImpl extends RawValueImpl implements BinaryValue { - public BinaryValueImpl(ByteBuffer byteBuffer) { - super(byteBuffer); - } - - @Override - public ValueType getValueType() { - return ValueType.BINARY; - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitBinary(this); - } - - @Override - public BinaryValue toValue() { - return this; - } - - - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/BooleanValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/BooleanValueImpl.java deleted file mode 100644 index 4d1cbc311..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/BooleanValueImpl.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessagePacker; -import org.msgpack.value.ValueType; -import org.msgpack.value.BooleanValue; -import org.msgpack.value.ValueVisitor; - -import java.io.IOException; - -/** -* Created on 5/30/14. -*/ -public class BooleanValueImpl extends AbstractValue implements BooleanValue { - - public static BooleanValue TRUE = new BooleanValueImpl(true); - public static BooleanValue FALSE = new BooleanValueImpl(false); - - private final boolean value; - - public BooleanValueImpl(boolean value) { - this.value = value; - } - - @Override - public ValueType getValueType() { - return ValueType.BOOLEAN; - } - @Override - public boolean equals(Object o) { - if (!(o instanceof BooleanValue)) - return false; - return value == ((BooleanValue) o).toBoolean(); - } - - @Override - public int hashCode() { - return 0; - } - - public String toString() { - return Boolean.toString(value); - } - - @Override - public boolean toBoolean() { - return value; - } - - @Override - public void writeTo(MessagePacker packer) throws IOException { - packer.packBoolean(value); - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitBoolean(value); - } - @Override - public BooleanValue toValue() { - return this; - } - - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/CursorImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/CursorImpl.java deleted file mode 100644 index 488ecae78..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/CursorImpl.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.*; -import org.msgpack.value.*; -import org.msgpack.value.holder.ValueHolder; -import java.io.IOException; - -/** - * Cursor implementation - */ -public class CursorImpl implements Cursor { - - private final MessageUnpacker unpacker; - private MessageFormat currentFormat; - private ValueHolder valueHolder; - - public CursorImpl(MessageUnpacker unpacker) { - this.unpacker = unpacker; - this.currentFormat = MessageFormat.NIL; - this.valueHolder = new ValueHolder(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("remove"); - } - - - @Override - public boolean hasNext() { - try { - return unpacker.hasNext(); - } - catch(IOException e) { - throw new MessageFormatException(e); - } - } - - @Override - public void skip() { - try { - unpacker.skipValue(); - } - catch(IOException e){ - throw new MessageFormatException(e); - } - } - @Override - public long getReadBytes() { - return unpacker.getTotalReadBytes(); - } - - - private final void readNext() { - try { - currentFormat = unpacker.unpackValue(valueHolder); - } - catch(IOException e) { - throw new MessageFormatException(e); - } - } - - @Override - public Value next() { - readNext(); - return valueHolder.get(); - } - - @Override - public ValueRef nextRef() { - readNext(); - return valueHolder.getRef(); - } - - private ValueType getValueType() { - return currentFormat.getValueType(); - } - - - @Override - public Out apply(Function f) { - return null; - } - - @Override - public boolean isNilValue() { - return getValueType().isNilType(); - } - @Override - public boolean isBooleanValue() { - return getValueType().isBooleanType(); - } - @Override - public boolean isNumberValue() { - return getValueType().isNumberType(); - } - @Override - public boolean isIntegerValue() { - return getValueType().isIntegerType(); - } - @Override - public boolean isFloatValue() { - return getValueType().isFloatType(); - } - @Override - public boolean isBinaryValue() { - return getValueType().isBinaryType(); - } - @Override - public boolean isStringValue() { - return getValueType().isStringType(); - } - @Override - public boolean isRawValue() { - return getValueType().isRawType(); - } - @Override - public boolean isArrayValue() { - return getValueType().isArrayType(); - } - @Override - public boolean isMapValue() { - return getValueType().isMapType(); - } - @Override - public boolean isExtendedValue() { - return getValueType().isExtendedType(); - } - - @Override - public void close() throws IOException { - unpacker.close(); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/DoubleValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/DoubleValueImpl.java deleted file mode 100644 index e8382a344..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/DoubleValueImpl.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessageFloatOverflowException; -import org.msgpack.core.MessageOverflowException; -import org.msgpack.core.MessagePacker; -import org.msgpack.value.*; - -import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; - -/** -* Created on 5/30/14. -*/ -public class DoubleValueImpl extends AbstractValue implements FloatValue { - private final double value; - - public DoubleValueImpl(double value) { - this.value = value; - } - - @Override - public ValueType getValueType() { - return ValueType.FLOAT; - } - - @Override - public boolean isValidByte() { - return ((double) ((byte) value)) == value; - } - @Override - public boolean isValidShort() { - return ((double) ((short) value)) == value; - } - @Override - public boolean isValidInt() { - return ((double) ((int) value)) == value; - } - @Override - public boolean isValidLong() { - long l = (long) value; - return ((double) l) == value && l != Long.MAX_VALUE; - } - @Override - public boolean isWhole() { - long l = (long) value; - return ((double) l == value) || l == Long.MAX_VALUE && value < Double.POSITIVE_INFINITY || l == Long.MIN_VALUE && value > Double.NEGATIVE_INFINITY; - } - @Override - public byte toByte() { - return (byte) value; - } - - @Override - public short toShort() { - return (short) value; - } - - @Override - public int toInt() { - return (int) value; - } - - @Override - public long toLong() { - return (long) value; - } - - @Override - public BigInteger toBigInteger() { - return new BigDecimal(value).toBigInteger(); - } - - @Override - public float toFloat() { - return (float) value; - } - - @Override - public double toDouble() { - return value; - } - @Override - public byte asByte() throws MessageOverflowException { - if(!isValidByte()) - throw new MessageFloatOverflowException(value); - return (byte) value; - } - @Override - public short asShort() throws MessageOverflowException { - if(!isValidShort()) - throw new MessageFloatOverflowException(value); - return (short) value; - } - @Override - public int asInt() throws MessageOverflowException { - if(!isValidInt()) - throw new MessageFloatOverflowException(value); - return (int) value; - } - @Override - public long asLong() throws MessageOverflowException { - if(!isValidLong()) - throw new MessageFloatOverflowException(value); - return (long) value; - } - @Override - public BigInteger asBigInteger() throws MessageOverflowException { - if(!isWhole()) - throw new MessageFloatOverflowException(value); - return new BigDecimal(value).toBigInteger(); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof Value)) { - return false; - } - Value v = (Value) o; - if (!v.isFloat()) { - return false; - } - return value == v.asFloat().toDouble(); - } - - @Override - public FloatValue toValue() { - return ValueFactory.newDouble(value); - } - @Override - public void writeTo(MessagePacker pk) throws IOException { - pk.packDouble(value); - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitFloat(this); - } - - @Override - public int hashCode() { - long v = Double.doubleToLongBits(value); - return (int) (v ^ (v >>> 32)); - } - - @Override - public String toString() { - return Double.toString(value); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ExtendedValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ExtendedValueImpl.java deleted file mode 100644 index 1591b6308..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/ExtendedValueImpl.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessagePacker; -import org.msgpack.value.ValueType; -import org.msgpack.value.ExtendedValue; -import org.msgpack.value.ValueVisitor; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Extended value implementation - */ -public class ExtendedValueImpl extends RawValueImpl implements ExtendedValue { - - private final int type; - - - public ExtendedValueImpl(int type, ByteBuffer data) { - super(data); - this.type = type; - } - - @Override - public ValueType getValueType() { - return ValueType.EXTENDED; - } - @Override - public void writeTo(MessagePacker packer) throws IOException { - packer.packExtendedTypeHeader(type, byteBuffer.remaining()); - packer.writePayload(byteBuffer); - } - - @Override - public void accept(ValueVisitor visitor) { - visitor.visitExtended(this); - } - - @Override - public ExtendedValue toValue() { - return this; - } - - @Override - public int getExtType() { - return type; - } - - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/FloatValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/FloatValueImpl.java deleted file mode 100644 index 644b81aaa..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/FloatValueImpl.java +++ /dev/null @@ -1,159 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessageFloatOverflowException; -import org.msgpack.core.MessageOverflowException; -import org.msgpack.core.MessagePacker; -import org.msgpack.value.*; - -import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; - -/** -* Created on 5/30/14. -*/ -public class FloatValueImpl extends AbstractValue implements FloatValue { - private final float value; - - public FloatValueImpl(float value) { - this.value = value; - } - - @Override - public ValueType getValueType() { - return ValueType.FLOAT; - } - - @Override - public boolean isValidByte() { - return (float) ((byte) value) == value; - } - @Override - public boolean isValidShort() { - return (float) ((short) value) == value; - } - @Override - public boolean isValidInt() { - int i = (int) value; - return ((float) i) == value && i != Integer.MAX_VALUE; - } - @Override - public boolean isValidLong() { - long l = (long) value; - return ((float) l) == value && l != Long.MAX_VALUE; - } - - @Override - public boolean isWhole() { - long l = (long) value; - return ((float) l) == value || l == Long.MAX_VALUE || value < Float.POSITIVE_INFINITY || l == Long.MIN_VALUE && value > Float.NEGATIVE_INFINITY; - } - - @Override - public byte toByte() { - return (byte) value; - } - - @Override - public short toShort() { - return (short) value; - } - - @Override - public int toInt() { - return (int) value; - } - - @Override - public long toLong() { - return (long) value; - } - - @Override - public BigInteger toBigInteger() { - return new BigDecimal((double) value).toBigInteger(); - } - - @Override - public float toFloat() { - return value; - } - - @Override - public double toDouble() { - return (double) value; - } - @Override - public byte asByte() throws MessageOverflowException { - if (!isValidByte()) { - throw new MessageFloatOverflowException(value); - } - return (byte) value; - } - @Override - public short asShort() throws MessageOverflowException { - if(!isValidShort()) - throw new MessageFloatOverflowException(value); - return (short) value; - } - @Override - public int asInt() throws MessageOverflowException { - if(!isValidInt()) - throw new MessageFloatOverflowException(value); - return (int) value; - } - - @Override - public long asLong() throws MessageOverflowException { - if(!isValidLong()) - throw new MessageFloatOverflowException(value); - return (long) value; - } - - @Override - public BigInteger asBigInteger() throws MessageOverflowException { - if(!isWhole()) - throw new MessageFloatOverflowException(value); - - return BigDecimal.valueOf(value).toBigInteger(); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof Value)) { - return false; - } - Value v = (Value) o; - if (!v.isFloat()) { - return false; - } - return (double) value == v.asFloat().toDouble(); - } - - @Override - public void writeTo(MessagePacker pk) throws IOException { - pk.packFloat(value); - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitFloat(this); - } - @Override - public FloatValue toValue() { - return this; - } - - @Override - public int hashCode() { - long v = Double.doubleToLongBits((double) value); - return (int) (v ^ (v >>> 32)); - } - - @Override - public String toString() { - return Float.toString(value); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableArrayValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableArrayValueImpl.java new file mode 100644 index 000000000..3e1b732c2 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableArrayValueImpl.java @@ -0,0 +1,263 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ArrayValue; +import org.msgpack.value.ImmutableArrayValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * {@code ImmutableArrayValueImpl} Implements {@code ImmutableArrayValue} using a {@code Value[]} field. + * + * @see org.msgpack.value.IntegerValue + */ +public class ImmutableArrayValueImpl + extends AbstractImmutableValue + implements ImmutableArrayValue +{ + private static final ImmutableArrayValueImpl EMPTY = new ImmutableArrayValueImpl(new Value[0]); + + public static ImmutableArrayValue empty() + { + return EMPTY; + } + + private final Value[] array; + + public ImmutableArrayValueImpl(Value[] array) + { + this.array = array; + } + + @Override + public ValueType getValueType() + { + return ValueType.ARRAY; + } + + @Override + public ImmutableArrayValue immutableValue() + { + return this; + } + + @Override + public ImmutableArrayValue asArrayValue() + { + return this; + } + + @Override + public int size() + { + return array.length; + } + + @Override + public Value get(int index) + { + return array[index]; + } + + @Override + public Value getOrNilValue(int index) + { + if (index < array.length && index >= 0) { + return array[index]; + } + return ImmutableNilValueImpl.get(); + } + + @Override + public Iterator iterator() + { + return new Ite(array); + } + + @Override + public List list() + { + return new ImmutableArrayValueList(array); + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packArrayHeader(array.length); + for (int i = 0; i < array.length; i++) { + array[i].writeTo(pk); + } + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + Value v = (Value) o; + + if (v instanceof ImmutableArrayValueImpl) { + ImmutableArrayValueImpl oa = (ImmutableArrayValueImpl) v; + return Arrays.equals(array, oa.array); + } + else { + if (!v.isArrayValue()) { + return false; + } + ArrayValue av = v.asArrayValue(); + if (size() != av.size()) { + return false; + } + Iterator oi = av.iterator(); + int i = 0; + while (i < array.length) { + if (!oi.hasNext() || !array[i].equals(oi.next())) { + return false; + } + i++; + } + return true; + } + } + + @Override + public int hashCode() + { + int h = 1; + for (int i = 0; i < array.length; i++) { + Value obj = array[i]; + h = 31 * h + obj.hashCode(); + } + return h; + } + + @Override + public String toJson() + { + if (array.length == 0) { + return "[]"; + } + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(array[0].toJson()); + for (int i = 1; i < array.length; i++) { + sb.append(","); + sb.append(array[i].toJson()); + } + sb.append("]"); + return sb.toString(); + } + + @Override + public String toString() + { + if (array.length == 0) { + return "[]"; + } + StringBuilder sb = new StringBuilder(); + sb.append("["); + appendString(sb, array[0]); + for (int i = 1; i < array.length; i++) { + sb.append(","); + appendString(sb, array[i]); + } + sb.append("]"); + return sb.toString(); + } + + private static void appendString(StringBuilder sb, Value value) + { + if (value.isRawValue()) { + sb.append(value.toJson()); + } + else { + sb.append(value.toString()); + } + } + + private static class ImmutableArrayValueList + extends AbstractList + { + private final Value[] array; + + public ImmutableArrayValueList(Value[] array) + { + this.array = array; + } + + @Override + public Value get(int index) + { + return array[index]; + } + + @Override + public int size() + { + return array.length; + } + } + + private static class Ite + implements Iterator + { + private final Value[] array; + private int index; + + public Ite(Value[] array) + { + this.array = array; + this.index = 0; + } + + @Override + public boolean hasNext() + { + return index != array.length; + } + + @Override + public Value next() + { + int i = index; + if (i >= array.length) { + throw new NoSuchElementException(); + } + index = i + 1; + return array[i]; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/BigIntegerValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBigIntegerValueImpl.java similarity index 51% rename from msgpack-core/src/main/java/org/msgpack/value/impl/BigIntegerValueImpl.java rename to msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBigIntegerValueImpl.java index 85af4a176..c6fe39386 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/BigIntegerValueImpl.java +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBigIntegerValueImpl.java @@ -1,26 +1,65 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value.impl; +import org.msgpack.core.MessageFormat; import org.msgpack.core.MessageIntegerOverflowException; import org.msgpack.core.MessagePacker; -import org.msgpack.value.ValueType; +import org.msgpack.value.ImmutableIntegerValue; +import org.msgpack.value.ImmutableNumberValue; import org.msgpack.value.IntegerValue; import org.msgpack.value.Value; -import org.msgpack.value.ValueVisitor; +import org.msgpack.value.ValueType; import java.io.IOException; import java.math.BigInteger; -import static org.msgpack.core.Preconditions.checkNotNull; - /** -* Created on 5/30/14. -*/ -public class BigIntegerValueImpl extends AbstractValue implements IntegerValue { + * {@code ImmutableBigIntegerValueImpl} Implements {@code ImmutableBigIntegerValue} using a {@code BigInteger} field. + * + * @see org.msgpack.value.IntegerValue + */ +public class ImmutableBigIntegerValueImpl + extends AbstractImmutableValue + implements ImmutableIntegerValue +{ + public static MessageFormat mostSuccinctMessageFormat(IntegerValue v) + { + if (v.isInByteRange()) { + return MessageFormat.INT8; + } + else if (v.isInShortRange()) { + return MessageFormat.INT16; + } + else if (v.isInIntRange()) { + return MessageFormat.INT32; + } + else if (v.isInLongRange()) { + return MessageFormat.INT64; + } + else { + return MessageFormat.UINT64; + } + } private final BigInteger value; - public BigIntegerValueImpl(BigInteger value) { - this.value = checkNotNull(value, "BigInteger value is null"); + public ImmutableBigIntegerValueImpl(BigInteger value) + { + this.value = value; } private static final BigInteger BYTE_MIN = BigInteger.valueOf((long) Byte.MIN_VALUE); @@ -33,121 +72,153 @@ public BigIntegerValueImpl(BigInteger value) { private static final BigInteger LONG_MAX = BigInteger.valueOf((long) Long.MAX_VALUE); @Override - public ValueType getValueType() { + public ValueType getValueType() + { return ValueType.INTEGER; } @Override - public byte toByte() { + public ImmutableIntegerValue immutableValue() + { + return this; + } + + @Override + public ImmutableNumberValue asNumberValue() + { + return this; + } + + @Override + public ImmutableIntegerValue asIntegerValue() + { + return this; + } + + @Override + public byte toByte() + { return value.byteValue(); } @Override - public short toShort() { + public short toShort() + { return value.shortValue(); } @Override - public int toInt() { + public int toInt() + { return value.intValue(); } @Override - public long toLong() { + public long toLong() + { return value.longValue(); } @Override - public BigInteger toBigInteger() { + public BigInteger toBigInteger() + { return value; } @Override - public float toFloat() { + public float toFloat() + { return value.floatValue(); } @Override - public double toDouble() { + public double toDouble() + { return value.doubleValue(); } @Override - public byte asByte() throws MessageIntegerOverflowException { - if (!isValidByte()) { - throw new MessageIntegerOverflowException(value); - } - return value.byteValue(); + public boolean isInByteRange() + { + return 0 <= value.compareTo(BYTE_MIN) && value.compareTo(BYTE_MAX) <= 0; } @Override - public short asShort() throws MessageIntegerOverflowException { - if (!isValidShort()) { - throw new MessageIntegerOverflowException(value); - } - return value.shortValue(); + public boolean isInShortRange() + { + return 0 <= value.compareTo(SHORT_MIN) && value.compareTo(SHORT_MAX) <= 0; } @Override - public int asInt() throws MessageIntegerOverflowException { - if (!isValidInt()) { - throw new MessageIntegerOverflowException(value); - } - return value.intValue(); + public boolean isInIntRange() + { + return 0 <= value.compareTo(INT_MIN) && value.compareTo(INT_MAX) <= 0; } @Override - public long asLong() throws MessageIntegerOverflowException { - if (!isValidLong()) { - throw new MessageIntegerOverflowException(value); - } - return value.longValue(); + public boolean isInLongRange() + { + return 0 <= value.compareTo(LONG_MIN) && value.compareTo(LONG_MAX) <= 0; } @Override - public BigInteger asBigInteger() throws MessageIntegerOverflowException { - return value; + public MessageFormat mostSuccinctMessageFormat() + { + return mostSuccinctMessageFormat(this); } @Override - public boolean isValidByte() { - return 0 <= value.compareTo(BYTE_MIN) && value.compareTo(BYTE_MAX) <= 0; + public byte asByte() + { + if (!isInByteRange()) { + throw new MessageIntegerOverflowException(value); + } + return value.byteValue(); } @Override - public boolean isValidShort() { - return 0 <= value.compareTo(SHORT_MIN) && value.compareTo(SHORT_MAX) <= 0; + public short asShort() + { + if (!isInShortRange()) { + throw new MessageIntegerOverflowException(value); + } + return value.shortValue(); } @Override - public boolean isValidInt() { - return 0 <= value.compareTo(INT_MIN) && value.compareTo(INT_MAX) <= 0; + public int asInt() + { + if (!isInIntRange()) { + throw new MessageIntegerOverflowException(value); + } + return value.intValue(); } @Override - public boolean isValidLong() { - return 0 <= value.compareTo(LONG_MIN) && value.compareTo(LONG_MAX) <= 0; + public long asLong() + { + if (!isInLongRange()) { + throw new MessageIntegerOverflowException(value); + } + return value.longValue(); } + @Override - public boolean isWhole() { - return true; + public BigInteger asBigInteger() + { + return value; } @Override - public void writeTo(MessagePacker pk) throws IOException { + public void writeTo(MessagePacker pk) + throws IOException + { pk.packBigInteger(value); } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitInteger(this); - } - @Override - public IntegerValue toValue() { - return this; - } @Override - public boolean equals(Object o) { + public boolean equals(Object o) + { if (o == this) { return true; } @@ -155,18 +226,21 @@ public boolean equals(Object o) { return false; } Value v = (Value) o; - if (!v.isInteger()) { + + if (!v.isIntegerValue()) { return false; } - IntegerValue iv = v.asInteger(); + IntegerValue iv = v.asIntegerValue(); return value.equals(iv.toBigInteger()); } @Override - public int hashCode() { + public int hashCode() + { if (INT_MIN.compareTo(value) <= 0 && value.compareTo(INT_MAX) <= 0) { return (int) value.longValue(); - } else if (LONG_MIN.compareTo(value) <= 0 + } + else if (LONG_MIN.compareTo(value) <= 0 && value.compareTo(LONG_MAX) <= 0) { long v = value.longValue(); return (int) (v ^ (v >>> 32)); @@ -175,7 +249,14 @@ public int hashCode() { } @Override - public String toString() { + public String toJson() + { return value.toString(); } + + @Override + public String toString() + { + return toJson(); + } } diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBinaryValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBinaryValueImpl.java new file mode 100644 index 000000000..2d444ae83 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBinaryValueImpl.java @@ -0,0 +1,95 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ImmutableBinaryValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; +import java.util.Arrays; + +/** + * {@code ImmutableBinaryValueImpl} Implements {@code ImmutableBinaryValue} using a {@code byte[]} field. + * This implementation caches result of {@code toString()} and {@code asString()} using a private {@code String} field. + * + * @see org.msgpack.value.StringValue + */ +public class ImmutableBinaryValueImpl + extends AbstractImmutableRawValue + implements ImmutableBinaryValue +{ + public ImmutableBinaryValueImpl(byte[] data) + { + super(data); + } + + @Override + public ValueType getValueType() + { + return ValueType.BINARY; + } + + @Override + public ImmutableBinaryValue immutableValue() + { + return this; + } + + @Override + public ImmutableBinaryValue asBinaryValue() + { + return this; + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packBinaryHeader(data.length); + pk.writePayload(data); + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + Value v = (Value) o; + if (!v.isBinaryValue()) { + return false; + } + + if (v instanceof ImmutableBinaryValueImpl) { + ImmutableBinaryValueImpl bv = (ImmutableBinaryValueImpl) v; + return Arrays.equals(data, bv.data); + } + else { + return Arrays.equals(data, v.asBinaryValue().asByteArray()); + } + } + + @Override + public int hashCode() + { + return Arrays.hashCode(data); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBooleanValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBooleanValueImpl.java new file mode 100644 index 000000000..535e91c61 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableBooleanValueImpl.java @@ -0,0 +1,110 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ImmutableBooleanValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; + +/** + * {@code ImmutableBooleanValueImpl} Implements {@code ImmutableBooleanValue} using a {@code boolean} field. + * + * This class is a singleton. {@code ImmutableBooleanValueImpl.trueInstance()} and {@code ImmutableBooleanValueImpl.falseInstance()} are the only instances of this class. + * + * @see org.msgpack.value.BooleanValue + */ +public class ImmutableBooleanValueImpl + extends AbstractImmutableValue + implements ImmutableBooleanValue +{ + public static final ImmutableBooleanValue TRUE = new ImmutableBooleanValueImpl(true); + public static final ImmutableBooleanValue FALSE = new ImmutableBooleanValueImpl(false); + + private final boolean value; + + private ImmutableBooleanValueImpl(boolean value) + { + this.value = value; + } + + @Override + public ValueType getValueType() + { + return ValueType.BOOLEAN; + } + + @Override + public ImmutableBooleanValue immutableValue() + { + return this; + } + + @Override + public boolean getBoolean() + { + return value; + } + + @Override + public void writeTo(MessagePacker packer) + throws IOException + { + packer.packBoolean(value); + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + Value v = (Value) o; + + if (!v.isBooleanValue()) { + return false; + } + return value == v.asBooleanValue().getBoolean(); + } + + @Override + public int hashCode() + { + if (value) { + return 1231; + } + else { + return 1237; + } + } + + @Override + public String toJson() + { + return Boolean.toString(value); + } + + @Override + public String toString() + { + return toJson(); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableDoubleValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableDoubleValueImpl.java new file mode 100644 index 000000000..2aae1633a --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableDoubleValueImpl.java @@ -0,0 +1,144 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ImmutableFloatValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * {@code ImmutableDoubleValueImpl} Implements {@code ImmutableFloatValue} using a {@code double} field. + * + * @see org.msgpack.value.FloatValue + */ +public class ImmutableDoubleValueImpl + extends AbstractImmutableValue + implements ImmutableFloatValue +{ + private final double value; + + public ImmutableDoubleValueImpl(double value) + { + this.value = value; + } + + @Override + public ValueType getValueType() + { + return ValueType.FLOAT; + } + + @Override + public ImmutableDoubleValueImpl immutableValue() + { + return this; + } + + @Override + public byte toByte() + { + return (byte) value; + } + + @Override + public short toShort() + { + return (short) value; + } + + @Override + public int toInt() + { + return (int) value; + } + + @Override + public long toLong() + { + return (long) value; + } + + @Override + public BigInteger toBigInteger() + { + return new BigDecimal(value).toBigInteger(); + } + + @Override + public float toFloat() + { + return (float) value; + } + + @Override + public double toDouble() + { + return value; + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packDouble(value); + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + Value v = (Value) o; + + if (!v.isFloatValue()) { + return false; + } + return value == v.asFloatValue().toDouble(); + } + + @Override + public int hashCode() + { + long v = Double.doubleToLongBits(value); + return (int) (v ^ (v >>> 32)); + } + + @Override + public String toJson() + { + if (Double.isNaN(value) || Double.isInfinite(value)) { + return "null"; + } + else { + return Double.toString(value); + } + } + + @Override + public String toString() + { + return Double.toString(value); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableExtensionValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableExtensionValueImpl.java new file mode 100644 index 000000000..eb14cc767 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableExtensionValueImpl.java @@ -0,0 +1,138 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ExtensionValue; +import org.msgpack.value.ImmutableExtensionValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; +import java.util.Arrays; + +/** + * {@code ImmutableExtensionValueImpl} Implements {@code ImmutableExtensionValue} using a {@code byte} and a {@code byte[]} fields. + * + * @see ExtensionValue + */ +public class ImmutableExtensionValueImpl + extends AbstractImmutableValue + implements ImmutableExtensionValue +{ + private final byte type; + private final byte[] data; + + public ImmutableExtensionValueImpl(byte type, byte[] data) + { + this.type = type; + this.data = data; + } + + @Override + public ValueType getValueType() + { + return ValueType.EXTENSION; + } + + @Override + public ImmutableExtensionValue immutableValue() + { + return this; + } + + @Override + public ImmutableExtensionValue asExtensionValue() + { + return this; + } + + @Override + public byte getType() + { + return type; + } + + @Override + public byte[] getData() + { + return data; + } + + @Override + public void writeTo(MessagePacker packer) + throws IOException + { + packer.packExtensionTypeHeader(type, data.length); + packer.writePayload(data); + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + Value v = (Value) o; + + if (!v.isExtensionValue()) { + return false; + } + ExtensionValue ev = v.asExtensionValue(); + return type == ev.getType() && Arrays.equals(data, ev.getData()); + } + + @Override + public int hashCode() + { + int hash = 31 + type; + for (byte e : data) { + hash = 31 * hash + e; + } + return hash; + } + + @Override + public String toJson() + { + StringBuilder sb = new StringBuilder(); + sb.append('['); + sb.append(Byte.toString(type)); + sb.append(",\""); + for (byte e : data) { + sb.append(Integer.toString((int) e, 16)); + } + sb.append("\"]"); + return sb.toString(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append('('); + sb.append(Byte.toString(type)); + sb.append(",0x"); + for (byte e : data) { + sb.append(Integer.toString((int) e, 16)); + } + sb.append(")"); + return sb.toString(); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/LongValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableLongValueImpl.java similarity index 50% rename from msgpack-core/src/main/java/org/msgpack/value/impl/LongValueImpl.java rename to msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableLongValueImpl.java index 57346fe11..872b97af0 100644 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/LongValueImpl.java +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableLongValueImpl.java @@ -1,23 +1,45 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value.impl; +import org.msgpack.core.MessageFormat; import org.msgpack.core.MessageIntegerOverflowException; import org.msgpack.core.MessagePacker; -import org.msgpack.value.ValueType; +import org.msgpack.value.ImmutableIntegerValue; +import org.msgpack.value.ImmutableNumberValue; import org.msgpack.value.IntegerValue; import org.msgpack.value.Value; -import org.msgpack.value.ValueVisitor; +import org.msgpack.value.ValueType; import java.io.IOException; import java.math.BigInteger; /** -* Created on 5/30/14. -*/ -public class LongValueImpl extends AbstractValue implements IntegerValue { - + * {@code ImmutableLongValueImpl} Implements {@code ImmutableIntegerValue} using a {@code long} field. + * + * @see org.msgpack.value.IntegerValue + */ +public class ImmutableLongValueImpl + extends AbstractImmutableValue + implements ImmutableIntegerValue +{ private final long value; - public LongValueImpl(long value) { + public ImmutableLongValueImpl(long value) + { this.value = value; } @@ -29,123 +51,150 @@ public LongValueImpl(long value) { private static final long INT_MAX = (long) Integer.MAX_VALUE; @Override - public ValueType getValueType() { + public ValueType getValueType() + { return ValueType.INTEGER; } @Override - public IntegerValue asInteger() { + public ImmutableIntegerValue immutableValue() + { + return this; + } + + @Override + public ImmutableNumberValue asNumberValue() + { return this; } @Override - public byte toByte() { + public ImmutableIntegerValue asIntegerValue() + { + return this; + } + + @Override + public byte toByte() + { return (byte) value; } @Override - public short toShort() { + public short toShort() + { return (short) value; } @Override - public int toInt() { + public int toInt() + { return (int) value; } @Override - public long toLong() { + public long toLong() + { return value; } @Override - public BigInteger toBigInteger() { + public BigInteger toBigInteger() + { return BigInteger.valueOf(value); } @Override - public float toFloat() { + public float toFloat() + { return (float) value; } @Override - public double toDouble() { + public double toDouble() + { return (double) value; } @Override - public byte asByte() throws MessageIntegerOverflowException { - if (!isValidByte()) { - throw new MessageIntegerOverflowException(value); - } - return (byte) value; + public boolean isInByteRange() + { + return BYTE_MIN <= value && value <= BYTE_MAX; } @Override - public short asShort() throws MessageIntegerOverflowException { - if (!isValidShort()) { - throw new MessageIntegerOverflowException(value); - } - return (short) value; + public boolean isInShortRange() + { + return SHORT_MIN <= value && value <= SHORT_MAX; } @Override - public int asInt() throws MessageIntegerOverflowException { - if (!isValidInt()) { - throw new MessageIntegerOverflowException(value); - } - return (int) value; + public boolean isInIntRange() + { + return INT_MIN <= value && value <= INT_MAX; } @Override - public long asLong() throws MessageIntegerOverflowException { - return value; + public boolean isInLongRange() + { + return true; } @Override - public BigInteger asBigInteger() throws MessageIntegerOverflowException { - return BigInteger.valueOf(value); + public MessageFormat mostSuccinctMessageFormat() + { + return ImmutableBigIntegerValueImpl.mostSuccinctMessageFormat(this); } @Override - public boolean isValidByte() { - return BYTE_MIN <= value && value <= BYTE_MAX; + public byte asByte() + { + if (!isInByteRange()) { + throw new MessageIntegerOverflowException(value); + } + return (byte) value; } @Override - public boolean isValidShort() { - return SHORT_MIN <= value && value <= SHORT_MAX; + public short asShort() + { + if (!isInShortRange()) { + throw new MessageIntegerOverflowException(value); + } + return (short) value; } @Override - public boolean isValidInt() { - return INT_MIN <= value && value <= INT_MAX; + public int asInt() + { + if (!isInIntRange()) { + throw new MessageIntegerOverflowException(value); + } + return (int) value; } @Override - public boolean isValidLong() { - return true; + public long asLong() + { + return value; } + @Override - public boolean isWhole() { - return true; + public BigInteger asBigInteger() + { + return BigInteger.valueOf((long) value); } @Override - public void writeTo(MessagePacker pk) throws IOException { + public void writeTo(MessagePacker pk) + throws IOException + { pk.packLong(value); } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitInteger(this); - } - @Override - public IntegerValue toValue() { - return this; - } @Override - public boolean equals(Object o) { + public boolean equals(Object o) + { if (o == this) { return true; } @@ -153,27 +202,37 @@ public boolean equals(Object o) { return false; } Value v = (Value) o; - if (!v.isInteger()) { + if (!v.isIntegerValue()) { return false; } - IntegerValue iv = v.asInteger(); - if (!iv.isValidLong()) { + + IntegerValue iv = v.asIntegerValue(); + if (!iv.isInLongRange()) { return false; } return value == iv.toLong(); } @Override - public int hashCode() { + public int hashCode() + { if (INT_MIN <= value && value <= INT_MAX) { return (int) value; - } else { + } + else { return (int) (value ^ (value >>> 32)); } } @Override - public String toString() { + public String toJson() + { return Long.toString(value); } + + @Override + public String toString() + { + return toJson(); + } } diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableMapValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableMapValueImpl.java new file mode 100644 index 000000000..dc55d783f --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableMapValueImpl.java @@ -0,0 +1,373 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ImmutableMapValue; +import org.msgpack.value.MapValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * {@code ImmutableMapValueImpl} Implements {@code ImmutableMapValue} using a {@code Value[]} field. + * + * @see org.msgpack.value.MapValue + */ +public class ImmutableMapValueImpl + extends AbstractImmutableValue + implements ImmutableMapValue +{ + private static final ImmutableMapValueImpl EMPTY = new ImmutableMapValueImpl(new Value[0]); + + public static ImmutableMapValue empty() + { + return EMPTY; + } + + private final Value[] kvs; + + public ImmutableMapValueImpl(Value[] kvs) + { + this.kvs = kvs; + } + + @Override + public ValueType getValueType() + { + return ValueType.MAP; + } + + @Override + public ImmutableMapValue immutableValue() + { + return this; + } + + @Override + public ImmutableMapValue asMapValue() + { + return this; + } + + @Override + public Value[] getKeyValueArray() + { + return Arrays.copyOf(kvs, kvs.length); + } + + @Override + public int size() + { + return kvs.length / 2; + } + + @Override + public Set keySet() + { + return new KeySet(kvs); + } + + @Override + public Set> entrySet() + { + return new EntrySet(kvs); + } + + @Override + public Collection values() + { + return new ValueCollection(kvs); + } + + @Override + public Map map() + { + return new ImmutableMapValueMap(kvs); + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packMapHeader(kvs.length / 2); + for (int i = 0; i < kvs.length; i++) { + kvs[i].writeTo(pk); + } + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + Value v = (Value) o; + + if (!v.isMapValue()) { + return false; + } + MapValue mv = v.asMapValue(); + return map().equals(mv.map()); + } + + @Override + public int hashCode() + { + int h = 0; + for (int i = 0; i < kvs.length; i += 2) { + h += kvs[i].hashCode() ^ kvs[i + 1].hashCode(); + } + return h; + } + + @Override + public String toJson() + { + if (kvs.length == 0) { + return "{}"; + } + StringBuilder sb = new StringBuilder(); + sb.append("{"); + appendJsonKey(sb, kvs[0]); + sb.append(":"); + sb.append(kvs[1].toJson()); + for (int i = 2; i < kvs.length; i += 2) { + sb.append(","); + appendJsonKey(sb, kvs[i]); + sb.append(":"); + sb.append(kvs[i + 1].toJson()); + } + sb.append("}"); + return sb.toString(); + } + + private static void appendJsonKey(StringBuilder sb, Value key) + { + if (key.isRawValue()) { + sb.append(key.toJson()); + } + else { + ImmutableStringValueImpl.appendJsonString(sb, key.toString()); + } + } + + @Override + public String toString() + { + if (kvs.length == 0) { + return "{}"; + } + StringBuilder sb = new StringBuilder(); + sb.append("{"); + appendString(sb, kvs[0]); + sb.append(":"); + appendString(sb, kvs[1]); + for (int i = 2; i < kvs.length; i += 2) { + sb.append(","); + appendString(sb, kvs[i]); + sb.append(":"); + appendString(sb, kvs[i + 1]); + } + sb.append("}"); + return sb.toString(); + } + + private static void appendString(StringBuilder sb, Value value) + { + if (value.isRawValue()) { + sb.append(value.toJson()); + } + else { + sb.append(value.toString()); + } + } + + private static class ImmutableMapValueMap + extends AbstractMap + { + private final Value[] kvs; + + public ImmutableMapValueMap(Value[] kvs) + { + this.kvs = kvs; + } + + @Override + public Set> entrySet() + { + return new EntrySet(kvs); + } + } + + private static class EntrySet + extends AbstractSet> + { + private final Value[] kvs; + + EntrySet(Value[] kvs) + { + this.kvs = kvs; + } + + @Override + public int size() + { + return kvs.length / 2; + } + + @Override + public Iterator> iterator() + { + return new EntrySetIterator(kvs); + } + } + + private static class EntrySetIterator + implements Iterator> + { + private final Value[] kvs; + private int index; + + EntrySetIterator(Value[] kvs) + { + this.kvs = kvs; + this.index = 0; + } + + @Override + public boolean hasNext() + { + return index < kvs.length; + } + + @Override + public Map.Entry next() + { + if (index >= kvs.length) { + throw new NoSuchElementException(); // TODO message + } + + Value key = kvs[index]; + Value value = kvs[index + 1]; + Map.Entry pair = new AbstractMap.SimpleImmutableEntry(key, value); + + index += 2; + return pair; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); // TODO message + } + } + + private static class KeySet + extends AbstractSet + { + private Value[] kvs; + + KeySet(Value[] kvs) + { + this.kvs = kvs; + } + + @Override + public int size() + { + return kvs.length / 2; + } + + @Override + public Iterator iterator() + { + return new EntryIterator(kvs, 0); + } + } + + private static class ValueCollection + extends AbstractCollection + { + private Value[] kvs; + + ValueCollection(Value[] kvs) + { + this.kvs = kvs; + } + + @Override + public int size() + { + return kvs.length / 2; + } + + @Override + public Iterator iterator() + { + return new EntryIterator(kvs, 1); + } + } + + private static class EntryIterator + implements Iterator + { + private Value[] kvs; + private int index; + + public EntryIterator(Value[] kvs, int offset) + { + this.kvs = kvs; + this.index = offset; + } + + @Override + public boolean hasNext() + { + return index < kvs.length; + } + + @Override + public Value next() + { + int i = index; + if (i >= kvs.length) { + throw new NoSuchElementException(); + } + index = i + 2; + return kvs[i]; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableNilValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableNilValueImpl.java new file mode 100644 index 000000000..7077d115e --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableNilValueImpl.java @@ -0,0 +1,101 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ImmutableNilValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; + +/** + * {@code ImmutableNilValueImpl} Implements {@code ImmutableNilValue}. + * + * This class is a singleton. {@code ImmutableNilValueImpl.get()} is the only instances of this class. + * + * @see org.msgpack.value.NilValue + */ +public class ImmutableNilValueImpl + extends AbstractImmutableValue + implements ImmutableNilValue +{ + private static ImmutableNilValue instance = new ImmutableNilValueImpl(); + + public static ImmutableNilValue get() + { + return instance; + } + + private ImmutableNilValueImpl() + { + } + + @Override + public ValueType getValueType() + { + return ValueType.NIL; + } + + @Override + public ImmutableNilValue immutableValue() + { + return this; + } + + @Override + public ImmutableNilValue asNilValue() + { + return this; + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packNil(); + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + return ((Value) o).isNilValue(); + } + + @Override + public int hashCode() + { + return 0; + } + + @Override + public String toString() + { + return toJson(); + } + + @Override + public String toJson() + { + return "null"; + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableStringValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableStringValueImpl.java new file mode 100644 index 000000000..dbdb57195 --- /dev/null +++ b/msgpack-core/src/main/java/org/msgpack/value/impl/ImmutableStringValueImpl.java @@ -0,0 +1,100 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value.impl; + +import org.msgpack.core.MessagePacker; +import org.msgpack.value.ImmutableStringValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueType; + +import java.io.IOException; +import java.util.Arrays; + +/** + * {@code ImmutableStringValueImpl} Implements {@code ImmutableStringValue} using a {@code byte[]} field. + * This implementation caches result of {@code toString()} and {@code asString()} using a private {@code String} field. + * + * @see org.msgpack.value.StringValue + */ +public class ImmutableStringValueImpl + extends AbstractImmutableRawValue + implements ImmutableStringValue +{ + public ImmutableStringValueImpl(byte[] data) + { + super(data); + } + + public ImmutableStringValueImpl(String string) + { + super(string); + } + + @Override + public ValueType getValueType() + { + return ValueType.STRING; + } + + @Override + public ImmutableStringValue immutableValue() + { + return this; + } + + @Override + public ImmutableStringValue asStringValue() + { + return this; + } + + @Override + public void writeTo(MessagePacker pk) + throws IOException + { + pk.packRawStringHeader(data.length); + pk.writePayload(data); + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (!(o instanceof Value)) { + return false; + } + Value v = (Value) o; + if (!v.isStringValue()) { + return false; + } + + if (v instanceof ImmutableStringValueImpl) { + ImmutableStringValueImpl bv = (ImmutableStringValueImpl) v; + return Arrays.equals(data, bv.data); + } + else { + return Arrays.equals(data, v.asStringValue().asByteArray()); + } + } + + @Override + public int hashCode() + { + return Arrays.hashCode(data); + } +} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/IntegerValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/IntegerValueImpl.java deleted file mode 100644 index 758fde939..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/IntegerValueImpl.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessageIntegerOverflowException; -import org.msgpack.core.MessageOverflowException; -import org.msgpack.core.MessagePacker; -import org.msgpack.value.*; - -import java.io.IOException; -import java.math.BigInteger; - -/** -* Created on 5/30/14. -*/ -public class IntegerValueImpl extends AbstractValue implements IntegerValue { - - private final int value; - - public IntegerValueImpl(int value) { - this.value = value; - } - - private static int BYTE_MIN = (int) Byte.MIN_VALUE; - private static int BYTE_MAX = (int) Byte.MAX_VALUE; - private static int SHORT_MIN = (int) Short.MIN_VALUE; - private static int SHORT_MAX = (int) Short.MAX_VALUE; - - @Override - public ValueType getValueType() { - return ValueType.INTEGER; - } - - @Override - public IntegerValue asInteger() { - return this; - } - - @Override - public byte toByte() { - return (byte) value; - } - - @Override - public short toShort() { - return (short) value; - } - - @Override - public int toInt() { - return value; - } - - @Override - public long toLong() { - return value; - } - - @Override - public BigInteger toBigInteger() { - return BigInteger.valueOf((long) value); - } - - @Override - public float toFloat() { - return (float) value; - } - - @Override - public double toDouble() { - return (double) value; - } - - @Override - public byte asByte() throws MessageOverflowException { - if (!isValidByte()) { - throw new MessageIntegerOverflowException(value); - } - return (byte) value; - } - - @Override - public short asShort() throws MessageOverflowException { - if (!isValidShort()) { - throw new MessageIntegerOverflowException(value); - } - return (short) value; - } - - @Override - public int asInt() throws MessageOverflowException { - return value; - } - - @Override - public long asLong() throws MessageOverflowException { - return value; - } - - @Override - public BigInteger asBigInteger() throws MessageOverflowException { - return BigInteger.valueOf((long) value); - } - - @Override - public boolean isValidByte() { - return BYTE_MIN <= value && value <= BYTE_MAX; - } - - @Override - public boolean isValidShort() { - return SHORT_MIN <= value && value <= SHORT_MAX; - } - - @Override - public boolean isValidInt() { - return true; - } - - @Override - public boolean isValidLong() { - return true; - } - - @Override - public boolean isWhole() { - return true; - } - - @Override - public void writeTo(MessagePacker pk) throws IOException { - pk.packInt(value); - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitInteger(this); - } - @Override - public IntegerValue toValue() { - return this; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof Value)) { - return false; - } - Value v = (Value) o; - if (!v.isInteger()) { - return false; - } - IntegerValue iv = v.asInteger(); - if (!iv.isValidInt()) { - return false; - } - return iv.toInt() == value; - } - - @Override - public int hashCode() { - return value; - } - - @Override - public String toString() { - return Integer.toString(value); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/MapCursorImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/MapCursorImpl.java deleted file mode 100644 index a6f5268b1..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/MapCursorImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.*; -import org.msgpack.value.*; -import org.msgpack.value.holder.ValueHolder; - -import java.io.IOException; - -import static org.msgpack.core.MessagePackException.UNSUPPORTED; - -/** - * Created on 6/16/14. - */ -public class MapCursorImpl extends AbstractValueRef implements MapCursor { - - private final ValueHolder valueHolder; - private MessageUnpacker unpacker; - private int cursor = 0; - private int mapSize; - - public MapCursorImpl(ValueHolder valueHolder) { - this.valueHolder = valueHolder; - } - - public void reset(MessageUnpacker unpacker) throws IOException { - this.unpacker = unpacker; - cursor = 0; - this.mapSize = unpacker.unpackMapHeader(); - } - - @Override - public int size() { - return mapSize; - } - @Override - public boolean hasNext() { - try { - return cursor < (mapSize * 2) && unpacker.hasNext(); - } - catch(IOException e) { - return false; - } - } - - @Override - public ValueRef nextKeyOrValue() { - try { - MessageFormat f = unpacker.unpackValue(valueHolder); - cursor++; - return valueHolder.getRef(); - } - catch(IOException e) { - throw new MessageFormatException(e); - } - } - - @Override - public void skipKeyOrValue() { - try { - unpacker.skipValue(); - } - catch(IOException e) { - throw new MessageFormatException(e); - } - } - - @Override - public void skipAll() { - while(hasNext()) { - skipKeyOrValue(); - } - } - - private void ensureNotTraversed() { - if(cursor != 0) - throw UNSUPPORTED("MapCursor is already traversed"); - } - - - @Override - public MapCursor getMapCursor() throws MessageTypeException { - return this; - } - - @Override - public ValueType getValueType() { - return ValueType.MAP; - } - - @Override - public void writeTo(MessagePacker packer) throws IOException { - ensureNotTraversed(); - packer.packMapHeader(mapSize); - while(hasNext()) { - packer.packValue(nextKeyOrValue().toValue()); - } - } - - @Override - public void accept(ValueVisitor visitor) { - visitor.visitMap(this.toValue()); - } - - @Override - public MapValue toValue() { - ensureNotTraversed(); - Value[] keyValueArray = new Value[mapSize * 2]; - int i = 0; - while(hasNext()) { - keyValueArray[i++] = nextKeyOrValue().toValue(); - } - return ValueFactory.newMap(keyValueArray); - } - - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/MapValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/MapValueImpl.java deleted file mode 100644 index adc0c00a1..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/MapValueImpl.java +++ /dev/null @@ -1,276 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessagePacker; -import org.msgpack.value.*; - -import java.io.IOException; -import java.util.*; - -/** -* Created on 5/30/14. -*/ -public class MapValueImpl extends AbstractValue implements MapValue { - private static MapValueImpl EMPTY = new MapValueImpl(new Value[0]); - - public static MapValue empty() { - return EMPTY; - } - - private final Value[] array; - private transient int cursor = 0; - - public MapValueImpl(Value[] array) { - this.array = array; - } - - @Override - public ValueType getValueType() { - return ValueType.MAP; - } - - @Override - public Value[] toKeyValueSeq() { - return Arrays.copyOf(array, array.length); - } - @Override - public int size() { - return array.length / 2; - } - - @Override - public boolean hasNext() { - return cursor < array.length; - } - @Override - public ValueRef nextKeyOrValue() { - return array[cursor++]; - } - - @Override - public void skipKeyOrValue() { - cursor++; - } - @Override - public void skipAll() { - while(hasNext()) { - skipKeyOrValue(); - } - } - - - private class MapImpl extends AbstractMap { - - private class EntrySet extends AbstractSet> { - - @Override - public int size() { - return array.length / 2; - } - - @Override - public Iterator> iterator() { - return new EntrySetIterator(); - } - } - - private class EntrySetIterator implements - Iterator> { - private int pos = 0; - - @Override - public boolean hasNext() { - return pos < array.length; - } - - @Override - public Entry next() { - if (pos >= array.length) { - throw new NoSuchElementException(); - } - - Value key = array[pos]; - Value value = array[pos + 1]; - Entry pair = new SimpleImmutableEntry(key, value); - - pos += 2; - return pair; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - private class KeySet extends AbstractSet { - @Override - public int size() { - return array.length / 2; - } - - @Override - public Iterator iterator() { - return new ValueIterator(0); - } - } - - private class ValueCollection extends AbstractCollection { - - @Override - public int size() { - return array.length / 2; - } - - @Override - public Iterator iterator() { - return new ValueIterator(1); - } - } - - private class ValueIterator implements Iterator { - private int pos; - - ValueIterator(int offset) { - this.pos = offset; - } - - @Override - public boolean hasNext() { - return pos < array.length; - } - - @Override - public Value next() { - if (pos >= array.length) { - throw new NoSuchElementException(); - } - Value v = array[pos]; - pos += 2; - return v; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - @Override - public Set> entrySet() { - return new EntrySet(); - } - - @Override - public Set keySet() { - return new KeySet(); - } - - @Override - public Collection values() { - return new ValueCollection(); - } - - - @Override - public Value get(Object key) { - for (int i = array.length - 2; i >= 0; i -= 2) { - if (array[i].equals(key)) { - return array[i + 1]; - } - } - return null; - } - } - - @Override - public Map toMap() { - return new MapImpl(); - } - - @Override - public void writeTo(MessagePacker pk) throws IOException { - pk.packMapHeader(array.length / 2); - for (int i = 0; i < array.length; i++) { - array[i].writeTo(pk); - } - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitMap(this); - } - - @Override - public MapValue toValue() { - return ValueFactory.newMap(array); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof Value)) { - return false; - } - Value v = (Value) o; - if (!v.isMap()) { - return false; - } - - Map mv = v.asMapValue().toMap(); - if (mv.size() != array.length / 2) { - return false; - } - - try { - for (int i = 0; i < array.length; i += 2) { - Value key = array[i]; - Value value = array[i + 1]; - if (!value.equals(mv.get(key))) { - return false; - } - } - } catch (ClassCastException ex) { - return false; - } catch (NullPointerException ex) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - int h = 0; - for (int i = 0; i < array.length; i += 2) { - h += array[i].hashCode() ^ array[i + 1].hashCode(); - } - return h; - } - - @Override - public String toString() { - return toString(new StringBuilder()).toString(); - } - - private StringBuilder toString(StringBuilder sb) { - if (array.length == 0) { - return sb.append("{}"); - } - sb.append("{"); - sb.append(array[0]); - sb.append(":"); - sb.append(array[1]); - for (int i = 2; i < array.length; i += 2) { - sb.append(","); - sb.append(array[i].toString()); - sb.append(":"); - sb.append(array[i + 1].toString()); - } - sb.append("}"); - return sb; - } - - - -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/NilValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/NilValueImpl.java deleted file mode 100644 index 3fea78364..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/NilValueImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessagePacker; -import org.msgpack.value.ValueType; -import org.msgpack.value.NilValue; -import org.msgpack.value.Value; -import org.msgpack.value.ValueVisitor; - -import java.io.IOException; - -/** -* Created on 5/30/14. -*/ -public class NilValueImpl extends AbstractValue implements NilValue { - - private static NilValue instance = new NilValueImpl(); - - public static NilValue getInstance() { - return instance; - } - - @Override - public ValueType getValueType() { - return ValueType.NIL; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof Value)) { - return false; - } - return ((Value) o).isNil(); - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public String toString() { - return "null"; - } - - @Override - public void writeTo(MessagePacker packer) throws IOException { - packer.packNil(); - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitNil(); - } - @Override - public NilValue toValue() { - return instance; - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/RawStringValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/RawStringValueImpl.java deleted file mode 100644 index 7fd0d61f2..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/RawStringValueImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessagePacker; -import org.msgpack.core.MessageStringCodingException; -import org.msgpack.value.*; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** -* Created on 5/30/14. -*/ -public class RawStringValueImpl extends RawValueImpl implements StringValue { - - public RawStringValueImpl(ByteBuffer byteBuffer) { - super(byteBuffer); - } - - @Override - public ValueType getValueType() { - return ValueType.STRING; - } - - @Override - public void accept(ValueVisitor visitor) { - visitor.visitString(this); - } - - @Override - public String toString() { - return super.toString(); - } - - @Override - public void writeTo(MessagePacker pk) throws IOException { - pk.packRawStringHeader(byteBuffer.remaining()); - pk.writePayload(byteBuffer); - } - - @Override - public StringValue toValue() { - return this; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof Value)) { - return false; - } - Value v = (Value) o; - if (!v.isString()) { - return false; - } - try { - return toString().equals(v.asString().toString()); - } catch (MessageStringCodingException ex) { - return false; - } - - } - - @Override - public int hashCode() { - return toString().hashCode(); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/RawValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/RawValueImpl.java deleted file mode 100644 index e9db5f129..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/RawValueImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessagePacker; -import org.msgpack.core.MessageStringCodingException; -import org.msgpack.core.buffer.MessageBuffer; -import org.msgpack.value.BinaryValue; -import org.msgpack.value.RawValue; -import org.msgpack.value.Value; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.*; - -/** -* Created on 5/30/14. -*/ -public abstract class RawValueImpl extends AbstractValue implements RawValue { - - protected final ByteBuffer byteBuffer; - private transient String decodedStringCache; - private transient MessageStringCodingException codingException; - - public RawValueImpl(ByteBuffer byteBuffer) { - this.byteBuffer = byteBuffer.slice(); - } - - @Override - public byte[] toByteArray() { - byte[] byteArray = new byte[byteBuffer.remaining()]; - byteBuffer.slice().get(byteArray); - return byteArray; - } - - @Override - public RawValue toValue() { - return this; - } - - @Override - public ByteBuffer toByteBuffer() { - return byteBuffer.asReadOnlyBuffer(); - } - - @Override - public MessageBuffer toMessageBuffer() { - return MessageBuffer.wrap(byteBuffer); - } - - @Override - public String toString() { - if (decodedStringCache == null) { - decodeString(); - } - if (codingException != null) { - throw codingException; - } - return decodedStringCache; - } - - - private synchronized void decodeString() { - if (decodedStringCache != null) { - return; - } - ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer(); - try { - CharsetDecoder reportDecoder = Charset.forName("UTF-8").newDecoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - readOnlyBuffer.position(0); - decodedStringCache = reportDecoder.decode(readOnlyBuffer).toString(); - } catch (UnsupportedCharsetException neverThrown) { - throw new AssertionError(neverThrown); - } catch (CharacterCodingException ex) { - codingException = new MessageStringCodingException(ex); - try { - CharsetDecoder replaceDecoder = Charset.forName("UTF-8").newDecoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - readOnlyBuffer.position(0); - decodedStringCache = replaceDecoder.decode(readOnlyBuffer).toString(); - } catch (UnsupportedCharsetException neverThrown) { - throw new AssertionError(neverThrown); - } catch (CharacterCodingException neverThrown) { - throw new AssertionError(neverThrown); - } - } - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof Value)) { - return false; - } - Value v = (Value) o; - if (!v.isBinary()) { - return false; - } - BinaryValue bv = v.asBinary(); - return bv.toByteBuffer().equals(byteBuffer); - } - - @Override - public int hashCode() { - return byteBuffer.hashCode(); - } - - @Override - public void writeTo(MessagePacker packer) throws IOException { - packer.packBinaryHeader(byteBuffer.remaining()); - packer.writePayload(byteBuffer); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/StringValueImpl.java b/msgpack-core/src/main/java/org/msgpack/value/impl/StringValueImpl.java deleted file mode 100644 index 4bbe14d6f..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/StringValueImpl.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.msgpack.value.impl; - -import org.msgpack.core.MessagePack; -import org.msgpack.core.MessagePacker; -import org.msgpack.core.MessageStringCodingException; -import org.msgpack.core.buffer.MessageBuffer; -import org.msgpack.value.*; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** -* Created on 5/30/14. -*/ -public class StringValueImpl extends AbstractValue implements StringValue { - - private final String value; - - public StringValueImpl(String value) { - this.value = value; - } - - @Override - public ValueType getValueType() { - return ValueType.STRING; - } - - @Override - public byte[] toByteArray() { - return value.getBytes(MessagePack.UTF8); - } - - @Override - public MessageBuffer toMessageBuffer() { - return MessageBuffer.wrap(toByteArray()); - } - - @Override - public String toString() { - return value; - } - - @Override - public ByteBuffer toByteBuffer() { - return toMessageBuffer().toByteBuffer(); - } - - @Override - public void writeTo(MessagePacker pk) throws IOException { - pk.packString(value); - } - @Override - public void accept(ValueVisitor visitor) { - visitor.visitString(this); - } - @Override - public StringValue toValue() { - return ValueFactory.newString(value); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (!(o instanceof Value)) { - return false; - } - Value v = (Value) o; - if (!v.isString()) { - return false; - } - try { - return v.asString().toString().equals(value); - } catch (MessageStringCodingException ex) { - return false; - } - } - - @Override - public int hashCode() { - return value.hashCode(); - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/value/impl/ValueUnion.java b/msgpack-core/src/main/java/org/msgpack/value/impl/ValueUnion.java deleted file mode 100644 index 0a282ef0d..000000000 --- a/msgpack-core/src/main/java/org/msgpack/value/impl/ValueUnion.java +++ /dev/null @@ -1,131 +0,0 @@ -// -// MessagePack for Java -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -package org.msgpack.value.impl; - -import java.util.List; -import java.util.Map; -import java.math.BigInteger; -import java.nio.ByteBuffer; - -import org.msgpack.value.Value; - -/** - * Union of multiple values so that we can minimize the object initialization cost - */ -class ValueUnion { - public static enum Type { - BOOLEAN, - LONG, - BIG_INTEGER, - DOUBLE, - BYTE_BUFFER, - STRING, - LIST, - MAP; - } - - private Type type; - - private long longValue; - private double doubleValue; - private Object objectValue; - - public void reset() { - this.type = null; - } - - public boolean isSet() { - return type != null; - } - - public Type getType() { - return type; - } - - public void setBoolean(boolean v) { - this.type = Type.BOOLEAN; - this.longValue = (v ? 1L : 0L); - } - - public boolean getBoolean() { - return longValue != 0L; - } - - public void setLong(long v) { - this.type = Type.LONG; - this.longValue = v; - } - - public long getLong() { - return longValue; - } - - public void setBigInteger(BigInteger v) { - this.type = Type.BIG_INTEGER; - this.objectValue = v; - } - - public BigInteger getBigInteger() { - return (BigInteger) objectValue; - } - - public void setDouble(double v) { - this.type = Type.DOUBLE; - this.doubleValue = v; - } - - public double getDouble() { - return doubleValue; - } - - public void setByteBuffer(ByteBuffer v) { - this.type = Type.BYTE_BUFFER; - this.objectValue = v; - } - - public ByteBuffer getByteBuffer() { - return (ByteBuffer) objectValue; - } - - public void setString(String v) { - this.type = Type.STRING; - this.objectValue = v; - } - - public String getString() { - return (String) objectValue; - } - - public void setList(List v) { - this.type = Type.LIST; - this.objectValue = v; - } - - @SuppressWarnings("unchecked") - public List getList() { - return (List) objectValue; - } - - public void setMap(Map v) { - this.type = Type.MAP; - this.objectValue = v; - } - - @SuppressWarnings("unchecked") - public Map getMap() { - return (Map) objectValue; - } -} diff --git a/msgpack-core/src/main/java/org/msgpack/core/example/MessagePackExample.java b/msgpack-core/src/test/java/org/msgpack/core/example/MessagePackExample.java similarity index 63% rename from msgpack-core/src/main/java/org/msgpack/core/example/MessagePackExample.java rename to msgpack-core/src/test/java/org/msgpack/core/example/MessagePackExample.java index 2720ab3f9..e8802a037 100644 --- a/msgpack-core/src/main/java/org/msgpack/core/example/MessagePackExample.java +++ b/msgpack-core/src/test/java/org/msgpack/core/example/MessagePackExample.java @@ -15,38 +15,51 @@ // package org.msgpack.core.example; -import org.msgpack.core.*; -import org.msgpack.core.buffer.MessageBuffer; -import org.msgpack.value.*; -import org.msgpack.value.holder.FloatHolder; -import org.msgpack.value.holder.IntegerHolder; -import org.msgpack.value.holder.ValueHolder; - -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.charset.CodingErrorAction; +import org.msgpack.core.MessageFormat; +import org.msgpack.core.MessagePack; +import org.msgpack.core.MessagePack.PackerConfig; +import org.msgpack.core.MessagePack.UnpackerConfig; +import org.msgpack.core.MessagePacker; +import org.msgpack.core.MessageUnpacker; +import org.msgpack.value.ArrayValue; +import org.msgpack.value.ExtensionValue; +import org.msgpack.value.FloatValue; +import org.msgpack.value.IntegerValue; +import org.msgpack.value.Value; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; /** * This class describes the usage of MessagePack v07 */ -public class MessagePackExample { - +public class MessagePackExample +{ + private MessagePackExample() + { + } /** * Basic usage example + * * @throws IOException */ - public static void basicUsage() throws IOException { - + public static void basicUsage() + throws IOException + { // Serialize with MessagePacker ByteArrayOutputStream out = new ByteArrayOutputStream(); MessagePacker packer = MessagePack.newDefaultPacker(out); packer - .packInt(1) - .packString("leo") - .packArrayHeader(2) - .packString("xxx-xxxx") - .packString("yyy-yyyy"); + .packInt(1) + .packString("leo") + .packArrayHeader(2) + .packString("xxx-xxxx") + .packString("yyy-yyyy"); packer.close(); // Deserialize with MessageUnpacker @@ -55,7 +68,7 @@ public static void basicUsage() throws IOException { String name = unpacker.unpackString(); // "leo" int numPhones = unpacker.unpackArrayHeader(); // 2 String[] phones = new String[numPhones]; - for(int i=0; i < numPhones; ++i) { + for (int i = 0; i < numPhones; ++i) { phones[i] = unpacker.unpackString(); // phones = {"xxx-xxxx", "yyy-yyyy"} } unpacker.close(); @@ -63,10 +76,11 @@ public static void basicUsage() throws IOException { System.out.println(String.format("id:%d, name:%s, phone:[%s]", id, name, join(phones))); } - private static String join(String[] in) { + private static String join(String[] in) + { StringBuilder s = new StringBuilder(); - for(int i=0; i 0) { + for (int i = 0; i < in.length; ++i) { + if (i > 0) { s.append(", "); } s.append(in[i]); @@ -76,10 +90,12 @@ private static String join(String[] in) { /** * Packing various types of data + * * @throws IOException */ - public static void packer() throws IOException { - + public static void packer() + throws IOException + { // Create a MesagePacker (encoder) instance ByteArrayOutputStream out = new ByteArrayOutputStream(); MessagePacker packer = MessagePack.newDefaultPacker(out); @@ -105,9 +121,9 @@ public static void packer() throws IOException { packer.writePayload(s); // pack arrays - int[] arr = new int[]{3, 5, 1, 0, -1, 255}; + int[] arr = new int[] {3, 5, 1, 0, -1, 255}; packer.packArrayHeader(arr.length); - for(int v : arr) { + for (int v : arr) { packer.packInt(v); } @@ -121,39 +137,32 @@ public static void packer() throws IOException { packer.packInt(2); // pack binary data - byte[] ba = new byte[]{1, 2, 3, 4}; + byte[] ba = new byte[] {1, 2, 3, 4}; packer.packBinaryHeader(ba.length); packer.writePayload(ba); - // Write ext type data: https://github.com/msgpack/msgpack/blob/master/spec.md#ext-format-family byte[] extData = "custom data type".getBytes(MessagePack.UTF8); - packer.packExtendedTypeHeader(1, 10); // type number [0, 127], data byte length + packer.packExtensionTypeHeader((byte) 1, 10); // type number [0, 127], data byte length packer.writePayload(extData); // Succinct syntax for packing packer - .packInt(1) - .packString("leo") - .packArrayHeader(2) - .packString("xxx-xxxx") - .packString("yyy-yyyy"); - - - // [Advanced] write data using ByteBuffer - ByteBuffer bb = ByteBuffer.wrap(new byte[] {'b', 'i', 'n', 'a', 'r', 'y', 'd', 'a', 't', 'a'}); - packer.packBinaryHeader(bb.remaining()); - packer.writePayload(bb); - + .packInt(1) + .packString("leo") + .packArrayHeader(2) + .packString("xxx-xxxx") + .packString("yyy-yyyy"); } - /** * An example of reading and writing MessagePack data + * * @throws IOException */ - public static void readAndWriteFile() throws IOException { - + public static void readAndWriteFile() + throws IOException + { File tempFile = File.createTempFile("target/tmp", ".txt"); tempFile.deleteOnExit(); @@ -167,99 +176,92 @@ public static void readAndWriteFile() throws IOException { // Read packed data from a file. No need exists to wrap the file stream with an buffer MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(new FileInputStream(tempFile)); - while(unpacker.hasNext()) { + while (unpacker.hasNext()) { // [Advanced] You can check the detailed data format with getNextFormat() // Here is a list of message pack data format: https://github.com/msgpack/msgpack/blob/master/spec.md#overview MessageFormat format = unpacker.getNextFormat(); // Alternatively you can use ValueHolder to extract a value of any type // NOTE: Value interface is in a preliminary state, so the following code might change in future releases - ValueHolder v = new ValueHolder(); - format = unpacker.unpackValue(v); - switch(format.getValueType()) { + Value v = unpacker.unpackValue(); + switch (v.getValueType()) { case NIL: - Value nil = v.get(); - nil.isNil(); // true + v.isNilValue(); // true System.out.println("read nil"); break; case BOOLEAN: - boolean b = v.get().asBoolean().toBoolean(); + boolean b = v.asBooleanValue().getBoolean(); System.out.println("read boolean: " + b); break; case INTEGER: - IntegerHolder ih = v.getIntegerHolder(); - if(ih.isValidInt()) { // int range check [-2^31-1, 2^31-1] - int i = ih.asInt(); + IntegerValue iv = v.asIntegerValue(); + if (iv.isInIntRange()) { + int i = iv.toInt(); System.out.println("read int: " + i); } - else { - long l = ih.asLong(); + else if (iv.isInLongRange()) { + long l = iv.toLong(); System.out.println("read long: " + l); } + else { + BigInteger i = iv.toBigInteger(); + System.out.println("read long: " + i); + } break; case FLOAT: - FloatHolder fh = v.getFloatHolder(); - float f = fh.toFloat(); // read as float - double d = fh.toDouble(); // read as double + FloatValue fv = v.asFloatValue(); + float f = fv.toFloat(); // use as float + double d = fv.toDouble(); // use as double System.out.println("read float: " + d); break; case STRING: - String s = v.get().asString().toString(); + String s = v.asStringValue().asString(); System.out.println("read string: " + s); break; case BINARY: - // Message buffer is an efficient byte buffer - MessageBuffer mb = v.get().asBinary().toMessageBuffer(); - System.out.println("read binary: " + mb.toHexString(0, mb.size())); + byte[] mb = v.asBinaryValue().asByteArray(); + System.out.println("read binary: size=" + mb.length); break; case ARRAY: - ArrayValue arr = v.get().asArrayValue(); - for(ValueRef a : arr) { - System.out.println("read array element: " + a); + ArrayValue a = v.asArrayValue(); + for (Value e : a) { + System.out.println("read array element: " + e); } break; - case EXTENDED: - ExtendedValue ev = v.get().asExtended(); - int extType = ev.getExtType(); - byte[] extValue = ev.toByteArray(); + case EXTENSION: + ExtensionValue ev = v.asExtensionValue(); + byte extType = ev.getType(); + byte[] extValue = ev.getData(); break; } } - } /** * Example of using custom MessagePack configuration + * * @throws IOException */ - public static void configuration() throws IOException { - - // Build a conifiguration - MessagePack.Config config = new MessagePack.ConfigBuilder() - .onMalFormedInput(CodingErrorAction.REPLACE) // Drop malformed and unmappable UTF-8 characters - .onUnmappableCharacter(CodingErrorAction.REPLACE) - .packerBufferSize(8192 * 2) - .build(); - // Create a that uses this configuration - MessagePack msgpack = new MessagePack(config); - - // Pack data + public static void configuration() + throws IOException + { ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = msgpack.newPacker(out); + PackerConfig packerConfig = new PackerConfig(); + packerConfig.smallStringOptimizationThreshold = 256; // String + MessagePacker packer = packerConfig.newPacker(out); + packer.packInt(10); packer.packBoolean(true); packer.close(); // Unpack data + UnpackerConfig unpackerConfig = new UnpackerConfig(); + unpackerConfig.stringDecoderBufferSize = 16 * 1024; // If your data contains many large strings (the default is 8k) + byte[] packedData = out.toByteArray(); - MessageUnpacker unpacker = msgpack.newUnpacker(packedData); + MessageUnpacker unpacker = unpackerConfig.newUnpacker(packedData); int i = unpacker.unpackInt(); // 10 boolean b = unpacker.unpackBoolean(); // true unpacker.close(); } - - - - - } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessageFormatTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessageFormatTest.scala index c4e381cd0..be9d270cd 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessageFormatTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessageFormatTest.scala @@ -1,65 +1,84 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core -import org.scalatest.exceptions.TestFailedException import org.msgpack.core.MessagePack.Code -import scala.util.Random import org.msgpack.value.ValueType +import org.scalatest.exceptions.TestFailedException + +import scala.util.Random /** * Created on 2014/05/07. */ -class MessageFormatTest extends MessagePackSpec { - +class MessageFormatTest + extends MessagePackSpec { "MessageFormat" should { "cover all byte codes" in { - - def checkV(b:Byte, tpe:ValueType) { + def checkV(b: Byte, tpe: ValueType) { try MessageFormat.valueOf(b).getValueType shouldBe tpe catch { - case e:TestFailedException => + case e: TestFailedException => error(f"Failure when looking at byte ${b}%02x") throw e } } - def checkF(b:Byte, f:MessageFormat) { + def checkF(b: Byte, f: MessageFormat) { MessageFormat.valueOf(b) shouldBe f } - def check(b:Byte, tpe:ValueType, f:MessageFormat) { + def check(b: Byte, tpe: ValueType, f: MessageFormat) { checkV(b, tpe) checkF(b, f) } - for(i <- 0 until 0x7f) + for (i <- 0 until 0x7f) { check(i.toByte, ValueType.INTEGER, MessageFormat.POSFIXINT) + } - for(i <- 0x80 until 0x8f) + for (i <- 0x80 until 0x8f) { check(i.toByte, ValueType.MAP, MessageFormat.FIXMAP) + } - for(i <- 0x90 until 0x9f) + for (i <- 0x90 until 0x9f) { check(i.toByte, ValueType.ARRAY, MessageFormat.FIXARRAY) + } check(Code.NIL, ValueType.NIL, MessageFormat.NIL) MessageFormat.valueOf(Code.NEVER_USED) shouldBe MessageFormat.NEVER_USED - for(i <- Seq(Code.TRUE, Code.FALSE)) + for (i <- Seq(Code.TRUE, Code.FALSE)) { check(i, ValueType.BOOLEAN, MessageFormat.BOOLEAN) + } check(Code.BIN8, ValueType.BINARY, MessageFormat.BIN8) check(Code.BIN16, ValueType.BINARY, MessageFormat.BIN16) check(Code.BIN32, ValueType.BINARY, MessageFormat.BIN32) - check(Code.FIXEXT1, ValueType.EXTENDED, MessageFormat.FIXEXT1) - check(Code.FIXEXT2, ValueType.EXTENDED, MessageFormat.FIXEXT2) - check(Code.FIXEXT4, ValueType.EXTENDED, MessageFormat.FIXEXT4) - check(Code.FIXEXT8, ValueType.EXTENDED, MessageFormat.FIXEXT8) - check(Code.FIXEXT16, ValueType.EXTENDED, MessageFormat.FIXEXT16) - check(Code.EXT8, ValueType.EXTENDED, MessageFormat.EXT8) - check(Code.EXT16, ValueType.EXTENDED, MessageFormat.EXT16) - check(Code.EXT32, ValueType.EXTENDED, MessageFormat.EXT32) + check(Code.FIXEXT1, ValueType.EXTENSION, MessageFormat.FIXEXT1) + check(Code.FIXEXT2, ValueType.EXTENSION, MessageFormat.FIXEXT2) + check(Code.FIXEXT4, ValueType.EXTENSION, MessageFormat.FIXEXT4) + check(Code.FIXEXT8, ValueType.EXTENSION, MessageFormat.FIXEXT8) + check(Code.FIXEXT16, ValueType.EXTENSION, MessageFormat.FIXEXT16) + check(Code.EXT8, ValueType.EXTENSION, MessageFormat.EXT8) + check(Code.EXT16, ValueType.EXTENSION, MessageFormat.EXT16) + check(Code.EXT32, ValueType.EXTENSION, MessageFormat.EXT32) check(Code.INT8, ValueType.INTEGER, MessageFormat.INT8) @@ -82,13 +101,12 @@ class MessageFormatTest extends MessagePackSpec { check(Code.ARRAY16, ValueType.ARRAY, MessageFormat.ARRAY16) check(Code.ARRAY32, ValueType.ARRAY, MessageFormat.ARRAY32) - for(i <- 0xe0 to 0xff) + for (i <- 0xe0 to 0xff) { check(i.toByte, ValueType.INTEGER, MessageFormat.NEGFIXINT) - + } } "improve the valueOf performance" in { - val N = 1000000 val idx = (0 until N).map(x => Random.nextInt(256).toByte).toArray[Byte] @@ -98,7 +116,7 @@ class MessageFormatTest extends MessagePackSpec { time("lookup", repeat = 10) { block("switch") { var i = 0 - while(i < N) { + while (i < N) { MessageFormat.toMessageFormat(idx(i)) i += 1 } @@ -106,18 +124,12 @@ class MessageFormatTest extends MessagePackSpec { block("table") { var i = 0 - while(i < N) { + while (i < N) { MessageFormat.valueOf(idx(i)) i += 1 } } - } - } - } - - - } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackSpec.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackSpec.scala index 8eec662c4..b8be3bed0 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackSpec.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackSpec.scala @@ -15,12 +15,14 @@ // package org.msgpack.core +import java.io.ByteArrayOutputStream + import org.scalatest._ +import org.scalatest.prop.PropertyChecks import xerial.core.log.{LogLevel, Logger} import xerial.core.util.{TimeReport, Timer} + import scala.language.implicitConversions -import org.scalatest.prop.PropertyChecks -import java.io.ByteArrayOutputStream trait MessagePackSpec extends WordSpec @@ -32,43 +34,36 @@ trait MessagePackSpec with Benchmark with Logger { - implicit def toTag(s:String) : Tag = Tag(s) + implicit def toTag(s: String): Tag = Tag(s) - def toHex(arr:Array[Byte]) = arr.map(x => f"$x%02x").mkString(" ") + def toHex(arr: Array[Byte]) = arr.map(x => f"$x%02x").mkString(" ") - - def createMessagePackData(f: MessagePacker => Unit) : Array[Byte] = { - val b = new ByteArrayOutputStream() + def createMessagePackData(f: MessagePacker => Unit): Array[Byte] = { + val b = new + ByteArrayOutputStream() val packer = MessagePack.newDefaultPacker(b) f(packer) packer.close() b.toByteArray } - - - - } - -trait Benchmark extends Timer { +trait Benchmark + extends Timer { val numWarmUpRuns = 10 - override protected def time[A](blockName: String, logLevel: LogLevel, repeat: Int)(f: => A): TimeReport = { - super.time(blockName, logLevel=LogLevel.INFO, repeat)(f) + super.time(blockName, logLevel = LogLevel.INFO, repeat)(f) } override protected def block[A](name: String, repeat: Int)(f: => A): TimeReport = { var i = 0 - while(i < numWarmUpRuns) { + while (i < numWarmUpRuns) { f i += 1 } super.block(name, repeat)(f) - } - } \ No newline at end of file diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackTest.scala index 59d71b6e8..112c3e5a7 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackTest.scala @@ -15,26 +15,27 @@ // package org.msgpack.core -import org.msgpack.value.Value -import org.msgpack.value.holder.ValueHolder - -import scala.util.Random -import MessagePack.Code import java.io.ByteArrayOutputStream import java.math.BigInteger import java.nio.CharBuffer -import java.nio.charset.{UnmappableCharacterException, CodingErrorAction} +import java.nio.charset.{CodingErrorAction, UnmappableCharacterException} + +import org.msgpack.core.MessagePack.Code +import org.msgpack.core.MessagePack.{UnpackerConfig, PackerConfig} +import org.msgpack.value.{Value, Variable} + +import scala.util.Random /** * Created on 2014/05/07. */ -class MessagePackTest extends MessagePackSpec { +class MessagePackTest extends MessagePackSpec { def isValidUTF8(s: String) = { MessagePack.UTF8.newEncoder().canEncode(s) } - def containsUnmappableCharacter(s: String) : Boolean = { + def containsUnmappableCharacter(s: String): Boolean = { try { MessagePack.UTF8.newEncoder().onUnmappableCharacter(CodingErrorAction.REPORT).encode(CharBuffer.wrap(s)) false @@ -59,6 +60,36 @@ class MessagePackTest extends MessagePackSpec { } } + "detect fixarray values" in { + val packer = MessagePack.newDefaultBufferPacker() + packer.packArrayHeader(0) + packer.close + val bytes = packer.toByteArray + MessagePack.newDefaultUnpacker(bytes).unpackArrayHeader() shouldBe 0 + try { + MessagePack.newDefaultUnpacker(bytes).unpackMapHeader() + fail("Shouldn't reach here") + } + catch { + case e: MessageTypeException => // OK + } + } + + "detect fixmap values" in { + val packer = MessagePack.newDefaultBufferPacker() + packer.packMapHeader(0) + packer.close + val bytes = packer.toByteArray + MessagePack.newDefaultUnpacker(bytes).unpackMapHeader() shouldBe 0 + try { + MessagePack.newDefaultUnpacker(bytes).unpackArrayHeader() + fail("Shouldn't reach here") + } + catch { + case e: MessageTypeException => // OK + } + } + "detect fixint quickly" in { val N = 100000 @@ -117,39 +148,52 @@ class MessagePackTest extends MessagePackSpec { } - def check[A](v: A, pack: MessagePacker => Unit, unpack: MessageUnpacker => A, msgpack:MessagePack = MessagePack.DEFAULT): Unit = { + def check[A]( + v: A, + pack: MessagePacker => Unit, + unpack: MessageUnpacker => A, + packerConfig: PackerConfig = new PackerConfig(), + unpackerConfig: UnpackerConfig = new UnpackerConfig() + ): Unit = { var b: Array[Byte] = null try { val bs = new ByteArrayOutputStream() - val packer = msgpack.newPacker(bs) + val packer = packerConfig.newPacker(bs) pack(packer) packer.close() b = bs.toByteArray - val unpacker = msgpack.newUnpacker(b) + val unpacker = unpackerConfig.newUnpacker(b) val ret = unpack(unpacker) ret shouldBe v } catch { case e: Exception => warn(e.getMessage) - if (b != null) + if (b != null) { warn(s"packed data (size:${b.length}): ${toHex(b)}") + } throw e } } - def checkException[A](v: A, pack: MessagePacker => Unit, unpack: MessageUnpacker => A, msgpack:MessagePack=MessagePack.DEFAULT) : Unit = { + def checkException[A]( + v: A, + pack: MessagePacker => Unit, + unpack: MessageUnpacker => A, + packerConfig: PackerConfig = new PackerConfig(), + unpaackerConfig: UnpackerConfig = new UnpackerConfig() + ): Unit = { var b: Array[Byte] = null val bs = new ByteArrayOutputStream() - val packer = msgpack.newPacker(bs) + val packer = packerConfig.newPacker(bs) pack(packer) packer.close() b = bs.toByteArray - val unpacker = msgpack.newUnpacker(b) + val unpacker = unpaackerConfig.newUnpacker(b) val ret = unpack(unpacker) fail("cannot not reach here") @@ -160,30 +204,29 @@ class MessagePackTest extends MessagePackSpec { checkException[A](v, pack, unpack) } catch { - case e:MessageIntegerOverflowException => // OK + case e: MessageIntegerOverflowException => // OK } } - - - - "pack/unpack primitive values" taggedAs("prim") in { - forAll { (v: Boolean) => check(v, _.packBoolean(v), _.unpackBoolean)} - forAll { (v: Byte) => check(v, _.packByte(v), _.unpackByte)} - forAll { (v: Short) => check(v, _.packShort(v), _.unpackShort)} - forAll { (v: Int) => check(v, _.packInt(v), _.unpackInt)} - forAll { (v: Float) => check(v, _.packFloat(v), _.unpackFloat)} - forAll { (v: Long) => check(v, _.packLong(v), _.unpackLong)} - forAll { (v: Double) => check(v, _.packDouble(v), _.unpackDouble)} - check(null, _.packNil, _.unpackNil()) + "pack/unpack primitive values" taggedAs ("prim") in { + forAll { (v: Boolean) => check(v, _.packBoolean(v), _.unpackBoolean) } + forAll { (v: Byte) => check(v, _.packByte(v), _.unpackByte) } + forAll { (v: Short) => check(v, _.packShort(v), _.unpackShort) } + forAll { (v: Int) => check(v, _.packInt(v), _.unpackInt) } + forAll { (v: Float) => check(v, _.packFloat(v), _.unpackFloat) } + forAll { (v: Long) => check(v, _.packLong(v), _.unpackLong) } + forAll { (v: Double) => check(v, _.packDouble(v), _.unpackDouble) } + check(null, _.packNil, { unpacker => unpacker.unpackNil(); null }) } - "pack/unpack integer values" taggedAs("int") in { - val sampleData = Seq[Long](Int.MinValue.toLong - 10, -65535, -8191, -1024, -255, -127, -63, -31, -15, -7, -3, -1, 0, 2, 4, 8, 16, 32, 64, 128, 256, 1024, 8192, 65536, Int.MaxValue.toLong + 10) - for(v <- sampleData) { + "pack/unpack integer values" taggedAs ("int") in { + val sampleData = Seq[Long](Int.MinValue.toLong - + 10, -65535, -8191, -1024, -255, -127, -63, -31, -15, -7, -3, -1, 0, 2, 4, 8, 16, 32, 64, 128, 256, 1024, 8192, 65536, + Int.MaxValue.toLong + 10) + for (v <- sampleData) { check(v, _.packLong(v), _.unpackLong) - if(v.isValidInt) { + if (v.isValidInt) { val vi = v.toInt check(vi, _.packInt(vi), _.unpackInt) } @@ -191,7 +234,7 @@ class MessagePackTest extends MessagePackSpec { checkOverflow(v, _.packLong(v), _.unpackInt) } - if(v.isValidShort) { + if (v.isValidShort) { val vi = v.toShort check(vi, _.packShort(vi), _.unpackShort) } @@ -199,7 +242,7 @@ class MessagePackTest extends MessagePackSpec { checkOverflow(v, _.packLong(v), _.unpackShort) } - if(v.isValidByte) { + if (v.isValidByte) { val vi = v.toByte check(vi, _.packByte(vi), _.unpackByte) } @@ -211,23 +254,23 @@ class MessagePackTest extends MessagePackSpec { } - "pack/unpack BigInteger" taggedAs("bi") in { + "pack/unpack BigInteger" taggedAs ("bi") in { forAll { (a: Long) => val v = BigInteger.valueOf(a) check(v, _.packBigInteger(v), _.unpackBigInteger) } - for(bi <- Seq(BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(1)))) { + for (bi <- Seq(BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(1)))) { check(bi, _.packBigInteger(bi), _.unpackBigInteger()) } - for(bi <- Seq(BigInteger.valueOf(Long.MaxValue).shiftLeft(10))) { + for (bi <- Seq(BigInteger.valueOf(Long.MaxValue).shiftLeft(10))) { try { checkException(bi, _.packBigInteger(bi), _.unpackBigInteger()) fail("cannot reach here") } catch { - case e:IllegalArgumentException => // OK + case e: IllegalArgumentException => // OK } } @@ -245,14 +288,14 @@ class MessagePackTest extends MessagePackSpec { "pack/unpack large strings" taggedAs ("large-string") in { // Large string val strLen = Seq(1000, 2000, 10000, 50000, 100000, 500000) - for(l <- strLen) { - val v : String = Iterator.continually(Random.nextString(l * 10)).find(isValidUTF8).get + for (l <- strLen) { + val v: String = Iterator.continually(Random.nextString(l * 10)).find(isValidUTF8).get check(v, _.packString(v), _.unpackString) } } - "report errors when packing/unpacking malformed strings" taggedAs("malformed") in { + "report errors when packing/unpacking malformed strings" taggedAs ("malformed") in { // TODO produce malformed utf-8 strings in Java8" pending // Create 100 malformed UTF8 Strings @@ -287,45 +330,37 @@ class MessagePackTest extends MessagePackSpec { } } - "report errors when packing/unpacking strings that contain unmappable characters" taggedAs("unmap") in { + "report errors when packing/unpacking strings that contain unmappable characters" taggedAs ("unmap") in { val unmappable = Array[Byte](0xfc.toByte, 0x0a.toByte) //val unmappableChar = Array[Char](new Character(0xfc0a).toChar) // Report error on unmappable character - val config = new MessagePack.ConfigBuilder().onMalFormedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT).build() - val msgpack = new MessagePack(config) + val unpackerConfig = new UnpackerConfig() + unpackerConfig.actionOnMalformedString = CodingErrorAction.REPORT + unpackerConfig.actionOnUnmappableString = CodingErrorAction.REPORT - for(bytes <- Seq(unmappable)) { + for (bytes <- Seq(unmappable)) { When("unpacking") try { - checkException(bytes, - { packer => + checkException(bytes, { packer => packer.packRawStringHeader(bytes.length) packer.writePayload(bytes) }, _.unpackString(), - msgpack) + new PackerConfig(), + unpackerConfig) } catch { - case e:MessageStringCodingException => // OK + case e: MessageStringCodingException => // OK } - -// When("packing") -// try { -// val s = new String(unmappableChar) -// checkException(s, _.packString(s), _.unpackString()) -// } -// catch { -// case e:MessageStringCodingException => // OK -// } - } + } } "pack/unpack binary" taggedAs ("binary") in { forAll { (v: Array[Byte]) => - check(v, { packer => packer.packBinaryHeader(v.length); packer.writePayload(v)}, { unpacker => + check(v, { packer => packer.packBinaryHeader(v.length); packer.writePayload(v) }, { unpacker => val len = unpacker.unpackBinaryHeader() val out = new Array[Byte](len) unpacker.readPayload(out, 0, len) @@ -335,10 +370,10 @@ class MessagePackTest extends MessagePackSpec { } val len = Seq(1000, 2000, 10000, 50000, 100000, 500000) - for(l <- len) { + for (l <- len) { val v = new Array[Byte](l) Random.nextBytes(v) - check(v, { packer => packer.packBinaryHeader(v.length); packer.writePayload(v)}, { unpacker => + check(v, { packer => packer.packBinaryHeader(v.length); packer.writePayload(v) }, { unpacker => val len = unpacker.unpackBinaryHeader() val out = new Array[Byte](len) unpacker.readPayload(out, 0, len) @@ -359,14 +394,15 @@ class MessagePackTest extends MessagePackSpec { }, { unpacker => val len = unpacker.unpackArrayHeader() val out = new Array[Int](len) - for (i <- 0 until v.length) + for (i <- 0 until v.length) { out(i) = unpacker.unpackInt + } out } ) } - for(l <- testHeaderLength) { + for (l <- testHeaderLength) { check(l, _.packArrayHeader(l), _.unpackArrayHeader()) } @@ -393,14 +429,15 @@ class MessagePackTest extends MessagePackSpec { }, { unpacker => val len = unpacker.unpackMapHeader() val b = Seq.newBuilder[(Int, String)] - for (i <- 0 until len) + for (i <- 0 until len) { b += ((unpacker.unpackInt, unpacker.unpackString)) + } b.result } ) } - for(l <- testHeaderLength) { + for (l <- testHeaderLength) { check(l, _.packMapHeader(l), _.unpackMapHeader()) } @@ -414,19 +451,18 @@ class MessagePackTest extends MessagePackSpec { } - "pack/unpack extended types" taggedAs("ext") in { - forAll { (dataLen: Int, tpe: Int) => + "pack/unpack extension types" taggedAs ("ext") in { + forAll { (dataLen: Int, tpe: Byte) => val l = Math.abs(dataLen) - val t = Math.abs(tpe) % 128 whenever(l >= 0) { - val ext = new ExtendedTypeHeader(l, t) - check(ext, _.packExtendedTypeHeader(ext.getType, ext.getLength), _.unpackExtendedTypeHeader()) + val ext = new ExtensionTypeHeader(ExtensionTypeHeader.checkedCastToByte(tpe), l) + check(ext, _.packExtensionTypeHeader(ext.getType, ext.getLength), _.unpackExtensionTypeHeader()) } } - for(l <- testHeaderLength) { - val ext = new ExtendedTypeHeader(l, Random.nextInt(128)) - check(ext, _.packExtendedTypeHeader(ext.getType, ext.getLength), _.unpackExtendedTypeHeader()) + for (l <- testHeaderLength) { + val ext = new ExtensionTypeHeader(ExtensionTypeHeader.checkedCastToByte(Random.nextInt(128)), l) + check(ext, _.packExtensionTypeHeader(ext.getType, ext.getLength), _.unpackExtensionTypeHeader()) } } @@ -444,23 +480,22 @@ class MessagePackTest extends MessagePackSpec { } } }, { unpacker => - val holder = new ValueHolder() - unpacker.unpackValue(holder) - val v = holder.get() - - v.asArrayValue().toValueArray.map { m => + val v = new Variable() + unpacker.unpackValue(v) + import scala.collection.JavaConversions._ + v.asArrayValue().map { m => val mv = m.asMapValue() - val kvs = mv.toKeyValueSeq + val kvs = mv.getKeyValueArray kvs.grouped(2).map({ kvp: Array[Value] => val k = kvp(0) val v = kvp(1) - (k.asString().toString, v.asString().toString) + (k.asStringValue().asString, v.asStringValue().asString) }).toMap }.toList }) } } -} \ No newline at end of file +} diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackerTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackerTest.scala index 4fb621e46..2fbe461d1 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessagePackerTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessagePackerTest.scala @@ -15,25 +15,24 @@ // package org.msgpack.core -import java.io.{FileInputStream, FileOutputStream, File, ByteArrayOutputStream} +import java.io.{ByteArrayOutputStream, File, FileInputStream, FileOutputStream} -import org.msgpack.core.buffer.{ChannelBufferOutput, MessageBufferOutput, OutputStreamBufferOutput} +import org.msgpack.core.MessagePack.{UnpackerConfig, PackerConfig} +import org.msgpack.core.buffer.{ChannelBufferOutput, OutputStreamBufferOutput} +import org.msgpack.value.ValueFactory import xerial.core.io.IOUtil import scala.util.Random -import org.msgpack.value.ValueFactory /** * */ class MessagePackerTest extends MessagePackSpec { - val msgpack = MessagePack.DEFAULT - - def verifyIntSeq(answer:Array[Int], packed:Array[Byte]) { - val unpacker = msgpack.newUnpacker(packed) + def verifyIntSeq(answer: Array[Int], packed: Array[Byte]) { + val unpacker = MessagePack.newDefaultUnpacker(packed) val b = Array.newBuilder[Int] - while(unpacker.hasNext) { + while (unpacker.hasNext) { b += unpacker.unpackInt() } val result = b.result @@ -49,7 +48,8 @@ class MessagePackerTest extends MessagePackSpec { def createTempFileWithOutputStream = { val f = createTempFile - val out = new FileOutputStream(f) + val out = new + FileOutputStream(f) (f, out) } @@ -64,37 +64,48 @@ class MessagePackerTest extends MessagePackSpec { "reset the internal states" in { val intSeq = (0 until 100).map(i => Random.nextInt).toArray - val b = new ByteArrayOutputStream - val packer = msgpack.newPacker(b) + val b = new + ByteArrayOutputStream + val packer = MessagePack.newDefaultPacker(b) intSeq foreach packer.packInt packer.close verifyIntSeq(intSeq, b.toByteArray) val intSeq2 = intSeq.reverse - val b2 = new ByteArrayOutputStream - packer.reset(new OutputStreamBufferOutput(b2)) + val b2 = new + ByteArrayOutputStream + packer + .reset(new + OutputStreamBufferOutput(b2)) intSeq2 foreach packer.packInt packer.close verifyIntSeq(intSeq2, b2.toByteArray) val intSeq3 = intSeq2.sorted - val b3 = new ByteArrayOutputStream - packer.reset(new OutputStreamBufferOutput(b3)) + val b3 = new + ByteArrayOutputStream + packer + .reset(new + OutputStreamBufferOutput(b3)) intSeq3 foreach packer.packInt packer.close verifyIntSeq(intSeq3, b3.toByteArray) } - "improve the performance via reset method" taggedAs("reset") in { + "improve the performance via reset method" taggedAs ("reset") in { val N = 1000 val t = time("packer", repeat = 10) { block("no-buffer-reset") { - val out = new ByteArrayOutputStream - IOUtil.withResource(msgpack.newPacker(out)) { packer => + val out = new + ByteArrayOutputStream + IOUtil.withResource(MessagePack.newDefaultPacker(out)) { packer => for (i <- 0 until N) { - val outputStream = new ByteArrayOutputStream() - packer.reset(new OutputStreamBufferOutput(outputStream)) + val outputStream = new + ByteArrayOutputStream() + packer + .reset(new + OutputStreamBufferOutput(outputStream)) packer.packInt(0) packer.flush() } @@ -102,11 +113,15 @@ class MessagePackerTest extends MessagePackSpec { } block("buffer-reset") { - val out = new ByteArrayOutputStream - IOUtil.withResource(msgpack.newPacker(out)) { packer => - val bufferOut = new OutputStreamBufferOutput(new ByteArrayOutputStream()) + val out = new + ByteArrayOutputStream + IOUtil.withResource(MessagePack.newDefaultPacker(out)) { packer => + val bufferOut = new + OutputStreamBufferOutput(new + ByteArrayOutputStream()) for (i <- 0 until N) { - val outputStream = new ByteArrayOutputStream() + val outputStream = new + ByteArrayOutputStream() bufferOut.reset(outputStream) packer.reset(bufferOut) packer.packInt(0) @@ -117,33 +132,30 @@ class MessagePackerTest extends MessagePackSpec { } t("buffer-reset").averageWithoutMinMax should be <= t("no-buffer-reset").averageWithoutMinMax - } "pack larger string array than byte buf" taggedAs ("larger-string-array-than-byte-buf") in { // Based on https://github.com/msgpack/msgpack-java/issues/154 - // TODO: Refactor this test code to fit other ones. def test(bufferSize: Int, stringSize: Int): Boolean = { - val msgpack = new MessagePack(new MessagePack.ConfigBuilder().packerBufferSize(bufferSize).build) val str = "a" * stringSize - val rawString = ValueFactory.newRawString(str.getBytes("UTF-8")) + val rawString = ValueFactory.newString(str.getBytes("UTF-8")) val array = ValueFactory.newArray(rawString) - val out = new ByteArrayOutputStream() - val packer = msgpack.newPacker(out) + val out = new ByteArrayOutputStream(bufferSize) + val packer = MessagePack.newDefaultPacker(out) packer.packValue(array) packer.close() out.toByteArray true } - val testCases = List( + val testCases = Seq( 32 -> 30, 33 -> 31, 32 -> 31, 34 -> 32 ) - testCases.foreach{ + testCases.foreach { case (bufferSize, stringSize) => test(bufferSize, stringSize) } } @@ -154,20 +166,28 @@ class MessagePackerTest extends MessagePackSpec { packer.packInt(99) packer.close - val up0 = MessagePack.newDefaultUnpacker(new FileInputStream(f0)) + val up0 = MessagePack + .newDefaultUnpacker(new + FileInputStream(f0)) up0.unpackInt shouldBe 99 up0.hasNext shouldBe false up0.close val (f1, out1) = createTempFileWithOutputStream - packer.reset(new OutputStreamBufferOutput(out1)) + packer + .reset(new + OutputStreamBufferOutput(out1)) packer.packInt(99) packer.flush - packer.reset(new OutputStreamBufferOutput(out1)) + packer + .reset(new + OutputStreamBufferOutput(out1)) packer.packString("hello") packer.close - val up1 = MessagePack.newDefaultUnpacker(new FileInputStream(f1)) + val up1 = MessagePack + .newDefaultUnpacker(new + FileInputStream(f1)) up1.unpackInt shouldBe 99 up1.unpackString shouldBe "hello" up1.hasNext shouldBe false @@ -180,41 +200,86 @@ class MessagePackerTest extends MessagePackSpec { packer.packInt(99) packer.close - val up0 = MessagePack.newDefaultUnpacker(new FileInputStream(f0)) + val up0 = MessagePack + .newDefaultUnpacker(new + FileInputStream(f0)) up0.unpackInt shouldBe 99 up0.hasNext shouldBe false up0.close val (f1, out1) = createTempFileWithChannel - packer.reset(new ChannelBufferOutput(out1)) + packer + .reset(new + ChannelBufferOutput(out1)) packer.packInt(99) packer.flush - packer.reset(new ChannelBufferOutput(out1)) + packer + .reset(new + ChannelBufferOutput(out1)) packer.packString("hello") packer.close - val up1 = MessagePack.newDefaultUnpacker(new FileInputStream(f1)) + val up1 = MessagePack + .newDefaultUnpacker(new + FileInputStream(f1)) up1.unpackInt shouldBe 99 up1.unpackString shouldBe "hello" up1.hasNext shouldBe false up1.close } + + "pack a lot of String within expected time" in { + val count = 20000 + + def measureDuration(outputStream: java.io.OutputStream) = { + val packer = MessagePack.newDefaultPacker(outputStream) + var i = 0 + while (i < count) { + packer.packString("0123456789ABCDEF") + i += 1 + } + packer.close + } + + val t = time("packString into OutputStream", repeat = 10) { + block("byte-array-output-stream") { + measureDuration(new ByteArrayOutputStream()) + } + + block("file-output-stream") { + val (_, fileOutput) = createTempFileWithOutputStream + measureDuration(fileOutput) + } + } + t("file-output-stream").averageWithoutMinMax shouldBe < (t("byte-array-output-stream").averageWithoutMinMax * 5) + } } "compute totalWrittenBytes" in { - val out = new ByteArrayOutputStream - val packerTotalWrittenBytes = IOUtil.withResource(msgpack.newPacker(out)) { packer => + val out = new + ByteArrayOutputStream + val packerTotalWrittenBytes = IOUtil.withResource(MessagePack.newDefaultPacker(out)) { packer => packer.packByte(0) // 1 - .packBoolean(true) // 1 - .packShort(12) // 1 - .packInt(1024) // 3 - .packLong(Long.MaxValue) // 5 - .packString("foobar") // 7 - .flush() + .packBoolean(true) // 1 + .packShort(12) // 1 + .packInt(1024) // 3 + .packLong(Long.MaxValue) // 5 + .packString("foobar") // 7 + .flush() packer.getTotalWrittenBytes } out.toByteArray.length shouldBe packerTotalWrittenBytes } + + "support read-only buffer" taggedAs ("read-only") in { + val payload = Array[Byte](1) + val out = new + ByteArrayOutputStream() + val packer = MessagePack.newDefaultPacker(out) + .packBinaryHeader(1) + .writePayload(payload) + .close() + } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/MessageUnpackerTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/MessageUnpackerTest.scala index 1428c68a9..e8ed65be7 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/MessageUnpackerTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/MessageUnpackerTest.scala @@ -1,21 +1,37 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core import java.io._ -import scala.util.Random +import java.nio.ByteBuffer + import org.msgpack.core.buffer._ import org.msgpack.value.ValueType import xerial.core.io.IOUtil +import scala.util.Random + /** * Created on 2014/05/07. */ class MessageUnpackerTest extends MessagePackSpec { - val msgpack = MessagePack.DEFAULT - - def testData : Array[Byte] = { + def testData: Array[Byte] = { val out = new ByteArrayOutputStream() - val packer = msgpack.newPacker(out) + val packer = MessagePack.newDefaultPacker(out) packer .packArrayHeader(2) @@ -33,12 +49,11 @@ class MessageUnpackerTest extends MessagePackSpec { arr } - val intSeq = (for(i <- 0 until 100) yield Random.nextInt()).toArray[Int] - - def testData2 : Array[Byte] = { + val intSeq = (for (i <- 0 until 100) yield Random.nextInt()).toArray[Int] + def testData2: Array[Byte] = { val out = new ByteArrayOutputStream() - val packer = msgpack.newPacker(out); + val packer = MessagePack.newDefaultPacker(out); packer .packBoolean(true) @@ -52,7 +67,7 @@ class MessageUnpackerTest extends MessagePackSpec { arr } - def write(packer:MessagePacker, r:Random) { + def write(packer: MessagePacker, r: Random) { val tpeIndex = Iterator.continually(r.nextInt(MessageFormat.values().length)).find(_ != MessageFormat.NEVER_USED.ordinal()).get val tpe = MessageFormat.values()(tpeIndex) tpe.getValueType match { @@ -85,7 +100,7 @@ class MessageUnpackerTest extends MessagePackSpec { trace(s"array len: $len") packer.packArrayHeader(len) var i = 0 - while(i < len) { + while (i < len) { write(packer, r) i += 1 } @@ -94,7 +109,7 @@ class MessageUnpackerTest extends MessagePackSpec { packer.packMapHeader(len) trace(s"map len: ${len}") var i = 0 - while(i < len * 2) { + while (i < len * 2) { write(packer, r) i += 1 } @@ -105,10 +120,10 @@ class MessageUnpackerTest extends MessagePackSpec { } } - def testData3(N:Int) : Array[Byte] = { + def testData3(N: Int): Array[Byte] = { val out = new ByteArrayOutputStream() - val packer = msgpack.newPacker(out) + val packer = MessagePack.newDefaultPacker(out) val r = new Random(0) @@ -122,7 +137,7 @@ class MessageUnpackerTest extends MessagePackSpec { } - def readValue(unpacker:MessageUnpacker) { + def readValue(unpacker: MessageUnpacker) { val f = unpacker.getNextFormat() f.getValueType match { case ValueType.ARRAY => @@ -152,20 +167,20 @@ class MessageUnpackerTest extends MessagePackSpec { f } - def checkFile(u:MessageUnpacker) = { + def checkFile(u: MessageUnpacker) = { u.unpackInt shouldBe 99 u.hasNext shouldBe false } "MessageUnpacker" should { - "parse message packed data" taggedAs("unpack") in { + "parse message packed data" taggedAs ("unpack") in { val arr = testData - val unpacker = msgpack.newUnpacker(arr) + val unpacker = MessagePack.newDefaultUnpacker(arr) var count = 0 - while(unpacker.hasNext) { + while (unpacker.hasNext) { count += 1 readValue(unpacker) } @@ -175,9 +190,9 @@ class MessageUnpackerTest extends MessagePackSpec { "skip reading values" in { - val unpacker = msgpack.newUnpacker(testData) + val unpacker = MessagePack.newDefaultUnpacker(testData) var skipCount = 0 - while(unpacker.hasNext) { + while (unpacker.hasNext) { unpacker.skipValue() skipCount += 1 } @@ -186,15 +201,15 @@ class MessageUnpackerTest extends MessagePackSpec { unpacker.getTotalReadBytes shouldBe testData.length } - "compare skip performance" taggedAs("skip") in { + "compare skip performance" taggedAs ("skip") in { val N = 10000 val data = testData3(N) time("skip performance", repeat = 100) { block("switch") { - val unpacker = msgpack.newUnpacker(data) + val unpacker = MessagePack.newDefaultUnpacker(data) var skipCount = 0 - while(unpacker.hasNext) { + while (unpacker.hasNext) { unpacker.skipValue() skipCount += 1 } @@ -210,8 +225,8 @@ class MessageUnpackerTest extends MessagePackSpec { val ib = Seq.newBuilder[Int] - val unpacker = msgpack.newUnpacker(testData2) - while(unpacker.hasNext) { + val unpacker = MessagePack.newDefaultUnpacker(testData2) + while (unpacker.hasNext) { val f = unpacker.getNextFormat f.getValueType match { case ValueType.INTEGER => @@ -231,7 +246,7 @@ class MessageUnpackerTest extends MessagePackSpec { } - class SplitMessageBufferInput(array:Array[Array[Byte]]) extends MessageBufferInput { + class SplitMessageBufferInput(array: Array[Array[Byte]]) extends MessageBufferInput { var cursor = 0 override def next(): MessageBuffer = { if (cursor < array.length) { @@ -239,19 +254,20 @@ class MessageUnpackerTest extends MessagePackSpec { cursor += 1 MessageBuffer.wrap(a) } - else + else { null + } } override def close(): Unit = {} } - "read data at the buffer boundary" taggedAs("boundary") in { + "read data at the buffer boundary" taggedAs ("boundary") in { trait SplitTest { - val data : Array[Byte] + val data: Array[Byte] def run { - val unpacker = msgpack.newUnpacker(data) + val unpacker = MessagePack.newDefaultUnpacker(data) val numElems = { var c = 0 while (unpacker.hasNext) { @@ -265,7 +281,7 @@ class MessageUnpackerTest extends MessagePackSpec { debug(s"split at $splitPoint") val (h, t) = data.splitAt(splitPoint) val bin = new SplitMessageBufferInput(Array(h, t)) - val unpacker = new MessageUnpacker(bin) + val unpacker = MessagePack.newDefaultUnpacker(bin) var count = 0 while (unpacker.hasNext) { count += 1 @@ -278,12 +294,12 @@ class MessageUnpackerTest extends MessagePackSpec { } } - new SplitTest { val data = testData }.run - new SplitTest { val data = testData3(30) }.run + new SplitTest {val data = testData}.run + new SplitTest {val data = testData3(30)}.run } - "be faster then msgpack-v6 skip" taggedAs("cmp-skip") in { + "be faster then msgpack-v6 skip" taggedAs ("cmp-skip") in { val data = testData3(10000) val N = 100 @@ -308,7 +324,7 @@ class MessageUnpackerTest extends MessagePackSpec { } block("v7") { - val unpacker = msgpack.newUnpacker(data) + val unpacker = MessagePack.newDefaultUnpacker(data) var count = 0 try { while (unpacker.hasNext) { @@ -324,22 +340,22 @@ class MessageUnpackerTest extends MessagePackSpec { t("v7").averageWithoutMinMax should be <= t("v6").averageWithoutMinMax } - import org.msgpack.`type`.{ValueType=>ValueTypeV6} + import org.msgpack.`type`.{ValueType => ValueTypeV6} - "be faster than msgpack-v6 read value" taggedAs("cmp-unpack") in { + "be faster than msgpack-v6 read value" taggedAs ("cmp-unpack") in { - def readValueV6(unpacker:org.msgpack.unpacker.MessagePackUnpacker) { + def readValueV6(unpacker: org.msgpack.unpacker.MessagePackUnpacker) { val vt = unpacker.getNextType() vt match { case ValueTypeV6.ARRAY => val len = unpacker.readArrayBegin() var i = 0 - while(i < len) { readValueV6(unpacker); i += 1 } + while (i < len) {readValueV6(unpacker); i += 1} unpacker.readArrayEnd() case ValueTypeV6.MAP => val len = unpacker.readMapBegin() var i = 0 - while(i < len) { readValueV6(unpacker); readValueV6(unpacker); i += 1 } + while (i < len) {readValueV6(unpacker); readValueV6(unpacker); i += 1} unpacker.readMapEnd() case ValueTypeV6.NIL => unpacker.readNil() @@ -358,18 +374,18 @@ class MessageUnpackerTest extends MessagePackSpec { val buf = new Array[Byte](8192) - def readValue(unpacker:MessageUnpacker) { + def readValue(unpacker: MessageUnpacker) { val f = unpacker.getNextFormat val vt = f.getValueType vt match { case ValueType.ARRAY => val len = unpacker.unpackArrayHeader() var i = 0 - while(i < len) { readValue(unpacker); i += 1 } + while (i < len) {readValue(unpacker); i += 1} case ValueType.MAP => val len = unpacker.unpackMapHeader() var i = 0 - while(i < len) { readValue(unpacker); readValue(unpacker); i += 1 } + while (i < len) {readValue(unpacker); readValue(unpacker); i += 1} case ValueType.NIL => unpacker.unpackNil() case ValueType.INTEGER => @@ -391,26 +407,26 @@ class MessageUnpackerTest extends MessagePackSpec { val data = testData3(10000) val N = 100 - val t = time("unpack performance", repeat=N) { + val t = time("unpack performance", repeat = N) { block("v6") { val v6 = new org.msgpack.MessagePack() val unpacker = new org.msgpack.unpacker.MessagePackUnpacker(v6, new ByteArrayInputStream(data)) var count = 0 try { - while(true) { + while (true) { readValueV6(unpacker) count += 1 } } catch { - case e:EOFException => + case e: EOFException => } finally unpacker.close() } block("v7") { - val unpacker = msgpack.newUnpacker(data) + val unpacker = MessagePack.newDefaultUnpacker(data) var count = 0 try { while (unpacker.hasNext) { @@ -428,10 +444,10 @@ class MessageUnpackerTest extends MessagePackSpec { } - "be faster for reading binary than v6" taggedAs("cmp-binary") in { + "be faster for reading binary than v6" taggedAs ("cmp-binary") in { val bos = new ByteArrayOutputStream() - val packer = msgpack.newPacker(bos) + val packer = MessagePack.newDefaultPacker(bos) val L = 10000 val R = 100 (0 until R).foreach { i => @@ -441,12 +457,12 @@ class MessageUnpackerTest extends MessagePackSpec { packer.close() val b = bos.toByteArray - time("unpackBinary", repeat=100) { + time("unpackBinary", repeat = 100) { block("v6") { val v6 = new org.msgpack.MessagePack() val unpacker = new org.msgpack.unpacker.MessagePackUnpacker(v6, new ByteArrayInputStream(b)) var i = 0 - while(i < R) { + while (i < R) { val out = unpacker.readByteArray() i += 1 } @@ -454,9 +470,9 @@ class MessageUnpackerTest extends MessagePackSpec { } block("v7") { - val unpacker = msgpack.newUnpacker(b) + val unpacker = MessagePack.newDefaultUnpacker(b) var i = 0 - while(i < R) { + while (i < R) { val len = unpacker.unpackBinaryHeader() val out = new Array[Byte](len) unpacker.readPayload(out, 0, len) @@ -466,9 +482,9 @@ class MessageUnpackerTest extends MessagePackSpec { } block("v7-ref") { - val unpacker = msgpack.newUnpacker(b) + val unpacker = MessagePack.newDefaultUnpacker(b) var i = 0 - while(i < R) { + while (i < R) { val len = unpacker.unpackBinaryHeader() val out = unpacker.readPayloadAsReference(len) i += 1 @@ -478,21 +494,21 @@ class MessageUnpackerTest extends MessagePackSpec { } } - "read payload as a reference" taggedAs("ref") in { + "read payload as a reference" taggedAs ("ref") in { val dataSizes = Seq(0, 1, 5, 8, 16, 32, 128, 256, 1024, 2000, 10000, 100000) - for(s <- dataSizes) { + for (s <- dataSizes) { When(f"data size is $s%,d") val data = new Array[Byte](s) Random.nextBytes(data) val b = new ByteArrayOutputStream() - val packer = msgpack.newPacker(b) + val packer = MessagePack.newDefaultPacker(b) packer.packBinaryHeader(s) packer.writePayload(data) packer.close() - val unpacker = msgpack.newUnpacker(b.toByteArray) + val unpacker = MessagePack.newDefaultUnpacker(b.toByteArray) val len = unpacker.unpackBinaryHeader() len shouldBe s val ref = unpacker.readPayloadAsReference(len) @@ -507,14 +523,14 @@ class MessageUnpackerTest extends MessagePackSpec { } - "reset the internal states" taggedAs("reset") in { + "reset the internal states" taggedAs ("reset") in { val data = intSeq val b = createMessagePackData(packer => data foreach packer.packInt) - val unpacker = msgpack.newUnpacker(b) + val unpacker = MessagePack.newDefaultUnpacker(b) val unpacked = Array.newBuilder[Int] - while(unpacker.hasNext) { + while (unpacker.hasNext) { unpacked += unpacker.unpackInt() } unpacker.close @@ -525,7 +541,7 @@ class MessageUnpackerTest extends MessagePackSpec { val bi = new ArrayBufferInput(b2) unpacker.reset(bi) val unpacked2 = Array.newBuilder[Int] - while(unpacker.hasNext) { + while (unpacker.hasNext) { unpacked2 += unpacker.unpackInt() } unpacker.close @@ -535,7 +551,7 @@ class MessageUnpackerTest extends MessagePackSpec { bi.reset(b2) unpacker.reset(bi) val unpacked3 = Array.newBuilder[Int] - while(unpacker.hasNext) { + while (unpacker.hasNext) { unpacked3 += unpacker.unpackInt() } unpacker.close @@ -543,10 +559,10 @@ class MessageUnpackerTest extends MessagePackSpec { } - "improve the performance via reset method" taggedAs("reset-arr") in { + "improve the performance via reset method" taggedAs ("reset-arr") in { val out = new ByteArrayOutputStream - val packer = msgpack.newPacker(out) + val packer = MessagePack.newDefaultPacker(out) packer.packInt(0) packer.flush val arr = out.toByteArray @@ -555,7 +571,7 @@ class MessageUnpackerTest extends MessagePackSpec { val N = 1000 val t = time("unpacker", repeat = 10) { block("no-buffer-reset") { - IOUtil.withResource(msgpack.newUnpacker(arr)) { unpacker => + IOUtil.withResource(MessagePack.newDefaultUnpacker(arr)) { unpacker => for (i <- 0 until N) { val buf = new ArrayBufferInput(arr) unpacker.reset(buf) @@ -566,7 +582,7 @@ class MessageUnpackerTest extends MessagePackSpec { } block("reuse-array-input") { - IOUtil.withResource(msgpack.newUnpacker(arr)) { unpacker => + IOUtil.withResource(MessagePack.newDefaultUnpacker(arr)) { unpacker => val buf = new ArrayBufferInput(arr) for (i <- 0 until N) { buf.reset(arr) @@ -578,7 +594,7 @@ class MessageUnpackerTest extends MessagePackSpec { } block("reuse-message-buffer") { - IOUtil.withResource(msgpack.newUnpacker(arr)) { unpacker => + IOUtil.withResource(MessagePack.newDefaultUnpacker(arr)) { unpacker => val buf = new ArrayBufferInput(arr) for (i <- 0 until N) { buf.reset(mb) @@ -590,8 +606,8 @@ class MessageUnpackerTest extends MessagePackSpec { } } - t("reuse-message-buffer").averageWithoutMinMax should be <= t("no-buffer-reset").averageWithoutMinMax - // This performance comparition is too close, so we disabled it + // This performance comparison is too close, so we disabled it + // t("reuse-message-buffer").averageWithoutMinMax should be <= t("no-buffer-reset").averageWithoutMinMax // t("reuse-array-input").averageWithoutMinMax should be <= t("no-buffer-reset").averageWithoutMinMax } @@ -618,5 +634,58 @@ class MessageUnpackerTest extends MessagePackSpec { checkFile(u) u.close } + + "unpack large string data" taggedAs ("large-string") in { + def createLargeData(stringLength: Int): Array[Byte] = { + val out = new ByteArrayOutputStream() + val packer = MessagePack.newDefaultPacker(out) + + packer + .packArrayHeader(2) + .packString("l" * stringLength) + .packInt(1) + + packer.close() + + out.toByteArray + } + + Seq(8191, 8192, 8193, 16383, 16384, 16385).foreach { n => + val arr = createLargeData(n) + + val unpacker = MessagePack.newDefaultUnpacker(arr) + + unpacker.unpackArrayHeader shouldBe 2 + unpacker.unpackString.length shouldBe n + unpacker.unpackInt shouldBe 1 + + unpacker.getTotalReadBytes shouldBe arr.length + } + } + + "unpack string crossing end of buffer" in { + def check(expected: String, strLen: Int) = { + val bytes = new Array[Byte](strLen) + val out = new ByteArrayOutputStream + + val packer = MessagePack.newDefaultPacker(out) + packer.packBinaryHeader(bytes.length) + packer.writePayload(bytes) + packer.packString(expected) + packer.close + + val unpacker = MessagePack.newDefaultUnpacker(new InputStreamBufferInput(new ByteArrayInputStream(out.toByteArray))) + val len = unpacker.unpackBinaryHeader + unpacker.readPayload(len) + val got = unpacker.unpackString + unpacker.close + + got shouldBe expected + } + + Seq("\u3042", "a\u3042", "\u3042a", "\u3042\u3044\u3046\u3048\u304A\u304B\u304D\u304F\u3051\u3053\u3055\u3057\u3059\u305B\u305D").foreach { s => + Seq(8185, 8186, 8187, 8188, 16377, 16378, 16379, 16380).foreach { n => check(s, n)} + } + } } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/buffer/ByteStringTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/buffer/ByteStringTest.scala new file mode 100644 index 000000000..2c080b59a --- /dev/null +++ b/msgpack-core/src/test/scala/org/msgpack/core/buffer/ByteStringTest.scala @@ -0,0 +1,46 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.core.buffer + +import akka.util.ByteString +import org.msgpack.core.{MessagePack, MessagePackSpec, MessageUnpacker} + +class ByteStringTest + extends MessagePackSpec { + + val unpackedString = "foo" + val byteString = ByteString(createMessagePackData(_.packString(unpackedString))) + + def unpackString(messageBuffer: MessageBuffer) = { + val input = new + MessageBufferInput { + + private var isRead = false + + override def next(): MessageBuffer = + if (isRead) { + null + } + else { + isRead = true + messageBuffer + } + override def close(): Unit = {} + } + + MessagePack.newDefaultUnpacker(input).unpackString() + } +} diff --git a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferInputTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferInputTest.scala index 379badf7e..1638806ee 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferInputTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferInputTest.scala @@ -1,23 +1,38 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer -import org.msgpack.core.{MessageUnpacker, MessagePack, MessagePackSpec} import java.io._ -import xerial.core.io.IOUtil -import scala.util.Random -import java.util.zip.{GZIPOutputStream, GZIPInputStream} import java.nio.ByteBuffer -import org.msgpack.unpacker.MessagePackUnpacker +import java.util.zip.{GZIPInputStream, GZIPOutputStream} + +import org.msgpack.core.{MessagePack, MessagePackSpec, MessageUnpacker} +import xerial.core.io.IOUtil._ + +import scala.util.Random -/** - * Created on 5/30/14. - */ -class MessageBufferInputTest extends MessagePackSpec { +class MessageBufferInputTest + extends MessagePackSpec { val targetInputSize = Seq(0, 10, 500, 1000, 2000, 4000, 8000, 10000, 30000, 50000, 100000) - def testData(size:Int) = { + def testData(size: Int) = { //debug(s"test data size: ${size}") - val b = new Array[Byte](size) + val b = new + Array[Byte](size) Random.nextBytes(b) b } @@ -25,17 +40,19 @@ class MessageBufferInputTest extends MessagePackSpec { def testDataSet = { targetInputSize.map(testData) } - - def runTest(factory:Array[Byte] => MessageBufferInput) { - for(b <- testDataSet) { + + def runTest(factory: Array[Byte] => MessageBufferInput) { + for (b <- testDataSet) { checkInputData(b, factory(b)) } } - implicit class InputData(b:Array[Byte]) { + implicit class InputData(b: Array[Byte]) { def compress = { - val compressed = new ByteArrayOutputStream() - val out = new GZIPOutputStream(compressed) + val compressed = new + ByteArrayOutputStream() + val out = new + GZIPOutputStream(compressed) out.write(b) out.close() compressed.toByteArray @@ -45,59 +62,61 @@ class MessageBufferInputTest extends MessagePackSpec { ByteBuffer.wrap(b) } - def saveToTmpFile : File = { - val tmp = File.createTempFile("testbuf", ".dat", new File("target")) + def saveToTmpFile: File = { + val tmp = File + .createTempFile("testbuf", + ".dat", + new + File("target")) tmp.getParentFile.mkdirs() tmp.deleteOnExit() - IOUtil.withResource(new FileOutputStream(tmp)) { out => + withResource(new + FileOutputStream(tmp)) { out => out.write(b) } tmp } } - - - def checkInputData(inputData:Array[Byte], in:MessageBufferInput) { + def checkInputData(inputData: Array[Byte], in: MessageBufferInput) { When(s"input data size = ${inputData.length}") var cursor = 0 - for(m <- Iterator.continually(in.next).takeWhile(_ != null)) { + for (m <- Iterator.continually(in.next).takeWhile(_ != null)) { m.toByteArray() shouldBe inputData.slice(cursor, cursor + m.size()) cursor += m.size() } cursor shouldBe inputData.length - } - "MessageBufferInput" should { "support byte arrays" in { - runTest(new ArrayBufferInput(_)) - } - - "support ByteBuffers" in { - runTest(b => new ByteBufferInput(b.toByteBuffer)) + runTest(new + ArrayBufferInput(_)) } - "support InputStreams" taggedAs("is") in { - runTest(b => - new InputStreamBufferInput( - new GZIPInputStream(new ByteArrayInputStream(b.compress))) + "support InputStreams" taggedAs ("is") in { + runTest(b => + new + InputStreamBufferInput( + new + GZIPInputStream(new + ByteArrayInputStream(b.compress))) ) } - "support file input channel" taggedAs("fc") in { + "support file input channel" taggedAs ("fc") in { runTest { b => val tmp = b.saveToTmpFile try { - InputStreamBufferInput.newBufferInput(new FileInputStream(tmp)) + InputStreamBufferInput + .newBufferInput(new + FileInputStream(tmp)) } finally { tmp.delete() } } } - } def createTempFile = { @@ -109,8 +128,9 @@ class MessageBufferInputTest extends MessagePackSpec { def createTempFileWithInputStream = { val f = createTempFile val out = new FileOutputStream(f) - new MessagePack().newPacker(out).packInt(42).close - val in = new FileInputStream(f) + MessagePack.newDefaultPacker(out).packInt(42).close + val in = new + FileInputStream(f) (f, in) } @@ -120,27 +140,56 @@ class MessageBufferInputTest extends MessagePackSpec { (f, ch) } - def readInt(buf:MessageBufferInput) : Int = { - val unpacker = new MessageUnpacker(buf) + def readInt(buf: MessageBufferInput): Int = { + val unpacker = MessagePack.newDefaultUnpacker(buf) unpacker.unpackInt } "InputStreamBufferInput" should { "reset buffer" in { val (f0, in0) = createTempFileWithInputStream - val buf = new InputStreamBufferInput(in0) + val buf = new + InputStreamBufferInput(in0) readInt(buf) shouldBe 42 val (f1, in1) = createTempFileWithInputStream buf.reset(in1) readInt(buf) shouldBe 42 } + + "be non-blocking" taggedAs ("non-blocking") in { + + withResource(new + PipedOutputStream()) { pipedOutputStream => + withResource(new + PipedInputStream()) { pipedInputStream => + pipedInputStream.connect(pipedOutputStream) + + val packer = MessagePack.newDefaultPacker(pipedOutputStream) + .packArrayHeader(2) + .packLong(42) + .packString("hello world") + + packer.flush + + val unpacker = MessagePack.newDefaultUnpacker(pipedInputStream) + unpacker.hasNext() shouldBe true + unpacker.unpackArrayHeader() shouldBe 2 + unpacker.unpackLong() shouldBe 42L + unpacker.unpackString() shouldBe "hello world" + + packer.close + unpacker.close + } + } + } } "ChannelBufferInput" should { "reset buffer" in { val (f0, in0) = createTempFileWithChannel - val buf = new ChannelBufferInput(in0) + val buf = new + ChannelBufferInput(in0) readInt(buf) shouldBe 42 val (f1, in1) = createTempFileWithChannel @@ -148,5 +197,4 @@ class MessageBufferInputTest extends MessagePackSpec { readInt(buf) shouldBe 42 } } - } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferOutputTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferOutputTest.scala index 7d8940dc6..1869f2aad 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferOutputTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferOutputTest.scala @@ -1,9 +1,26 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer -import org.msgpack.core.MessagePackSpec import java.io._ -class MessageBufferOutputTest extends MessagePackSpec { +import org.msgpack.core.MessagePackSpec + +class MessageBufferOutputTest + extends MessagePackSpec { def createTempFile = { val f = File.createTempFile("msgpackTest", "msgpack") @@ -13,7 +30,8 @@ class MessageBufferOutputTest extends MessagePackSpec { def createTempFileWithOutputStream = { val f = createTempFile - val out = new FileOutputStream(f) + val out = new + FileOutputStream(f) (f, out) } @@ -23,17 +41,18 @@ class MessageBufferOutputTest extends MessagePackSpec { (f, ch) } - def writeIntToBuf(buf:MessageBufferOutput) = { + def writeIntToBuf(buf: MessageBufferOutput) = { val mb0 = buf.next(8) mb0.putInt(0, 42) - buf.flush(mb0) + buf.writeBuffer(4) buf.close } "OutputStreamBufferOutput" should { "reset buffer" in { val (f0, out0) = createTempFileWithOutputStream - val buf = new OutputStreamBufferOutput(out0) + val buf = new + OutputStreamBufferOutput(out0) writeIntToBuf(buf) f0.length.toInt should be > 0 @@ -47,7 +66,8 @@ class MessageBufferOutputTest extends MessagePackSpec { "ChannelBufferOutput" should { "reset buffer" in { val (f0, ch0) = createTempFileWithChannel - val buf = new ChannelBufferOutput(ch0) + val buf = new + ChannelBufferOutput(ch0) writeIntToBuf(buf) f0.length.toInt should be > 0 @@ -57,5 +77,4 @@ class MessageBufferOutputTest extends MessagePackSpec { f1.length.toInt should be > 0 } } - } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferTest.scala index 505dbd312..75ef00a11 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/buffer/MessageBufferTest.scala @@ -1,20 +1,42 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.buffer import java.nio.ByteBuffer -import scala.util.Random + import org.msgpack.core.MessagePackSpec +import scala.util.Random + /** * Created on 2014/05/01. */ -class MessageBufferTest extends MessagePackSpec { +class MessageBufferTest + extends MessagePackSpec { "MessageBuffer" should { - "wrap ByteBuffer considering position and remaining values" taggedAs("wrap-bb") in { - val d = Array[Byte](10,11,12,13,14,15,16,17,18,19) - val subset = ByteBuffer.wrap(d, 2, 2) - val mb = MessageBuffer.wrap(subset) + "check buffer type" in { + val b = MessageBuffer.allocate(0) + info(s"MessageBuffer type: ${b.getClass.getName}") + } + + "wrap ByteBuffer considering position and remaining values" taggedAs ("wrap-bb") in { + val d = Array[Byte](10, 11, 12, 13, 14, 15, 16, 17, 18, 19) + val mb = MessageBuffer.wrap(d, 2, 2) mb.getByte(0) shouldBe 12 mb.size() shouldBe 2 } @@ -24,25 +46,26 @@ class MessageBufferTest extends MessagePackSpec { val N = 1000000 val M = 64 * 1024 * 1024 - val ub = MessageBuffer.newBuffer(M) - val ud = MessageBuffer.newDirectBuffer(M) + val ub = MessageBuffer.allocate(M) val hb = ByteBuffer.allocate(M) val db = ByteBuffer.allocateDirect(M) def bench(f: Int => Unit) { var i = 0 - while(i < N) { + while (i < N) { f((i * 4) % M) i += 1 } } - val r = new Random(0) - val rs = new Array[Int](N) + val r = new + Random(0) + val rs = new + Array[Int](N) (0 until N).map(i => rs(i) = r.nextInt(N)) def randomBench(f: Int => Unit) { var i = 0 - while(i < N) { + while (i < N) { f((rs(i) * 4) % M) i += 1 } @@ -53,23 +76,15 @@ class MessageBufferTest extends MessagePackSpec { time("sequential getInt", repeat = rep) { block("unsafe array") { var i = 0 - while(i < N) { + while (i < N) { ub.getInt((i * 4) % M) i += 1 } } - block("unsafe direct") { - var i = 0 - while(i < N) { - ud.getInt((i * 4) % M) - i += 1 - } - } - block("allocate") { var i = 0 - while(i < N) { + while (i < N) { hb.getInt((i * 4) % M) i += 1 } @@ -77,7 +92,7 @@ class MessageBufferTest extends MessagePackSpec { block("allocateDirect") { var i = 0 - while(i < N) { + while (i < N) { db.getInt((i * 4) % M) i += 1 } @@ -87,23 +102,15 @@ class MessageBufferTest extends MessagePackSpec { time("random getInt", repeat = rep) { block("unsafe array") { var i = 0 - while(i < N) { + while (i < N) { ub.getInt((rs(i) * 4) % M) i += 1 } } - block("unsafe direct") { - var i = 0 - while(i < N) { - ud.getInt((rs(i) * 4) % M) - i += 1 - } - } - block("allocate") { var i = 0 - while(i < N) { + while (i < N) { hb.getInt((rs(i) * 4) % M) i += 1 } @@ -111,29 +118,81 @@ class MessageBufferTest extends MessagePackSpec { block("allocateDirect") { var i = 0 - while(i < N) { + while (i < N) { db.getInt((rs(i) * 4) % M) i += 1 } } } - } "convert to ByteBuffer" in { for (t <- Seq( - MessageBuffer.newBuffer(10), - MessageBuffer.newDirectBuffer(10), - MessageBuffer.newOffHeapBuffer(10)) + MessageBuffer.allocate(10)) ) { - val bb = t.toByteBuffer + val bb = t.sliceAsByteBuffer bb.position shouldBe 0 bb.limit shouldBe 10 bb.capacity shouldBe 10 } } - } + "put ByteBuffer on itself" in { + for (t <- Seq( + MessageBuffer.allocate(10)) + ) { + val b = Array[Byte](0x02, 0x03) + val srcArray = ByteBuffer.wrap(b) + val srcHeap = ByteBuffer.allocate(b.length) + srcHeap.put(b).flip + val srcOffHeap = ByteBuffer.allocateDirect(b.length) + srcOffHeap.put(b).flip + + for (src <- Seq(srcArray, srcHeap, srcOffHeap)) { + // Write header bytes + val header = Array[Byte](0x00, 0x01) + t.putBytes(0, header, 0, header.length) + // Write src after the header + t.putByteBuffer(header.length, src, header.length) + + t.getByte(0) shouldBe 0x00 + t.getByte(1) shouldBe 0x01 + t.getByte(2) shouldBe 0x02 + t.getByte(3) shouldBe 0x03 + } + } + } + + "copy sliced buffer" in { + def prepareBytes : Array[Byte] = { + Array[Byte](0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07) + } + + def checkSliceAndCopyTo(srcBuffer: MessageBuffer, dstBuffer: MessageBuffer) = { + val sliced = srcBuffer.slice(2, 5) + + sliced.size() shouldBe 5 + sliced.getByte(0) shouldBe 0x02 + sliced.getByte(1) shouldBe 0x03 + sliced.getByte(2) shouldBe 0x04 + sliced.getByte(3) shouldBe 0x05 + sliced.getByte(4) shouldBe 0x06 + + sliced.copyTo(3, dstBuffer, 1, 2) // copy 0x05 and 0x06 to dstBuffer[1] and [2] + + dstBuffer.getByte(0) shouldBe 0x00 + dstBuffer.getByte(1) shouldBe 0x05 // copied by sliced.getByte(3) + dstBuffer.getByte(2) shouldBe 0x06 // copied by sliced.getByte(4) + dstBuffer.getByte(3) shouldBe 0x03 + dstBuffer.getByte(4) shouldBe 0x04 + dstBuffer.getByte(5) shouldBe 0x05 + dstBuffer.getByte(6) shouldBe 0x06 + dstBuffer.getByte(7) shouldBe 0x07 + } + + checkSliceAndCopyTo(MessageBuffer.wrap(prepareBytes), MessageBuffer.wrap(prepareBytes)) + } + } } diff --git a/msgpack-core/src/test/scala/org/msgpack/core/example/MessagePackExampleTest.scala b/msgpack-core/src/test/scala/org/msgpack/core/example/MessagePackExampleTest.scala index 21c1d8334..bacf39ed4 100644 --- a/msgpack-core/src/test/scala/org/msgpack/core/example/MessagePackExampleTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/core/example/MessagePackExampleTest.scala @@ -1,3 +1,18 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.core.example import org.msgpack.core.MessagePackSpec @@ -5,7 +20,8 @@ import org.msgpack.core.MessagePackSpec /** * */ -class MessagePackExampleTest extends MessagePackSpec { +class MessagePackExampleTest + extends MessagePackSpec { "example" should { @@ -24,6 +40,5 @@ class MessagePackExampleTest extends MessagePackSpec { "have configuration example" in { MessagePackExample.configuration(); } - } } diff --git a/msgpack-core/src/test/scala/org/msgpack/value/CursorTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/CursorTest.scala deleted file mode 100644 index ee46ff7bd..000000000 --- a/msgpack-core/src/test/scala/org/msgpack/value/CursorTest.scala +++ /dev/null @@ -1,199 +0,0 @@ -// -// MessagePack for Java -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -package org.msgpack.value - -import java.io.ByteArrayInputStream - -import org.msgpack.core.{MessagePack, MessageUnpacker, MessagePackSpec} -import ValueFactory._ -import scala.util.Random -import org.msgpack.value.holder.{ValueHolder, IntegerHolder} - -/** - * Created on 6/13/14. - */ -class CursorTest extends MessagePackSpec { - - val msgpack = MessagePack.DEFAULT - - def sampleData = createMessagePackData { packer => - packer.packValue( - ValueFactory.newArray( - newInt(10), - newBinary("message pack".getBytes(MessagePack.UTF8)), - newString("hello") - ) - ) - } - - def intSeq(n:Int) = createMessagePackData { packer => - (0 until n).foreach { i => - packer.packInt(Random.nextInt(65536)) - } - } - def binSeq(n:Int) = createMessagePackData { packer => - (0 until n).foreach { i => - val len = Random.nextInt(256) - val b = new Array[Byte](len) - Random.nextBytes(b) - packer.packBinaryHeader(b.length).writePayload(b) - } - } - - - "Cursor" should { - - "have array cursor" taggedAs("array") in { - - val cursor = msgpack.newUnpacker(sampleData).getCursor - // Traverse as references - val arrCursor = cursor.nextRef().getArrayCursor - arrCursor.size() shouldBe 3 - - import scala.collection.JavaConversions._ - for(v <- arrCursor) { - info(s"[${v.getValueType}]\t${v}") - } - } - - "have map cursor" taggedAs("map") in { - val packedData = createMessagePackData { packer => - packer packMapHeader(1) packString("f") packString("x") - } - - val cursor = msgpack.newUnpacker(packedData).getCursor - val mapCursor = cursor.nextRef().getMapCursor - mapCursor.size() shouldBe 1 - - val mapValue = mapCursor.toValue - val data = mapValue.toKeyValueSeq - - data should have length 2 - - data(0).asString().toString shouldBe "f" - data(1).asString().toString shouldBe "x" - } - - "traverse ValueRef faster than traversing Value" taggedAs("ref") in { - val N = 10000 - val data = binSeq(N) - - time("traversal", repeat=100) { - block("value") { - val cursor = msgpack.newUnpacker(data).getCursor - while(cursor.hasNext) { - cursor.next() - } - cursor.close() - } - block("value-ref") { - val cursor = msgpack.newUnpacker(data).getCursor - while(cursor.hasNext) { - cursor.nextRef() - } - cursor.close() - } - } - - } - - "have negligible overhead" taggedAs("perf") in { - val N = 10000 - val data = intSeq(N) - time("scan int-seq", repeat=1000) { - block("unpacker") { - val unpacker = msgpack.newUnpacker(data) - val intHolder = new IntegerHolder() - var count = 0 - while(unpacker.hasNext) { - val vt = unpacker.getNextFormat.getValueType - if(vt.isIntegerType) { - unpacker.unpackInteger(intHolder); - count += 1 - } - else { - throw new IllegalStateException(s"invalid format: ${vt}") - } - } - unpacker.close() - count shouldBe N - } - block("cursor") { - var count = 0 - val cursor = msgpack.newUnpacker(data).getCursor - while(cursor.hasNext) { - val ref = cursor.nextRef() - val v = ref.asInteger().toInt - count += 1 - } - cursor.close() - count shouldBe N - } - } - - } - - "create immutable map" taggedAs("im-map") in { - - val m = createMessagePackData { packer => - packer.packMapHeader(3) - - // A -> [1, "leo"] - packer.packString("A") - packer.packArrayHeader(2) - packer.packInt(1) - packer.packString("leo") - - // B -> 10 - packer.packString("B") - packer.packInt(10) - - // C -> {a -> 1.0f, b -> 5, c -> {cc->1}} - packer.packString("C") - packer.packMapHeader(3) - packer.packString("a") - packer.packFloat(1.0f) - packer.packString("b") - packer.packInt(5) - - packer.packString("c") - packer.packMapHeader(1) - packer.packString("cc") - packer.packInt(1) - - } - - val unpacker = msgpack.newUnpacker(m) - val vh = new ValueHolder - unpacker.unpackValue(vh) - val mapValue = vh.get().asMapValue() - - val map = mapValue.toMap - map.size shouldBe 3 - - val arr = map.get(ValueFactory.newString("A")).asArrayValue() - arr.size shouldBe 2 - - val cmap = map.get(ValueFactory.newString("C")).asMapValue() - cmap.size shouldBe 3 - cmap.toMap.get(ValueFactory.newString("c")).asMapValue().size() shouldBe 1 - - info(mapValue) - } - - - } -} diff --git a/msgpack-core/src/test/scala/org/msgpack/value/RawStringValueImplTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/RawStringValueImplTest.scala index 1193a3310..e545d7d2a 100644 --- a/msgpack-core/src/test/scala/org/msgpack/value/RawStringValueImplTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/value/RawStringValueImplTest.scala @@ -1,13 +1,29 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value import org.msgpack.core.MessagePackSpec -class RawStringValueImplTest extends MessagePackSpec { +class RawStringValueImplTest + extends MessagePackSpec { "StringValue" should { "return the same hash code if they are equal" in { val str = "a" - val a1 = ValueFactory.newRawString(str.getBytes("UTF-8")) + val a1 = ValueFactory.newString(str.getBytes("UTF-8")) val a2 = ValueFactory.newString(str) a1.shouldEqual(a2) diff --git a/msgpack-core/src/test/scala/org/msgpack/value/RawValueImplTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/RawValueImplTest.scala deleted file mode 100644 index 6e56aa762..000000000 --- a/msgpack-core/src/test/scala/org/msgpack/value/RawValueImplTest.scala +++ /dev/null @@ -1,35 +0,0 @@ -package org.msgpack.value - -import org.msgpack.core.{MessagePack, MessagePackSpec} -import java.io.ByteArrayOutputStream - - -class RawValueImplTest extends MessagePackSpec { - - "RawValueImple" should { - "toString shouldn't return empty value" in { - val str = "aaa" - def newRawStr() = ValueFactory.newRawString(str.getBytes("UTF-8")) - - def pack(v: Value): Array[Byte] = { - val out = new ByteArrayOutputStream() - val packer = MessagePack.newDefaultPacker(out) - packer.packValue(v) - packer.close() - out.toByteArray - } - - { - val rawStr = newRawStr() - pack(rawStr) - rawStr.toString() shouldBe str - } - - { - val rawStr = newRawStr() - pack(rawStr) - rawStr.asString().toString shouldBe str - } - } - } -} diff --git a/msgpack-core/src/test/scala/org/msgpack/value/ValueFactoryTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/ValueFactoryTest.scala index cb79a575e..a8d996376 100644 --- a/msgpack-core/src/test/scala/org/msgpack/value/ValueFactoryTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/value/ValueFactoryTest.scala @@ -1,53 +1,69 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value -import org.scalatest.FunSuite import org.msgpack.core.MessagePackSpec /** - * Created on 6/13/14. + * */ -class ValueFactoryTest extends MessagePackSpec { +class ValueFactoryTest + extends MessagePackSpec { - def isValid(v:Value, - expected:ValueType, + def isValid(v: Value, + expected: ValueType, isNil: Boolean = false, isBoolean: Boolean = false, isInteger: Boolean = false, - isString : Boolean = false, + isString: Boolean = false, isFloat: Boolean = false, isBinary: Boolean = false, isArray: Boolean = false, isMap: Boolean = false, - isExtended : Boolean = false, - isRaw : Boolean = false, - isNumber : Boolean = false + isExtension: Boolean = false, + isRaw: Boolean = false, + isNumber: Boolean = false ) { - v.isNil shouldBe isNil - v.isBoolean shouldBe isBoolean - v.isInteger shouldBe isInteger - v.isFloat shouldBe isFloat - v.isString shouldBe isString - v.isBinary shouldBe isBinary - v.isArray shouldBe isArray - v.isMap shouldBe isMap - v.isExtended shouldBe isExtended - v.isRaw shouldBe isRaw - v.isNumber shouldBe isNumber + v.isNilValue shouldBe isNil + v.isBooleanValue shouldBe isBoolean + v.isIntegerValue shouldBe isInteger + v.isFloatValue shouldBe isFloat + v.isStringValue shouldBe isString + v.isBinaryValue shouldBe isBinary + v.isArrayValue shouldBe isArray + v.isMapValue shouldBe isMap + v.isExtensionValue shouldBe isExtension + v.isRawValue shouldBe isRaw + v.isNumberValue shouldBe isNumber } "ValueFactory" should { "create valid type values" in { - isValid(ValueFactory.nilValue(), expected=ValueType.NIL, isNil = true) - forAll{(v:Boolean) => isValid(ValueFactory.newBoolean(v), expected=ValueType.BOOLEAN, isBoolean = true)} - forAll{(v:Int) => isValid(ValueFactory.newInt(v), expected=ValueType.INTEGER, isInteger = true, isNumber = true)} - forAll{(v:Float) => isValid(ValueFactory.newFloat(v), expected=ValueType.FLOAT, isFloat = true, isNumber = true)} - forAll{(v:String) => isValid(ValueFactory.newString(v), expected=ValueType.STRING, isString = true, isRaw = true)} - forAll{(v:Array[Byte]) => isValid(ValueFactory.newBinary(v), expected=ValueType.BINARY, isBinary = true, isRaw = true)} - isValid(ValueFactory.emptyArray(), expected=ValueType.ARRAY, isArray = true) - isValid(ValueFactory.emptyMap(), expected=ValueType.MAP, isMap = true) - forAll{(v:Array[Byte]) => isValid(ValueFactory.newExtendedValue(0, v), expected=ValueType.EXTENDED, isExtended=true, isRaw=true)} + isValid(ValueFactory.newNil(), expected = ValueType.NIL, isNil = true) + forAll { (v: Boolean) => isValid(ValueFactory.newBoolean(v), expected = ValueType.BOOLEAN, isBoolean = true) } + forAll { (v: Int) => isValid(ValueFactory.newInteger(v), expected = ValueType.INTEGER, isInteger = true, isNumber = true) } + forAll { (v: Float) => isValid(ValueFactory.newFloat(v), expected = ValueType.FLOAT, isFloat = true, isNumber = true) } + forAll { (v: String) => isValid(ValueFactory.newString(v), expected = ValueType.STRING, isString = true, isRaw = true) } + forAll { (v: Array[Byte]) => isValid(ValueFactory.newBinary(v), expected = ValueType.BINARY, isBinary = true, isRaw = true) } + isValid(ValueFactory.emptyArray(), expected = ValueType.ARRAY, isArray = true) + isValid(ValueFactory.emptyMap(), expected = ValueType.MAP, isMap = true) + forAll { (v: Array[Byte]) => isValid(ValueFactory.newExtension(0, v), expected = ValueType + .EXTENSION, isExtension = true, isRaw = true) + } } - } } diff --git a/msgpack-core/src/test/scala/org/msgpack/value/ValueTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/ValueTest.scala new file mode 100644 index 000000000..6cb7af603 --- /dev/null +++ b/msgpack-core/src/test/scala/org/msgpack/value/ValueTest.scala @@ -0,0 +1,130 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.value + +import java.math.BigInteger +import org.msgpack.core._ + +import scala.util.parsing.json.JSON + +class ValueTest extends MessagePackSpec +{ + def checkSuccinctType(pack:MessagePacker => Unit, expectedAtMost:MessageFormat) { + val b = createMessagePackData(pack) + val v1 = MessagePack.newDefaultUnpacker(b).unpackValue() + val mf = v1.asIntegerValue().mostSuccinctMessageFormat() + mf.getValueType shouldBe ValueType.INTEGER + mf.ordinal() shouldBe <= (expectedAtMost.ordinal()) + + val v2 = new Variable + MessagePack.newDefaultUnpacker(b).unpackValue(v2) + val mf2 = v2.asIntegerValue().mostSuccinctMessageFormat() + mf2.getValueType shouldBe ValueType.INTEGER + mf2.ordinal() shouldBe <= (expectedAtMost.ordinal()) + } + + "Value" should { + "tell most succinct integer type" in { + forAll { (v: Byte) => checkSuccinctType(_.packByte(v), MessageFormat.INT8) } + forAll { (v: Short) => checkSuccinctType(_.packShort(v), MessageFormat.INT16) } + forAll { (v: Int) => checkSuccinctType(_.packInt(v), MessageFormat.INT32) } + forAll { (v: Long) => checkSuccinctType(_.packLong(v), MessageFormat.INT64) } + forAll { (v: Long) => checkSuccinctType(_.packBigInteger(BigInteger.valueOf(v)), MessageFormat.INT64) } + forAll { (v: Long) => + whenever(v > 0) { + // Create value between 2^63-1 < v <= 2^64-1 + checkSuccinctType(_.packBigInteger(BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(v))), MessageFormat.UINT64) + } + } + } + + "produce json strings" in { + + import ValueFactory._ + + newNil().toJson shouldBe "null" + newNil().toString shouldBe "null" + + newBoolean(true).toJson shouldBe "true" + newBoolean(false).toJson shouldBe "false" + newBoolean(true).toString shouldBe "true" + newBoolean(false).toString shouldBe "false" + + newInteger(3).toJson shouldBe "3" + newInteger(3).toString shouldBe "3" + newInteger(BigInteger.valueOf(1324134134134L)).toJson shouldBe "1324134134134" + newInteger(BigInteger.valueOf(1324134134134L)).toString shouldBe "1324134134134" + + newFloat(0.1).toJson shouldBe "0.1" + newFloat(0.1).toString shouldBe "0.1" + + newArray(newInteger(0), newString("hello")).toJson shouldBe "[0,\"hello\"]" + newArray(newInteger(0), newString("hello")).toString shouldBe "[0,\"hello\"]" + newArray(newArray(newString("Apple"), newFloat(0.2)), newNil()).toJson shouldBe """[["Apple",0.2],null]""" + + // Map value + val m = newMapBuilder() + .put(newString("id"), newInteger(1001)) + .put(newString("name"), newString("leo")) + .put(newString("address"), newArray(newString("xxx-xxxx"), newString("yyy-yyyy"))) + .put(newString("name"), newString("mitsu")) + .build() + val i1 = JSON.parseFull(m.toJson) + val i2 = JSON.parseFull(m.toString) // expect json value + val a1 = JSON.parseFull("""{"id":1001,"name":"mitsu","address":["xxx-xxxx","yyy-yyyy"]}""") + // Equals as JSON map + i1 shouldBe a1 + i2 shouldBe a1 + + // toJson should quote strings + newString("1").toJson shouldBe "\"1\"" + // toString is for extracting string values + newString("1").toString shouldBe "1" + + } + + "check appropriate range for integers" in { + import ValueFactory._ + import java.lang.Byte + import java.lang.Short + + newInteger(Byte.MAX_VALUE).asByte() shouldBe Byte.MAX_VALUE + newInteger(Byte.MIN_VALUE).asByte() shouldBe Byte.MIN_VALUE + newInteger(Short.MAX_VALUE).asShort() shouldBe Short.MAX_VALUE + newInteger(Short.MIN_VALUE).asShort() shouldBe Short.MIN_VALUE + newInteger(Integer.MAX_VALUE).asInt() shouldBe Integer.MAX_VALUE + newInteger(Integer.MIN_VALUE).asInt() shouldBe Integer.MIN_VALUE + intercept[MessageIntegerOverflowException] { + newInteger(Byte.MAX_VALUE+1).asByte() + } + intercept[MessageIntegerOverflowException] { + newInteger(Byte.MIN_VALUE-1).asByte() + } + intercept[MessageIntegerOverflowException] { + newInteger(Short.MAX_VALUE+1).asShort() + } + intercept[MessageIntegerOverflowException] { + newInteger(Short.MIN_VALUE-1).asShort() + } + intercept[MessageIntegerOverflowException] { + newInteger(Integer.MAX_VALUE+1.toLong).asInt() + } + intercept[MessageIntegerOverflowException] { + newInteger(Integer.MIN_VALUE-1.toLong).asInt() + } + } + } +} diff --git a/msgpack-core/src/test/scala/org/msgpack/value/ValueTypeTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/ValueTypeTest.scala index 467162447..979c33c9b 100644 --- a/msgpack-core/src/test/scala/org/msgpack/value/ValueTypeTest.scala +++ b/msgpack-core/src/test/scala/org/msgpack/value/ValueTypeTest.scala @@ -1,109 +1,92 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.value -import scala.util.Random -import org.msgpack.core.MessagePack.Code -import org.msgpack.core.{MessageFormat, MessageFormatException, MessagePackSpec} import org.msgpack.core.MessagePack.Code._ - +import org.msgpack.core.{MessageFormat, MessageFormatException, MessagePackSpec} /** - * Created on 2014/05/06. - */ -class ValueTypeTest extends MessagePackSpec { + * Created on 2014/05/06. + */ +class ValueTypeTest + extends MessagePackSpec +{ "ValueType" should { - "lookup ValueType from a byte value" taggedAs("code") in { + "lookup ValueType from a byte value" taggedAs ("code") in { - def check(b:Byte, tpe:ValueType) { - ValueType.valueOf(b) shouldBe tpe + def check(b: Byte, tpe: ValueType) + { + MessageFormat.valueOf(b).getValueType shouldBe tpe } - for(i <- 0 until 0x7f) + for (i <- 0 until 0x7f) { check(i.toByte, ValueType.INTEGER) + } - for(i <- 0x80 until 0x8f) + for (i <- 0x80 until 0x8f) { check(i.toByte, ValueType.MAP) + } - for(i <- 0x90 until 0x9f) + for (i <- 0x90 until 0x9f) { check(i.toByte, ValueType.ARRAY) + } check(NIL, ValueType.NIL) try { - ValueType.valueOf(NEVER_USED) + MessageFormat.valueOf(NEVER_USED).getValueType fail("NEVER_USED type should not have ValueType") } catch { - case e:MessageFormatException => - // OK + case e: MessageFormatException => + // OK } check(TRUE, ValueType.BOOLEAN) check(FALSE, ValueType.BOOLEAN) - for(t <- Seq(BIN8, BIN16, BIN32)) + for (t <- Seq(BIN8, BIN16, BIN32)) { check(t, ValueType.BINARY) + } - for(t <- Seq(FIXEXT1, FIXEXT2, FIXEXT4, FIXEXT8, FIXEXT16, EXT8, EXT16, EXT32)) - check(t, ValueType.EXTENDED) + for (t <- Seq(FIXEXT1, FIXEXT2, FIXEXT4, FIXEXT8, FIXEXT16, EXT8, EXT16, EXT32)) { + check(t, ValueType.EXTENSION) + } - for(t <- Seq(INT8, INT16, INT32, INT64, UINT8, UINT16, UINT32, UINT64)) + for (t <- Seq(INT8, INT16, INT32, INT64, UINT8, UINT16, UINT32, UINT64)) { check(t, ValueType.INTEGER) + } - for(t <- Seq(STR8, STR16, STR32)) + for (t <- Seq(STR8, STR16, STR32)) { check(t, ValueType.STRING) + } - for(t <- Seq(FLOAT32, FLOAT64)) + for (t <- Seq(FLOAT32, FLOAT64)) { check(t, ValueType.FLOAT) - - for(t <- Seq(ARRAY16, ARRAY32)) - check(t, ValueType.ARRAY) - - for(i <- 0xe0 until 0xff) - check(i.toByte, ValueType.INTEGER) - - } - - "lookup table" in { - - val N = 100000 - val idx = { - val b = Array.newBuilder[Byte] - for(i <- 0 until N) { - val r = Iterator.continually(Random.nextInt(256)).find(_.toByte != Code.NEVER_USED).get - b += r.toByte - } - b.result() } - time("lookup", repeat=100) { - block("switch") { - var i = 0 - while(i < N) { - MessageFormat.toMessageFormat(idx(i)).getValueType() - i += 1 - } - } - - block("table") { - var i = 0 - while(i < N) { - ValueType.valueOf(idx(i)) - i += 1 - } - } - + for (t <- Seq(ARRAY16, ARRAY32)) { + check(t, ValueType.ARRAY) } - } - - "support isTypeOf" in { - for(v <- ValueType.values()) { - v.isTypeOf(v.getBitMask) shouldBe true + for (i <- 0xe0 until 0xff) { + check(i.toByte, ValueType.INTEGER) } } - - } } diff --git a/msgpack-core/src/test/scala/org/msgpack/value/holder/FloatHolderTest.scala b/msgpack-core/src/test/scala/org/msgpack/value/holder/FloatHolderTest.scala deleted file mode 100644 index 126d39352..000000000 --- a/msgpack-core/src/test/scala/org/msgpack/value/holder/FloatHolderTest.scala +++ /dev/null @@ -1,26 +0,0 @@ -package org.msgpack.value.holder - -import org.msgpack.core.MessagePackSpec - -/** - * - */ -class FloatHolderTest extends MessagePackSpec { - - "FloatHolder" should { - - "display value in an appropriate format" in { - - val h = new FloatHolder - val f = 0.1341f - h.setFloat(f) - h.toString shouldBe java.lang.Float.toString(f) - - val d = 0.1341341344 - h.setDouble(d) - h.toString shouldBe java.lang.Double.toString(d) - } - - } - -} diff --git a/msgpack-jackson/README.md b/msgpack-jackson/README.md index a10b2c6aa..51435b401 100644 --- a/msgpack-jackson/README.md +++ b/msgpack-jackson/README.md @@ -1,26 +1,38 @@ # jackson-dataformat-msgpack This Jackson extension library handles reading and writing of data encoded in [MessagePack](http://msgpack.org/) data format. -It extends standard Jackson streaming API (`JsonFactory`, `JsonParser`, `JsonGenerator`), and as such works seamlessly with all the higher level data abstractions (data binding, tree model, and pluggable extensions). +It extends standard Jackson streaming API (`JsonFactory`, `JsonParser`, `JsonGenerator`), and as such works seamlessly with all the higher level data abstractions (data binding, tree model, and pluggable extensions). For the details of Jackson-annotations, please see https://github.com/FasterXML/jackson-annotations. -## Maven dependency +## Install -To use this module on Maven-based projects, use following dependency: +### Maven ``` org.msgpack jackson-dataformat-msgpack - 0.7.0-p5 + 0.7.1 ``` +### Gradle +``` +repositories { + mavenCentral() +} + +dependencies { + compile 'org.msgpack:jackson-dataformat-msgpack:0.7.1' +} +``` + + ## Usage -Only thing you need to do is to instantiate MessagePackFactory and pass it to the constructor of ObjectMapper. +Only thing you need to do is to instantiate MessagePackFormatFactory and pass it to the constructor of ObjectMapper. ``` - ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFormatFactory()); ExamplePojo orig = new ExamplePojo("komamitsu"); byte[] bytes = objectMapper.writeValueAsBytes(orig); ExamplePojo value = objectMapper.readValue(bytes, ExamplePojo.class); diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackExtensionType.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackExtensionType.java new file mode 100644 index 000000000..1906757f5 --- /dev/null +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackExtensionType.java @@ -0,0 +1,48 @@ +package org.msgpack.jackson.dataformat; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import java.io.IOException; + +@JsonSerialize(using = MessagePackExtensionType.Serializer.class) +public class MessagePackExtensionType +{ + private final byte type; + private final byte[] data; + + public MessagePackExtensionType(byte type, byte[] data) + { + this.type = type; + this.data = data; + } + + public byte getType() + { + return type; + } + + public byte[] getData() + { + return data; + } + + public static class Serializer extends JsonSerializer + { + @Override + public void serialize(MessagePackExtensionType value, JsonGenerator gen, SerializerProvider serializers) + throws IOException, JsonProcessingException + { + if (gen instanceof MessagePackGenerator) { + MessagePackGenerator msgpackGenerator = (MessagePackGenerator) gen; + msgpackGenerator.writeExtensionType(value); + } + else { + throw new IllegalStateException("'gen' is expected to be MessagePackGenerator but it's " + gen.getClass()); + } + } + } +} diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java index 3d63f22a2..ff7aa373f 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackFactory.java @@ -15,56 +15,79 @@ // package org.msgpack.jackson.dataformat; -import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.io.IOContext; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Writer; import java.util.Arrays; -public class MessagePackFactory extends JsonFactory { +public class MessagePackFactory + extends JsonFactory +{ private static final long serialVersionUID = 2578263992015504347L; - protected int messagePackGeneratorFeature = 0; - protected int messagePackParserFeature = 0; @Override - public JsonGenerator createGenerator(OutputStream out, JsonEncoding enc) throws IOException { - return new MessagePackGenerator(messagePackGeneratorFeature, _objectCodec, out); + public JsonGenerator createGenerator(OutputStream out, JsonEncoding enc) + throws IOException + { + return new MessagePackGenerator(_generatorFeatures, _objectCodec, out); } @Override - public JsonGenerator createGenerator(File f, JsonEncoding enc) throws IOException { + public JsonGenerator createGenerator(File f, JsonEncoding enc) + throws IOException + { return createGenerator(new FileOutputStream(f), enc); } @Override - public JsonGenerator createGenerator(Writer w) throws IOException { + public JsonGenerator createGenerator(Writer w) + throws IOException + { throw new UnsupportedOperationException(); } @Override - public JsonParser createParser(byte[] data) throws IOException, JsonParseException { + public JsonParser createParser(byte[] data) + throws IOException, JsonParseException + { IOContext ioContext = _createContext(data, false); return _createParser(data, 0, data.length, ioContext); } @Override - public JsonParser createParser(InputStream in) throws IOException, JsonParseException { + public JsonParser createParser(InputStream in) + throws IOException, JsonParseException + { IOContext ioContext = _createContext(in, false); return _createParser(in, ioContext); } @Override - protected MessagePackParser _createParser(InputStream in, IOContext ctxt) throws IOException { - MessagePackParser parser = new MessagePackParser(ctxt, messagePackParserFeature, in); + protected MessagePackParser _createParser(InputStream in, IOContext ctxt) + throws IOException + { + MessagePackParser parser = new MessagePackParser(ctxt, _parserFeatures, _objectCodec, in); return parser; } @Override - protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException, JsonParseException { + protected JsonParser _createParser(byte[] data, int offset, int len, IOContext ctxt) + throws IOException, JsonParseException + { if (offset != 0 || len != data.length) { data = Arrays.copyOfRange(data, offset, offset + len); } - MessagePackParser parser = new MessagePackParser(ctxt, messagePackParserFeature, data); + MessagePackParser parser = new MessagePackParser(ctxt, _parserFeatures, _objectCodec, data); return parser; } } diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java index 7e21ff58b..e62528a75 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackGenerator.java @@ -1,3 +1,18 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.jackson.dataformat; import com.fasterxml.jackson.core.Base64Variant; @@ -5,6 +20,7 @@ import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.core.base.GeneratorBase; import com.fasterxml.jackson.core.json.JsonWriteContext; +import org.msgpack.core.MessagePack; import org.msgpack.core.MessagePacker; import org.msgpack.core.buffer.OutputStreamBufferOutput; @@ -18,56 +34,70 @@ import java.util.LinkedList; import java.util.List; -public class MessagePackGenerator extends GeneratorBase { +public class MessagePackGenerator + extends GeneratorBase +{ private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private static ThreadLocal messagePackersHolder = new ThreadLocal(); private static ThreadLocal messageBufferOutputHolder = new ThreadLocal(); private LinkedList stack; private StackItem rootStackItem; - - private static abstract class StackItem { + private abstract static class StackItem + { protected List objectKeys = new ArrayList(); protected List objectValues = new ArrayList(); abstract void addKey(String key); - void addValue(Object value) { + void addValue(Object value) + { objectValues.add(value); } abstract List getKeys(); - List getValues() { + List getValues() + { return objectValues; } } - private static class StackItemForObject extends StackItem { + private static class StackItemForObject + extends StackItem + { @Override - void addKey(String key) { + void addKey(String key) + { objectKeys.add(key); } @Override - List getKeys() { + List getKeys() + { return objectKeys; } } - private static class StackItemForArray extends StackItem { + private static class StackItemForArray + extends StackItem + { @Override - void addKey(String key) { + void addKey(String key) + { throw new IllegalStateException("This method shouldn't be called"); } @Override - List getKeys() { + List getKeys() + { throw new IllegalStateException("This method shouldn't be called"); } } - public MessagePackGenerator(int features, ObjectCodec codec, OutputStream out) throws IOException { + public MessagePackGenerator(int features, ObjectCodec codec, OutputStream out) + throws IOException + { super(features, codec); MessagePacker messagePacker = messagePackersHolder.get(); OutputStreamBufferOutput messageBufferOutput = messageBufferOutputHolder.get(); @@ -80,7 +110,7 @@ public MessagePackGenerator(int features, ObjectCodec codec, OutputStream out) t messageBufferOutputHolder.set(messageBufferOutput); if (messagePacker == null) { - messagePacker = new MessagePacker(messageBufferOutput); + messagePacker = MessagePack.newDefaultPacker(messageBufferOutput); } else { messagePacker.reset(messageBufferOutput); @@ -91,13 +121,17 @@ public MessagePackGenerator(int features, ObjectCodec codec, OutputStream out) t } @Override - public void writeStartArray() throws IOException, JsonGenerationException { + public void writeStartArray() + throws IOException, JsonGenerationException + { _writeContext = _writeContext.createChildArrayContext(); stack.push(new StackItemForArray()); } @Override - public void writeEndArray() throws IOException, JsonGenerationException { + public void writeEndArray() + throws IOException, JsonGenerationException + { if (!_writeContext.inArray()) { _reportError("Current context not an array but " + _writeContext.getTypeDesc()); } @@ -110,13 +144,17 @@ public void writeEndArray() throws IOException, JsonGenerationException { } @Override - public void writeStartObject() throws IOException, JsonGenerationException { + public void writeStartObject() + throws IOException, JsonGenerationException + { _writeContext = _writeContext.createChildObjectContext(); stack.push(new StackItemForObject()); } @Override - public void writeEndObject() throws IOException, JsonGenerationException { + public void writeEndObject() + throws IOException, JsonGenerationException + { if (!_writeContext.inObject()) { _reportError("Current context not an object but " + _writeContext.getTypeDesc()); } @@ -134,7 +172,9 @@ public void writeEndObject() throws IOException, JsonGenerationException { popStackAndStoreTheItemAsValue(); } - private void packValue(Object v) throws IOException { + private void packValue(Object v) + throws IOException + { MessagePacker messagePacker = getMessagePacker(); if (v == null) { messagePacker.packNil(); @@ -144,8 +184,17 @@ else if (v instanceof Integer) { } else if (v instanceof ByteBuffer) { ByteBuffer bb = (ByteBuffer) v; - messagePacker.packBinaryHeader(bb.limit()); - messagePacker.writePayload(bb); + int len = bb.remaining(); + if (bb.hasArray()) { + messagePacker.packBinaryHeader(len); + messagePacker.writePayload(bb.array(), bb.arrayOffset(), len); + } + else { + byte[] data = new byte[len]; + bb.get(data); + messagePacker.packBinaryHeader(len); + messagePacker.addPayload(data); + } } else if (v instanceof String) { messagePacker.packString((String) v); @@ -169,18 +218,52 @@ else if (v instanceof BigInteger) { messagePacker.packBigInteger((BigInteger) v); } else if (v instanceof BigDecimal) { - // TODO - throw new UnsupportedOperationException("BigDecimal isn't supported yet"); + packBigDecimal((BigDecimal) v); } else if (v instanceof Boolean) { messagePacker.packBoolean((Boolean) v); } + else if (v instanceof MessagePackExtensionType) { + MessagePackExtensionType extensionType = (MessagePackExtensionType) v; + byte[] extData = extensionType.getData(); + messagePacker.packExtensionTypeHeader(extensionType.getType(), extData.length); + messagePacker.writePayload(extData); + } else { throw new IllegalArgumentException(v.toString()); } } - private void packObject(StackItemForObject stackItem) throws IOException { + private void packBigDecimal(BigDecimal decimal) + throws IOException + { + MessagePacker messagePacker = getMessagePacker(); + boolean failedToPackAsBI = false; + try { + //Check to see if this BigDecimal can be converted to BigInteger + BigInteger integer = decimal.toBigIntegerExact(); + messagePacker.packBigInteger(integer); + } + catch (ArithmeticException e) { + failedToPackAsBI = true; + } + catch (IllegalArgumentException e) { + failedToPackAsBI = true; + } + + if (failedToPackAsBI) { + double doubleValue = decimal.doubleValue(); + //Check to make sure this BigDecimal can be represented as a double + if (!decimal.stripTrailingZeros().toEngineeringString().equals(BigDecimal.valueOf(doubleValue).toEngineeringString())) { + throw new IllegalArgumentException("MessagePack cannot serialize a BigDecimal that can't be represented as double. " + decimal); + } + messagePacker.packDouble(doubleValue); + } + } + + private void packObject(StackItemForObject stackItem) + throws IOException + { List keys = stackItem.getKeys(); List values = stackItem.getValues(); @@ -194,7 +277,9 @@ private void packObject(StackItemForObject stackItem) throws IOException { } } - private void packArray(StackItemForArray stackItem) throws IOException { + private void packArray(StackItemForArray stackItem) + throws IOException + { List values = stackItem.getValues(); MessagePacker messagePacker = getMessagePacker(); @@ -207,116 +292,163 @@ private void packArray(StackItemForArray stackItem) throws IOException { } @Override - public void writeFieldName(String name) throws IOException, JsonGenerationException { + public void writeFieldName(String name) + throws IOException, JsonGenerationException + { addKeyToStackTop(name); } @Override - public void writeString(String text) throws IOException, JsonGenerationException { + public void writeString(String text) + throws IOException, JsonGenerationException + { addValueToStackTop(text); } @Override - public void writeString(char[] text, int offset, int len) throws IOException, JsonGenerationException { + public void writeString(char[] text, int offset, int len) + throws IOException, JsonGenerationException + { addValueToStackTop(new String(text, offset, len)); } @Override - public void writeRawUTF8String(byte[] text, int offset, int length) throws IOException, JsonGenerationException { + public void writeRawUTF8String(byte[] text, int offset, int length) + throws IOException, JsonGenerationException + { addValueToStackTop(new String(text, offset, length, DEFAULT_CHARSET)); } @Override - public void writeUTF8String(byte[] text, int offset, int length) throws IOException, JsonGenerationException { + public void writeUTF8String(byte[] text, int offset, int length) + throws IOException, JsonGenerationException + { addValueToStackTop(new String(text, offset, length, DEFAULT_CHARSET)); } @Override - public void writeRaw(String text) throws IOException, JsonGenerationException { + public void writeRaw(String text) + throws IOException, JsonGenerationException + { addValueToStackTop(text); } @Override - public void writeRaw(String text, int offset, int len) throws IOException, JsonGenerationException { + public void writeRaw(String text, int offset, int len) + throws IOException, JsonGenerationException + { addValueToStackTop(text.substring(0, len)); } @Override - public void writeRaw(char[] text, int offset, int len) throws IOException, JsonGenerationException { + public void writeRaw(char[] text, int offset, int len) + throws IOException, JsonGenerationException + { addValueToStackTop(new String(text, offset, len)); } @Override - public void writeRaw(char c) throws IOException, JsonGenerationException { + public void writeRaw(char c) + throws IOException, JsonGenerationException + { addValueToStackTop(String.valueOf(c)); } @Override - public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException, JsonGenerationException { + public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) + throws IOException, JsonGenerationException + { addValueToStackTop(ByteBuffer.wrap(data, offset, len)); } @Override - public void writeNumber(int v) throws IOException, JsonGenerationException { + public void writeNumber(int v) + throws IOException, JsonGenerationException + { addValueToStackTop(Integer.valueOf(v)); } @Override - public void writeNumber(long v) throws IOException, JsonGenerationException { + public void writeNumber(long v) + throws IOException, JsonGenerationException + { addValueToStackTop(Long.valueOf(v)); } @Override - public void writeNumber(BigInteger v) throws IOException, JsonGenerationException { + public void writeNumber(BigInteger v) + throws IOException, JsonGenerationException + { addValueToStackTop(v); } @Override - public void writeNumber(double d) throws IOException, JsonGenerationException { + public void writeNumber(double d) + throws IOException, JsonGenerationException + { addValueToStackTop(Double.valueOf(d)); } @Override - public void writeNumber(float f) throws IOException, JsonGenerationException { + public void writeNumber(float f) + throws IOException, JsonGenerationException + { addValueToStackTop(Float.valueOf(f)); } @Override - public void writeNumber(BigDecimal dec) throws IOException, JsonGenerationException { + public void writeNumber(BigDecimal dec) + throws IOException, JsonGenerationException + { addValueToStackTop(dec); } @Override - public void writeNumber(String encodedValue) throws IOException, JsonGenerationException, UnsupportedOperationException { + public void writeNumber(String encodedValue) + throws IOException, JsonGenerationException, UnsupportedOperationException + { throw new UnsupportedOperationException("writeNumber(String encodedValue) isn't supported yet"); } @Override - public void writeBoolean(boolean state) throws IOException, JsonGenerationException { + public void writeBoolean(boolean state) + throws IOException, JsonGenerationException + { addValueToStackTop(Boolean.valueOf(state)); } @Override - public void writeNull() throws IOException, JsonGenerationException { + public void writeNull() + throws IOException, JsonGenerationException + { addValueToStackTop(null); } + public void writeExtensionType(MessagePackExtensionType extensionType) + throws IOException + { + addValueToStackTop(extensionType); + } + @Override - public void close() throws IOException { + public void close() + throws IOException + { try { flush(); } - catch (Exception e) { - e.printStackTrace(); - } finally { - MessagePacker messagePacker = getMessagePacker(); - messagePacker.close(); + if (isEnabled(Feature.AUTO_CLOSE_TARGET)) { + MessagePacker messagePacker = getMessagePacker(); + messagePacker.close(); + } } } @Override - public void flush() throws IOException { + public void flush() + throws IOException + { if (rootStackItem != null) { if (rootStackItem instanceof StackItemForObject) { packObject((StackItemForObject) rootStackItem); @@ -328,32 +460,42 @@ else if (rootStackItem instanceof StackItemForArray) { throw new IllegalStateException("Unexpected rootStackItem: " + rootStackItem); } rootStackItem = null; - MessagePacker messagePacker = getMessagePacker(); - messagePacker.flush(); + flushMessagePacker(); } } - @Override - protected void _releaseBuffers() { + private void flushMessagePacker() + throws IOException + { + MessagePacker messagePacker = getMessagePacker(); + messagePacker.flush(); + } + @Override + protected void _releaseBuffers() + { } @Override - protected void _verifyValueWrite(String typeMsg) throws IOException, JsonGenerationException { + protected void _verifyValueWrite(String typeMsg) + throws IOException, JsonGenerationException + { int status = _writeContext.writeValue(); if (status == JsonWriteContext.STATUS_EXPECT_NAME) { - _reportError("Can not "+typeMsg+", expecting field name"); + _reportError("Can not " + typeMsg + ", expecting field name"); } } - private StackItem getStackTop() { + private StackItem getStackTop() + { if (stack.isEmpty()) { throw new IllegalStateException("The stack is empty"); } return stack.getFirst(); } - private StackItemForObject getStackTopForObject() { + private StackItemForObject getStackTopForObject() + { StackItem stackTop = getStackTop(); if (!(stackTop instanceof StackItemForObject)) { throw new IllegalStateException("The stack top should be Object: " + stackTop); @@ -361,7 +503,8 @@ private StackItemForObject getStackTopForObject() { return (StackItemForObject) stackTop; } - private StackItemForArray getStackTopForArray() { + private StackItemForArray getStackTopForArray() + { StackItem stackTop = getStackTop(); if (!(stackTop instanceof StackItemForArray)) { throw new IllegalStateException("The stack top should be Array: " + stackTop); @@ -369,15 +512,26 @@ private StackItemForArray getStackTopForArray() { return (StackItemForArray) stackTop; } - private void addKeyToStackTop(String key) { + private void addKeyToStackTop(String key) + { getStackTop().addKey(key); } - private void addValueToStackTop(Object value) { - getStackTop().addValue(value); + private void addValueToStackTop(Object value) + throws IOException + { + if (stack.isEmpty()) { + packValue(value); + flushMessagePacker(); + } + else { + getStackTop().addValue(value); + } } - private void popStackAndStoreTheItemAsValue() { + private void popStackAndStoreTheItemAsValue() + throws IOException + { StackItem child = stack.pop(); if (stack.size() > 0) { addValueToStackTop(child); @@ -392,7 +546,8 @@ private void popStackAndStoreTheItemAsValue() { } } - private MessagePacker getMessagePacker() { + private MessagePacker getMessagePacker() + { MessagePacker messagePacker = messagePackersHolder.get(); if (messagePacker == null) { throw new IllegalStateException("messagePacker is null"); diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java index 926b5691b..e85ff7cd6 100644 --- a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/MessagePackParser.java @@ -1,18 +1,44 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.jackson.dataformat; -import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.Base64Variant; +import com.fasterxml.jackson.core.JsonLocation; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.base.ParserMinimalBase; import com.fasterxml.jackson.core.io.IOContext; import com.fasterxml.jackson.core.json.DupDetector; import com.fasterxml.jackson.core.json.JsonReadContext; -import org.msgpack.core.MessageFormat; +import org.msgpack.core.MessagePack; import org.msgpack.core.MessageUnpacker; import org.msgpack.core.buffer.ArrayBufferInput; import org.msgpack.core.buffer.InputStreamBufferInput; import org.msgpack.core.buffer.MessageBufferInput; -import org.msgpack.value.NumberValue; +import org.msgpack.value.ExtensionValue; +import org.msgpack.value.IntegerValue; +import org.msgpack.value.Value; +import org.msgpack.value.ValueFactory; import org.msgpack.value.ValueType; -import org.msgpack.value.holder.ValueHolder; +import org.msgpack.value.Variable; import java.io.IOException; import java.io.InputStream; @@ -20,88 +46,124 @@ import java.math.BigInteger; import java.util.LinkedList; -public class MessagePackParser extends ParserMinimalBase { - private static final ThreadLocal messageUnpackerHolder = new ThreadLocal(); +public class MessagePackParser + extends ParserMinimalBase +{ + private static final ThreadLocal> messageUnpackerHolder = + new ThreadLocal>(); private ObjectCodec codec; private JsonReadContext parsingContext; private final LinkedList stack = new LinkedList(); - private final ValueHolder valueHolder = new ValueHolder(); + private Value value = ValueFactory.newNil(); + private Variable var = new Variable(); private boolean isClosed; private long tokenPosition; private long currentPosition; private final IOContext ioContext; - private static abstract class StackItem { + private abstract static class StackItem + { private long numOfElements; - protected StackItem(long numOfElements) { + protected StackItem(long numOfElements) + { this.numOfElements = numOfElements; } - public void consume() { - numOfElements--; + public void consume() + { + numOfElements--; } - public boolean isEmpty() { + public boolean isEmpty() + { return numOfElements == 0; } } - private static class StackItemForObject extends StackItem { - StackItemForObject(long numOfElements) { + private static class StackItemForObject + extends StackItem + { + StackItemForObject(long numOfElements) + { super(numOfElements); } } - private static class StackItemForArray extends StackItem { - StackItemForArray(long numOfElements) { + private static class StackItemForArray + extends StackItem + { + StackItemForArray(long numOfElements) + { super(numOfElements); } } - public MessagePackParser(IOContext ctxt, int features, InputStream in) throws IOException { - this(ctxt, features, new InputStreamBufferInput(in)); + public MessagePackParser(IOContext ctxt, int features, ObjectCodec objectCodec, InputStream in) + throws IOException + { + this(ctxt, features, new InputStreamBufferInput(in), objectCodec, in); } - public MessagePackParser(IOContext ctxt, int features, byte[] bytes) throws IOException { - this(ctxt, features, new ArrayBufferInput(bytes)); + public MessagePackParser(IOContext ctxt, int features, ObjectCodec objectCodec, byte[] bytes) + throws IOException + { + this(ctxt, features, new ArrayBufferInput(bytes), objectCodec, bytes); } - private MessagePackParser(IOContext ctxt, int features, MessageBufferInput input) throws IOException { + private MessagePackParser(IOContext ctxt, int features, MessageBufferInput input, ObjectCodec objectCodec, Object src) + throws IOException + { + super(features); + + this.codec = objectCodec; ioContext = ctxt; DupDetector dups = Feature.STRICT_DUPLICATE_DETECTION.enabledIn(features) ? DupDetector.rootDetector(this) : null; parsingContext = JsonReadContext.createRootContext(dups); - MessageUnpacker messageUnpacker = messageUnpackerHolder.get(); - if (messageUnpacker == null) { - messageUnpacker = new MessageUnpacker(input); + MessageUnpacker messageUnpacker; + Tuple messageUnpackerTuple = messageUnpackerHolder.get(); + if (messageUnpackerTuple == null) { + messageUnpacker = MessagePack.newDefaultUnpacker(input); } else { - messageUnpacker.reset(input); + // Considering to reuse InputStream with JsonParser.Feature.AUTO_CLOSE_SOURCE, + // MessagePackParser needs to use the MessageUnpacker that has the same InputStream + // since it has buffer which has loaded the InputStream data ahead. + // However, it needs to call MessageUnpacker#reset when the source is different from the previous one. + if (isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE) || messageUnpackerTuple.first() != src) { + messageUnpackerTuple.second().reset(input); + } + messageUnpacker = messageUnpackerTuple.second(); } - messageUnpackerHolder.set(messageUnpacker); + messageUnpackerHolder.set(new Tuple(src, messageUnpacker)); } @Override - public ObjectCodec getCodec() { + public ObjectCodec getCodec() + { return codec; } @Override - public void setCodec(ObjectCodec c) { + public void setCodec(ObjectCodec c) + { codec = c; } @Override - public Version version() { + public Version version() + { return null; } @Override - public JsonToken nextToken() throws IOException, JsonParseException { + public JsonToken nextToken() + throws IOException, JsonParseException + { MessageUnpacker messageUnpacker = getMessageUnpacker(); tokenPosition = messageUnpacker.getTotalReadBytes(); @@ -116,33 +178,56 @@ public JsonToken nextToken() throws IOException, JsonParseException { } } - MessageFormat nextFormat = messageUnpacker.getNextFormat(); - ValueType valueType = nextFormat.getValueType(); + if (!messageUnpacker.hasNext()) { + return null; + } + + ValueType type = messageUnpacker.getNextFormat().getValueType(); // We should push a new StackItem lazily after updating the current stack. StackItem newStack = null; - switch (valueType) { + switch (type) { case NIL: messageUnpacker.unpackNil(); + value = ValueFactory.newNil(); nextToken = JsonToken.VALUE_NULL; break; case BOOLEAN: boolean b = messageUnpacker.unpackBoolean(); - nextToken = b ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE; + value = ValueFactory.newNil(); + if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { + parsingContext.setCurrentName(Boolean.toString(b)); + nextToken = JsonToken.FIELD_NAME; + } + else { + nextToken = b ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE; + } break; case INTEGER: - messageUnpacker.unpackValue(valueHolder); - nextToken = JsonToken.VALUE_NUMBER_INT; + value = messageUnpacker.unpackValue(var); + if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { + parsingContext.setCurrentName(value.asIntegerValue().toString()); + nextToken = JsonToken.FIELD_NAME; + } + else { + nextToken = JsonToken.VALUE_NUMBER_INT; + } break; case FLOAT: - messageUnpacker.unpackValue(valueHolder); - nextToken = JsonToken.VALUE_NUMBER_FLOAT; + value = messageUnpacker.unpackValue(var); + if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { + parsingContext.setCurrentName(value.asFloatValue().toString()); + nextToken = JsonToken.FIELD_NAME; + } + else { + nextToken = JsonToken.VALUE_NUMBER_FLOAT; + } break; case STRING: - messageUnpacker.unpackValue(valueHolder); + value = messageUnpacker.unpackValue(var); if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { - parsingContext.setCurrentName(valueHolder.getRef().asRaw().toString()); + parsingContext.setCurrentName(value.asRawValue().toString()); nextToken = JsonToken.FIELD_NAME; } else { @@ -150,17 +235,27 @@ public JsonToken nextToken() throws IOException, JsonParseException { } break; case BINARY: - messageUnpacker.unpackValue(valueHolder); - nextToken = JsonToken.VALUE_EMBEDDED_OBJECT; + value = messageUnpacker.unpackValue(var); + if (parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { + parsingContext.setCurrentName(value.asRawValue().toString()); + nextToken = JsonToken.FIELD_NAME; + } + else { + nextToken = JsonToken.VALUE_EMBEDDED_OBJECT; + } break; case ARRAY: + value = ValueFactory.newNil(); newStack = new StackItemForArray(messageUnpacker.unpackArrayHeader()); break; case MAP: + value = ValueFactory.newNil(); newStack = new StackItemForObject(messageUnpacker.unpackMapHeader()); break; - case EXTENDED: - throw new UnsupportedOperationException(); + case EXTENSION: + value = messageUnpacker.unpackValue(var); + nextToken = JsonToken.VALUE_EMBEDDED_OBJECT; + break; default: throw new IllegalStateException("Shouldn't reach here"); } @@ -187,110 +282,183 @@ else if (newStack instanceof StackItemForObject) { } @Override - protected void _handleEOF() throws JsonParseException {} + protected void _handleEOF() + throws JsonParseException + { + } @Override - public String getText() throws IOException, JsonParseException { + public String getText() + throws IOException, JsonParseException + { // This method can be called for new BigInteger(text) - return valueHolder.getRef().toString(); + if (value.isRawValue()) { + return value.asRawValue().toString(); + } + else { + return value.toString(); + } } @Override - public char[] getTextCharacters() throws IOException, JsonParseException { + public char[] getTextCharacters() + throws IOException, JsonParseException + { return getText().toCharArray(); } @Override - public boolean hasTextCharacters() { + public boolean hasTextCharacters() + { return false; } @Override - public int getTextLength() throws IOException, JsonParseException { + public int getTextLength() + throws IOException, JsonParseException + { return getText().length(); } @Override - public int getTextOffset() throws IOException, JsonParseException { + public int getTextOffset() + throws IOException, JsonParseException + { return 0; } @Override - public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException { - return valueHolder.getRef().asBinary().toByteArray(); + public byte[] getBinaryValue(Base64Variant b64variant) + throws IOException, JsonParseException + { + return value.asRawValue().asByteArray(); } @Override - public Number getNumberValue() throws IOException, JsonParseException { - NumberValue numberValue = valueHolder.getRef().asNumber(); - if (numberValue.isValidInt()) { - return numberValue.toInt(); - } - else if (numberValue.isValidLong()) { - return numberValue.toLong(); + public Number getNumberValue() + throws IOException, JsonParseException + { + if (value.isIntegerValue()) { + IntegerValue integerValue = value.asIntegerValue(); + if (integerValue.isInIntRange()) { + return integerValue.toInt(); + } + else if (integerValue.isInLongRange()) { + return integerValue.toLong(); + } + else { + return integerValue.toBigInteger(); + } } else { - return numberValue.toBigInteger(); + return value.asNumberValue().toDouble(); } } @Override - public int getIntValue() throws IOException, JsonParseException { - return valueHolder.getRef().asNumber().toInt(); + public int getIntValue() + throws IOException, JsonParseException + { + return value.asNumberValue().toInt(); } @Override - public long getLongValue() throws IOException, JsonParseException { - return valueHolder.getRef().asNumber().toLong(); + public long getLongValue() + throws IOException, JsonParseException + { + return value.asNumberValue().toLong(); } @Override - public BigInteger getBigIntegerValue() throws IOException, JsonParseException { - return valueHolder.getRef().asNumber().toBigInteger(); + public BigInteger getBigIntegerValue() + throws IOException, JsonParseException + { + return value.asNumberValue().toBigInteger(); } @Override - public float getFloatValue() throws IOException, JsonParseException { - return valueHolder.getRef().asFloat().toFloat(); + public float getFloatValue() + throws IOException, JsonParseException + { + return value.asNumberValue().toFloat(); } @Override - public double getDoubleValue() throws IOException, JsonParseException { - return valueHolder.getRef().asFloat().toDouble(); + public double getDoubleValue() + throws IOException, JsonParseException + { + return value.asNumberValue().toDouble(); } @Override - public BigDecimal getDecimalValue() throws IOException { - return null; + public BigDecimal getDecimalValue() + throws IOException + { + if (value.isIntegerValue()) { + IntegerValue number = value.asIntegerValue(); + //optimization to not convert the value to BigInteger unnecessarily + if (number.isInLongRange()) { + return BigDecimal.valueOf(number.toLong()); + } + else { + return new BigDecimal(number.toBigInteger()); + } + } + else if (value.isFloatValue()) { + return BigDecimal.valueOf(value.asFloatValue().toDouble()); + } + else { + throw new UnsupportedOperationException("Couldn't parse value as BigDecimal. " + value); + } } @Override - public Object getEmbeddedObject() throws IOException, JsonParseException { - return valueHolder.getRef().asBinary().toByteArray(); + public Object getEmbeddedObject() + throws IOException, JsonParseException + { + if (value.isBinaryValue()) { + return value.asBinaryValue().asByteArray(); + } + else if (value.isExtensionValue()) { + ExtensionValue extensionValue = value.asExtensionValue(); + return new MessagePackExtensionType(extensionValue.getType(), extensionValue.getData()); + } + else { + throw new UnsupportedOperationException(); + } } @Override - public NumberType getNumberType() throws IOException, JsonParseException { - NumberValue numberValue = valueHolder.getRef().asNumber(); - if (numberValue.isValidInt()) { - return NumberType.INT; - } - else if (numberValue.isValidLong()) { - return NumberType.LONG; + public NumberType getNumberType() + throws IOException, JsonParseException + { + if (value.isIntegerValue()) { + IntegerValue integerValue = value.asIntegerValue(); + if (integerValue.isInIntRange()) { + return NumberType.INT; + } + else if (integerValue.isInLongRange()) { + return NumberType.LONG; + } + else { + return NumberType.BIG_INTEGER; + } } else { - return NumberType.BIG_INTEGER; + value.asNumberValue(); + return NumberType.DOUBLE; } } @Override - public void close() throws IOException { + public void close() + throws IOException + { try { - MessageUnpacker messageUnpacker = getMessageUnpacker(); - messageUnpacker.close(); - } - catch (Exception e) { - e.printStackTrace(); + if (isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)) { + MessageUnpacker messageUnpacker = getMessageUnpacker(); + messageUnpacker.close(); + } } finally { isClosed = true; @@ -298,27 +466,32 @@ public void close() throws IOException { } @Override - public boolean isClosed() { + public boolean isClosed() + { return isClosed; } @Override - public JsonStreamContext getParsingContext() { + public JsonStreamContext getParsingContext() + { return parsingContext; } @Override - public JsonLocation getTokenLocation() { + public JsonLocation getTokenLocation() + { return new JsonLocation(ioContext.getSourceReference(), tokenPosition, -1, -1, (int) tokenPosition); } @Override - public JsonLocation getCurrentLocation() { + public JsonLocation getCurrentLocation() + { return new JsonLocation(ioContext.getSourceReference(), currentPosition, -1, -1, (int) currentPosition); } @Override - public void overrideCurrentName(String name) { + public void overrideCurrentName(String name) + { try { if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { JsonReadContext parent = parsingContext.getParent(); @@ -327,12 +500,16 @@ public void overrideCurrentName(String name) { else { parsingContext.setCurrentName(name); } - } catch (JsonProcessingException e) { + } + catch (JsonProcessingException e) { throw new IllegalStateException(e); } } - @Override public String getCurrentName() throws IOException { + @Override + public String getCurrentName() + throws IOException + { if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { JsonReadContext parent = parsingContext.getParent(); return parent.getCurrentName(); @@ -340,11 +517,12 @@ public void overrideCurrentName(String name) { return parsingContext.getCurrentName(); } - private MessageUnpacker getMessageUnpacker() { - MessageUnpacker messageUnpacker = messageUnpackerHolder.get(); - if (messageUnpacker == null) { + private MessageUnpacker getMessageUnpacker() + { + Tuple messageUnpackerTuple = messageUnpackerHolder.get(); + if (messageUnpackerTuple == null) { throw new IllegalStateException("messageUnpacker is null"); } - return messageUnpacker; + return messageUnpackerTuple.second(); } } diff --git a/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/Tuple.java b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/Tuple.java new file mode 100644 index 000000000..1a252739f --- /dev/null +++ b/msgpack-jackson/src/main/java/org/msgpack/jackson/dataformat/Tuple.java @@ -0,0 +1,41 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.jackson.dataformat; + +/** + * Created by komamitsu on 5/28/15. + */ +public class Tuple +{ + private final F first; + private final S second; + + public Tuple(F first, S second) + { + this.first = first; + this.second = second; + } + + public F first() + { + return first; + } + + public S second() + { + return second; + } +} diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/ExampleOfTypeInformationSerDe.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/ExampleOfTypeInformationSerDe.java new file mode 100644 index 000000000..5414b0bdc --- /dev/null +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/ExampleOfTypeInformationSerDe.java @@ -0,0 +1,167 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.jackson.dataformat; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class ExampleOfTypeInformationSerDe + extends MessagePackDataformatTestBase +{ + static class A + { + private List list = new ArrayList(); + + public List getList() + { + return list; + } + + public void setList(List list) + { + this.list = list; + } + } + + static class B + { + private String str; + + public String getStr() + { + return str; + } + + public void setStr(String str) + { + this.str = str; + } + } + + @JsonSerialize(using = ObjectContainerSerializer.class) + @JsonDeserialize(using = ObjectContainerDeserializer.class) + static class ObjectContainer + { + private final Map objects; + + public ObjectContainer(Map objects) + { + this.objects = objects; + } + + public Map getObjects() + { + return objects; + } + } + + static class ObjectContainerSerializer + extends JsonSerializer + { + @Override + public void serialize(ObjectContainer value, JsonGenerator gen, SerializerProvider serializers) + throws IOException, JsonProcessingException + { + gen.writeStartObject(); + HashMap metadata = new HashMap(); + for (Map.Entry entry : value.getObjects().entrySet()) { + metadata.put(entry.getKey(), entry.getValue().getClass().getName()); + } + gen.writeObjectField("__metadata", metadata); + gen.writeObjectField("objects", value.getObjects()); + gen.writeEndObject(); + } + } + + static class ObjectContainerDeserializer + extends JsonDeserializer + { + @Override + public ObjectContainer deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + ObjectContainer objectContainer = new ObjectContainer(new HashMap()); + TreeNode treeNode = p.readValueAsTree(); + + Map metadata = treeNode.get("__metadata").traverse(p.getCodec()).readValueAs(new TypeReference>() {}); + TreeNode dataMapTree = treeNode.get("objects"); + for (Map.Entry entry : metadata.entrySet()) { + try { + Object o = dataMapTree.get(entry.getKey()).traverse(p.getCodec()).readValueAs(Class.forName(entry.getValue())); + objectContainer.getObjects().put(entry.getKey(), o); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("Failed to deserialize: " + entry, e); + } + } + + return objectContainer; + } + } + + @Test + public void test() + throws IOException + { + ObjectContainer objectContainer = new ObjectContainer(new HashMap()); + { + A a = new A(); + a.setList(Arrays.asList("first", "second", "third")); + objectContainer.getObjects().put("a", a); + + B b = new B(); + b.setStr("hello world"); + objectContainer.getObjects().put("b", b); + + Double pi = 3.14; + objectContainer.getObjects().put("pi", pi); + } + + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + byte[] bytes = objectMapper.writeValueAsBytes(objectContainer); + ObjectContainer restored = objectMapper.readValue(bytes, ObjectContainer.class); + + { + assertEquals(3, restored.getObjects().size()); + A a = (A) restored.getObjects().get("a"); + assertArrayEquals(new String[] {"first", "second", "third"}, a.getList().toArray()); + B b = (B) restored.getObjects().get("b"); + assertEquals("hello world", b.getStr()); + assertEquals(3.14, restored.getObjects().get("pi")); + } + } +} diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java index a27bc27fa..5c1559770 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatForPojoTest.java @@ -1,3 +1,18 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.jackson.dataformat; import org.junit.Test; @@ -8,9 +23,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class MessagePackDataformatForPojoTest extends MessagePackDataformatTestBase { +public class MessagePackDataformatForPojoTest + extends MessagePackDataformatTestBase +{ @Test - public void testNormal() throws IOException { + public void testNormal() + throws IOException + { byte[] bytes = objectMapper.writeValueAsBytes(normalPojo); NormalPojo value = objectMapper.readValue(bytes, NormalPojo.class); assertEquals(normalPojo.s, value.getS()); @@ -25,7 +44,9 @@ public void testNormal() throws IOException { } @Test - public void testNestedList() throws IOException { + public void testNestedList() + throws IOException + { byte[] bytes = objectMapper.writeValueAsBytes(nestedListPojo); NestedListPojo value = objectMapper.readValue(bytes, NestedListPojo.class); assertEquals(nestedListPojo.s, value.s); @@ -33,7 +54,9 @@ public void testNestedList() throws IOException { } @Test - public void testNestedListComplex() throws IOException { + public void testNestedListComplex() + throws IOException + { byte[] bytes = objectMapper.writeValueAsBytes(nestedListComplexPojo); NestedListComplexPojo value = objectMapper.readValue(bytes, NestedListComplexPojo.class); assertEquals(nestedListPojo.s, value.s); @@ -41,7 +64,9 @@ public void testNestedListComplex() throws IOException { } @Test - public void testUsingCustomConstructor() throws IOException { + public void testUsingCustomConstructor() + throws IOException + { UsingCustomConstructorPojo orig = new UsingCustomConstructorPojo("komamitsu", 55); byte[] bytes = objectMapper.writeValueAsBytes(orig); UsingCustomConstructorPojo value = objectMapper.readValue(bytes, UsingCustomConstructorPojo.class); @@ -50,7 +75,9 @@ public void testUsingCustomConstructor() throws IOException { } @Test - public void testIgnoringProperties() throws IOException { + public void testIgnoringProperties() + throws IOException + { IgnoringPropertiesPojo orig = new IgnoringPropertiesPojo(); orig.internal = "internal"; orig.external = "external"; @@ -63,12 +90,13 @@ public void testIgnoringProperties() throws IOException { } @Test - public void testChangingPropertyNames() throws IOException { + public void testChangingPropertyNames() + throws IOException + { ChangingPropertyNamesPojo orig = new ChangingPropertyNamesPojo(); orig.setTheName("komamitsu"); byte[] bytes = objectMapper.writeValueAsBytes(orig); ChangingPropertyNamesPojo value = objectMapper.readValue(bytes, ChangingPropertyNamesPojo.class); assertEquals("komamitsu", value.getTheName()); } - } diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java index 27a5dfd12..1d5156adc 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackDataformatTestBase.java @@ -1,23 +1,40 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.jackson.dataformat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.math3.stat.StatUtils; -import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation; import org.junit.After; import org.junit.Before; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class MessagePackDataformatTestBase { +public class MessagePackDataformatTestBase +{ protected MessagePackFactory factory; protected ByteArrayOutputStream out; protected ByteArrayInputStream in; @@ -28,7 +45,8 @@ public class MessagePackDataformatTestBase { protected TinyPojo tinyPojo; @Before - public void setup() { + public void setup() + { factory = new MessagePackFactory(); objectMapper = new ObjectMapper(factory); out = new ByteArrayOutputStream(); @@ -58,11 +76,13 @@ public void setup() { } @After - public void teardown() { + public void teardown() + { if (in != null) { try { in.close(); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); } } @@ -70,41 +90,37 @@ public void teardown() { if (out != null) { try { out.close(); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); } } } - protected void printStat(String label, double[] values) { - StandardDeviation standardDeviation = new StandardDeviation(); - System.out.println(label + ":"); - System.out.println(String.format(" mean : %.2f", StatUtils.mean(values))); - System.out.println(String.format(" min : %.2f", StatUtils.min(values))); - System.out.println(String.format(" max : %.2f", StatUtils.max(values))); - System.out.println(String.format(" stdev: %.2f", standardDeviation.evaluate(values))); - System.out.println(""); - } - - public enum Suit { + public enum Suit + { SPADE, HEART, DIAMOND, CLUB; } - public static class NestedListPojo { + public static class NestedListPojo + { public String s; public List strs; } - public static class TinyPojo { + public static class TinyPojo + { public String t; } - public static class NestedListComplexPojo { + public static class NestedListComplexPojo + { public String s; public List foos; } - public static class NormalPojo { + public static class NormalPojo + { String s; public boolean bool; public int i; @@ -115,11 +131,13 @@ public static class NormalPojo { public BigInteger bi; public Suit suit; - public String getS() { + public String getS() + { return s; } - public void setS(String s) { + public void setS(String s) + { this.s = s; } } @@ -135,19 +153,21 @@ public UsingCustomConstructorPojo(@JsonProperty("name") String name, @JsonProper this.age = age; } - public String getName() { + public String getName() + { return name; } - public int getAge() { + public int getAge() + { return age; } } - @JsonIgnoreProperties({ "foo", "bar" }) + @JsonIgnoreProperties({"foo", "bar"}) public static class IgnoringPropertiesPojo { - int _code; + int code; // will not be written as JSON; nor assigned from JSON: @JsonIgnore @@ -157,22 +177,65 @@ public static class IgnoringPropertiesPojo public String external; @JsonIgnore - public void setCode(int c) { _code = c; } + public void setCode(int c) + { + code = c; + } // note: will also be ignored because setter has annotation! - public int getCode() { return _code; } + public int getCode() + { + return code; + } } - public static class ChangingPropertyNamesPojo { - String _name; + public static class ChangingPropertyNamesPojo + { + String name; // without annotation, we'd get "theName", but we want "name": @JsonProperty("name") - public String getTheName() { return _name; } + public String getTheName() + { + return name; + } // note: it is enough to add annotation on just getter OR setter; // so we can omit it here - public void setTheName(String n) { _name = n; } + public void setTheName(String n) + { + name = n; + } + } + + protected interface FileSetup + { + void setup(File f) + throws Exception; } + protected File createTempFile() + throws Exception + { + return createTempFile(null); + } + + protected File createTempFile(FileSetup fileSetup) + throws Exception + { + File tempFile = File.createTempFile("test", "msgpack"); + tempFile.deleteOnExit(); + if (fileSetup != null) { + fileSetup.setup(tempFile); + } + return tempFile; + } + + protected OutputStream createTempFileOutputStream() + throws IOException + { + File tempFile = File.createTempFile("test", "msgpack"); + tempFile.deleteOnExit(); + return new FileOutputStream(tempFile); + } } diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackFactoryTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackFactoryTest.java index 867229518..25180f784 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackFactoryTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackFactoryTest.java @@ -1,3 +1,18 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.jackson.dataformat; import com.fasterxml.jackson.core.JsonEncoding; @@ -9,16 +24,22 @@ import static org.junit.Assert.assertEquals; -public class MessagePackFactoryTest extends MessagePackDataformatTestBase { +public class MessagePackFactoryTest + extends MessagePackDataformatTestBase +{ @Test - public void testCreateGenerator() throws IOException { + public void testCreateGenerator() + throws IOException + { JsonEncoding enc = JsonEncoding.UTF8; JsonGenerator generator = factory.createGenerator(out, enc); assertEquals(MessagePackGenerator.class, generator.getClass()); } @Test - public void testCreateParser() throws IOException { + public void testCreateParser() + throws IOException + { JsonParser parser = factory.createParser(in); assertEquals(MessagePackParser.class, parser.getClass()); } diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java index 8126fa64a..fd2ea313f 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackGeneratorTest.java @@ -17,26 +17,44 @@ import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; +import org.msgpack.core.ExtensionTypeHeader; import org.msgpack.core.MessagePack; import org.msgpack.core.MessageUnpacker; import org.msgpack.core.buffer.ArrayBufferInput; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class MessagePackGeneratorTest extends MessagePackDataformatTestBase { +public class MessagePackGeneratorTest + extends MessagePackDataformatTestBase +{ @Test - public void testGeneratorShouldWriteObject() throws IOException { + public void testGeneratorShouldWriteObject() + throws IOException + { Map hashMap = new HashMap(); // #1 hashMap.put("str", "komamitsu"); @@ -51,7 +69,7 @@ public void testGeneratorShouldWriteObject() throws IOException { // #6 hashMap.put("double", 3.14159d); // #7 - hashMap.put("bin", new byte[]{0x00, 0x01, (byte)0xFE, (byte)0xFF}); + hashMap.put("bin", new byte[] {0x00, 0x01, (byte) 0xFE, (byte) 0xFF}); // #8 Map childObj = new HashMap(); childObj.put("co_str", "child#0"); @@ -62,10 +80,13 @@ public void testGeneratorShouldWriteObject() throws IOException { childArray.add("child#1"); childArray.add(1.23f); hashMap.put("childArray", childArray); + // #10 + byte[] hello = "hello".getBytes("UTF-8"); + hashMap.put("ext", new MessagePackExtensionType((byte) 17, hello)); long bitmap = 0; byte[] bytes = objectMapper.writeValueAsBytes(hashMap); - MessageUnpacker messageUnpacker = new MessageUnpacker(new ArrayBufferInput(bytes)); + MessageUnpacker messageUnpacker = MessagePack.newDefaultUnpacker(new ArrayBufferInput(bytes)); assertEquals(hashMap.size(), messageUnpacker.unpackMapHeader()); for (int i = 0; i < hashMap.size(); i++) { String key = messageUnpacker.unpackString(); @@ -101,11 +122,11 @@ else if (key.equals("double")) { } else if (key.equals("bin")) { // #7 - assertEquals(4, messageUnpacker.unpackBinaryHeader()); - assertEquals((byte)0x00, messageUnpacker.unpackByte()); - assertEquals((byte)0x01, messageUnpacker.unpackByte()); - assertEquals((byte)0xFE, messageUnpacker.unpackByte()); - assertEquals((byte)0xFF, messageUnpacker.unpackByte()); + assertEquals(4, messageUnpacker.unpackBinaryHeader()); + assertEquals((byte) 0x00, messageUnpacker.unpackByte()); + assertEquals((byte) 0x01, messageUnpacker.unpackByte()); + assertEquals((byte) 0xFE, messageUnpacker.unpackByte()); + assertEquals((byte) 0xFF, messageUnpacker.unpackByte()); bitmap |= 0x1 << 6; } else if (key.equals("childObj")) { @@ -133,15 +154,30 @@ else if (key.equals("childArray")) { assertEquals(1.23f, messageUnpacker.unpackFloat(), 0.01f); bitmap |= 0x1 << 9; } + else if (key.equals("ext")) { + // #10 + ExtensionTypeHeader header = messageUnpacker.unpackExtensionTypeHeader(); + assertEquals(17, header.getType()); + assertEquals(5, header.getLength()); + ByteBuffer payload = ByteBuffer.allocate(header.getLength()); + payload.flip(); + payload.limit(payload.capacity()); + messageUnpacker.readPayload(payload); + payload.flip(); + assertArrayEquals("hello".getBytes(), payload.array()); + bitmap |= 0x1 << 10; + } else { assertTrue(false); } } - assertEquals(0x03FF, bitmap); + assertEquals(0x07FF, bitmap); } @Test - public void testGeneratorShouldWriteArray() throws IOException { + public void testGeneratorShouldWriteArray() + throws IOException + { List array = new ArrayList(); // #1 array.add("komamitsu"); @@ -163,7 +199,7 @@ public void testGeneratorShouldWriteArray() throws IOException { long bitmap = 0; byte[] bytes = objectMapper.writeValueAsBytes(array); - MessageUnpacker messageUnpacker = new MessageUnpacker(new ArrayBufferInput(bytes)); + MessageUnpacker messageUnpacker = MessagePack.newDefaultUnpacker(new ArrayBufferInput(bytes)); assertEquals(array.size(), messageUnpacker.unpackArrayHeader()); // #1 assertEquals("komamitsu", messageUnpacker.unpackString()); @@ -197,10 +233,11 @@ else if (key.equals("num")) { } @Test - public void testMessagePackGeneratorDirectly() throws IOException { + public void testMessagePackGeneratorDirectly() + throws Exception + { MessagePackFactory messagePackFactory = new MessagePackFactory(); - File tempFile = File.createTempFile("msgpackTest", "msgpack"); - tempFile.deleteOnExit(); + File tempFile = createTempFile(); JsonGenerator generator = messagePackFactory.createGenerator(tempFile, JsonEncoding.UTF8); assertTrue(generator instanceof MessagePackGenerator); @@ -221,4 +258,181 @@ public void testMessagePackGeneratorDirectly() throws IOException { assertEquals(2.0f, unpacker.unpackFloat(), 0.001f); assertFalse(unpacker.hasNext()); } + + @Test + public void testWritePrimitives() + throws Exception + { + MessagePackFactory messagePackFactory = new MessagePackFactory(); + File tempFile = createTempFile(); + + JsonGenerator generator = messagePackFactory.createGenerator(tempFile, JsonEncoding.UTF8); + assertTrue(generator instanceof MessagePackGenerator); + generator.writeNumber(0); + generator.writeString("one"); + generator.writeNumber(2.0f); + generator.flush(); + generator.close(); + + FileInputStream fileInputStream = new FileInputStream(tempFile); + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(fileInputStream); + assertEquals(0, unpacker.unpackInt()); + assertEquals("one", unpacker.unpackString()); + assertEquals(2.0f, unpacker.unpackFloat(), 0.001f); + assertFalse(unpacker.hasNext()); + } + + @Test + public void testBigDecimal() + throws IOException + { + ObjectMapper mapper = new ObjectMapper(new MessagePackFactory()); + + { + double d0 = 1.23456789; + double d1 = 1.23450000000000000000006789; + String d2 = "12.30"; + List bigDecimals = Arrays.asList( + BigDecimal.valueOf(d0), + BigDecimal.valueOf(d1), + new BigDecimal(d2), + BigDecimal.valueOf(Double.MIN_VALUE), + BigDecimal.valueOf(Double.MAX_VALUE), + BigDecimal.valueOf(Double.MIN_NORMAL) + ); + + byte[] bytes = mapper.writeValueAsBytes(bigDecimals); + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes); + + assertEquals(bigDecimals.size(), unpacker.unpackArrayHeader()); + assertEquals(d0, unpacker.unpackDouble(), 0.000000000000001); + assertEquals(d1, unpacker.unpackDouble(), 0.000000000000001); + assertEquals(Double.valueOf(d2), unpacker.unpackDouble(), 0.000000000000001); + assertEquals(Double.MIN_VALUE, unpacker.unpackDouble(), 0.000000000000001); + assertEquals(Double.MAX_VALUE, unpacker.unpackDouble(), 0.000000000000001); + assertEquals(Double.MIN_NORMAL, unpacker.unpackDouble(), 0.000000000000001); + } + + { + BigDecimal decimal = new BigDecimal("1234.567890123456789012345678901234567890"); + List bigDecimals = Arrays.asList( + decimal + ); + + try { + mapper.writeValueAsBytes(bigDecimals); + assertTrue(false); + } + catch (IllegalArgumentException e) { + assertTrue(true); + } + } + } + + @Test(expected = IOException.class) + public void testEnableFeatureAutoCloseTarget() + throws IOException + { + OutputStream out = createTempFileOutputStream(); + MessagePackFactory messagePackFactory = new MessagePackFactory(); + ObjectMapper objectMapper = new ObjectMapper(messagePackFactory); + List integers = Arrays.asList(1); + objectMapper.writeValue(out, integers); + objectMapper.writeValue(out, integers); + } + + @Test + public void testDisableFeatureAutoCloseTarget() + throws Exception + { + File tempFile = createTempFile(); + OutputStream out = new FileOutputStream(tempFile); + MessagePackFactory messagePackFactory = new MessagePackFactory(); + ObjectMapper objectMapper = new ObjectMapper(messagePackFactory); + objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + List integers = Arrays.asList(1); + objectMapper.writeValue(out, integers); + objectMapper.writeValue(out, integers); + out.close(); + + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(new FileInputStream(tempFile)); + assertEquals(1, unpacker.unpackArrayHeader()); + assertEquals(1, unpacker.unpackInt()); + assertEquals(1, unpacker.unpackArrayHeader()); + assertEquals(1, unpacker.unpackInt()); + } + + @Test + public void testWritePrimitiveObjectViaObjectMapper() + throws Exception + { + File tempFile = createTempFile(); + OutputStream out = new FileOutputStream(tempFile); + + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + objectMapper.writeValue(out, 1); + objectMapper.writeValue(out, "two"); + objectMapper.writeValue(out, 3.14); + objectMapper.writeValue(out, Arrays.asList(4)); + objectMapper.writeValue(out, 5L); + + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(new FileInputStream(tempFile)); + assertEquals(1, unpacker.unpackInt()); + assertEquals("two", unpacker.unpackString()); + assertEquals(3.14, unpacker.unpackFloat(), 0.0001); + assertEquals(1, unpacker.unpackArrayHeader()); + assertEquals(4, unpacker.unpackInt()); + assertEquals(5, unpacker.unpackLong()); + } + + @Test + public void testInMultiThreads() + throws Exception + { + int threadCount = 8; + final int loopCount = 4000; + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + final List buffers = new ArrayList(threadCount); + List> results = new ArrayList>(); + + for (int ti = 0; ti < threadCount; ti++) { + buffers.add(new ByteArrayOutputStream()); + final int threadIndex = ti; + results.add(executorService.submit(new Callable() + { + @Override + public Exception call() + throws Exception + { + try { + for (int i = 0; i < loopCount; i++) { + objectMapper.writeValue(buffers.get(threadIndex), threadIndex); + } + return null; + } + catch (IOException e) { + return e; + } + } + })); + } + + for (int ti = 0; ti < threadCount; ti++) { + Future exceptionFuture = results.get(ti); + Exception exception = exceptionFuture.get(20, TimeUnit.SECONDS); + if (exception != null) { + throw exception; + } + else { + ByteArrayOutputStream outputStream = buffers.get(ti); + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(outputStream.toByteArray()); + for (int i = 0; i < loopCount; i++) { + assertEquals(ti, unpacker.unpackInt()); + } + } + } + } } diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackParserTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackParserTest.java index dfba2e25f..112390598 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackParserTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/MessagePackParserTest.java @@ -1,27 +1,60 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// package org.msgpack.jackson.dataformat; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; import org.junit.Test; import org.msgpack.core.MessagePack; import org.msgpack.core.MessagePacker; -import org.msgpack.core.buffer.OutputStreamBufferOutput; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -public class MessagePackParserTest extends MessagePackDataformatTestBase { +public class MessagePackParserTest + extends MessagePackDataformatTestBase +{ @Test - public void testParserShouldReadObject() throws IOException { - MessagePacker packer = new MessagePacker(new OutputStreamBufferOutput(out)); - packer.packMapHeader(8); + public void testParserShouldReadObject() + throws IOException + { + MessagePacker packer = MessagePack.newDefaultPacker(out); + packer.packMapHeader(9); // #1 packer.packString("str"); packer.packString("foobar"); @@ -58,14 +91,19 @@ public void testParserShouldReadObject() throws IOException { // #8 packer.packString("bool"); packer.packBoolean(false); + // #9 + byte[] extPayload = {-80, -50, -25, -114, -25, 16, 60, 68}; + packer.packString("ext"); + packer.packExtensionTypeHeader((byte) 0, extPayload.length); + packer.writePayload(extPayload); packer.flush(); byte[] bytes = out.toByteArray(); - TypeReference> typeReference = new TypeReference>(){}; + TypeReference> typeReference = new TypeReference>() {}; Map object = objectMapper.readValue(bytes, typeReference); - assertEquals(8, object.keySet().size()); + assertEquals(9, object.keySet().size()); int bitmap = 0; for (Map.Entry entry : object.entrySet()) { @@ -120,7 +158,7 @@ else if (k.equals("array")) { // #7 bitmap |= 1 << 8; @SuppressWarnings("unchecked") - List expected = Arrays.asList((double)Float.MIN_VALUE, null, "array_child_str"); + List expected = Arrays.asList((double) Float.MIN_VALUE, null, "array_child_str"); assertEquals(expected, v); } else if (k.equals("bool")) { @@ -128,14 +166,23 @@ else if (k.equals("bool")) { bitmap |= 1 << 9; assertEquals(false, v); } + else if (k.equals("ext")) { + // #9 + bitmap |= 1 << 10; + MessagePackExtensionType extensionType = (MessagePackExtensionType) v; + assertEquals(0, extensionType.getType()); + assertArrayEquals(extPayload, extensionType.getData()); + } } - assertEquals(0x3FF, bitmap); + assertEquals(0x7FF, bitmap); } @Test - public void testParserShouldReadArray() throws IOException { - MessagePacker packer = new MessagePacker(new OutputStreamBufferOutput(out)); - packer.packArrayHeader(10); + public void testParserShouldReadArray() + throws IOException + { + MessagePacker packer = MessagePack.newDefaultPacker(out); + packer.packArrayHeader(11); // #1 packer.packArrayHeader(3); { @@ -158,7 +205,7 @@ public void testParserShouldReadArray() throws IOException { bi = bi.add(BigInteger.ONE); packer.packBigInteger(bi); // #8 - byte[] bytes = new byte[]{(byte) 0xFF, (byte) 0xFE, 0x01, 0x00}; + byte[] bytes = new byte[] {(byte) 0xFF, (byte) 0xFE, 0x01, 0x00}; packer.packBinaryHeader(bytes.length); packer.writePayload(bytes); // #9 @@ -171,14 +218,18 @@ public void testParserShouldReadArray() throws IOException { } // #10 packer.packBoolean(true); + // #11 + byte[] extPayload = {-80, -50, -25, -114, -25, 16, 60, 68}; + packer.packExtensionTypeHeader((byte) -1, extPayload.length); + packer.writePayload(extPayload); packer.flush(); bytes = out.toByteArray(); - TypeReference> typeReference = new TypeReference>(){}; + TypeReference> typeReference = new TypeReference>() {}; List array = objectMapper.readValue(bytes, typeReference); - assertEquals(10, array.size()); + assertEquals(11, array.size()); int i = 0; // #1 @SuppressWarnings("unchecked") @@ -196,18 +247,18 @@ public void testParserShouldReadArray() throws IOException { // #4 assertEquals(Long.MIN_VALUE, array.get(i++)); // #5 - assertEquals(Float.MAX_VALUE, (Double)array.get(i++), 0.001f); + assertEquals(Float.MAX_VALUE, (Double) array.get(i++), 0.001f); // #6 - assertEquals(Double.MIN_VALUE, (Double)array.get(i++), 0.001f); + assertEquals(Double.MIN_VALUE, (Double) array.get(i++), 0.001f); // #7 assertEquals(bi, array.get(i++)); // #8 byte[] bs = (byte[]) array.get(i++); assertEquals(4, bs.length); - assertEquals((byte)0xFF, bs[0]); - assertEquals((byte)0xFE, bs[1]); - assertEquals((byte)0x01, bs[2]); - assertEquals((byte)0x00, bs[3]); + assertEquals((byte) 0xFF, bs[0]); + assertEquals((byte) 0xFE, bs[1]); + assertEquals((byte) 0x01, bs[2]); + assertEquals((byte) 0x00, bs[3]); // #9 @SuppressWarnings("unchecked") Map childMap = (Map) array.get(i++); @@ -226,11 +277,17 @@ else if (k.equals("child_map_age")) { } // #10 assertEquals(true, array.get(i++)); + // #11 + MessagePackExtensionType extensionType = (MessagePackExtensionType) array.get(i++); + assertEquals(-1, extensionType.getType()); + assertArrayEquals(extPayload, extensionType.getData()); } @Test - public void testMessagePackParserDirectly() throws IOException { - MessagePackFactory messagePackFactory = new MessagePackFactory(); + public void testMessagePackParserDirectly() + throws IOException + { + MessagePackFactory factory = new MessagePackFactory(); File tempFile = File.createTempFile("msgpackTest", "msgpack"); tempFile.deleteOnExit(); @@ -243,7 +300,7 @@ public void testMessagePackParserDirectly() throws IOException { packer.packFloat(1.0f); packer.close(); - JsonParser parser = messagePackFactory.createParser(tempFile); + JsonParser parser = factory.createParser(tempFile); assertTrue(parser instanceof MessagePackParser); JsonToken jsonToken = parser.nextToken(); @@ -286,14 +343,332 @@ public void testMessagePackParserDirectly() throws IOException { assertEquals(-1, parser.getCurrentLocation().getLineNr()); assertEquals(16, parser.getCurrentLocation().getColumnNr()); - try { - parser.nextToken(); - assertTrue(false); - } - catch (EOFException e) { - // Expected - } + assertNull(parser.nextToken()); + parser.close(); parser.close(); // Intentional } + + @Test + public void testReadPrimitives() + throws Exception + { + MessagePackFactory factory = new MessagePackFactory(); + File tempFile = createTempFile(); + + FileOutputStream out = new FileOutputStream(tempFile); + MessagePacker packer = MessagePack.newDefaultPacker(out); + packer.packString("foo"); + packer.packDouble(3.14); + packer.packLong(Long.MAX_VALUE); + byte[] bytes = {0x00, 0x11, 0x22}; + packer.packBinaryHeader(bytes.length); + packer.writePayload(bytes); + packer.close(); + + JsonParser parser = factory.createParser(new FileInputStream(tempFile)); + assertEquals(JsonToken.VALUE_STRING, parser.nextToken()); + assertEquals("foo", parser.getText()); + assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken()); + assertEquals(3.14, parser.getDoubleValue(), 0.0001); + assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextToken()); + assertEquals(Long.MAX_VALUE, parser.getLongValue()); + assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, parser.nextToken()); + assertEquals(bytes.length, parser.getBinaryValue().length); + assertEquals(bytes[0], parser.getBinaryValue()[0]); + assertEquals(bytes[1], parser.getBinaryValue()[1]); + assertEquals(bytes[2], parser.getBinaryValue()[2]); + } + + @Test + public void testBigDecimal() + throws IOException + { + double d0 = 1.23456789; + double d1 = 1.23450000000000000000006789; + MessagePacker packer = MessagePack.newDefaultPacker(out); + packer.packArrayHeader(5); + packer.packDouble(d0); + packer.packDouble(d1); + packer.packDouble(Double.MIN_VALUE); + packer.packDouble(Double.MAX_VALUE); + packer.packDouble(Double.MIN_NORMAL); + packer.flush(); + + ObjectMapper mapper = new ObjectMapper(new MessagePackFactory()); + mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + List objects = mapper.readValue(out.toByteArray(), new TypeReference>() {}); + assertEquals(5, objects.size()); + int idx = 0; + assertEquals(BigDecimal.valueOf(d0), objects.get(idx++)); + assertEquals(BigDecimal.valueOf(d1), objects.get(idx++)); + assertEquals(BigDecimal.valueOf(Double.MIN_VALUE), objects.get(idx++)); + assertEquals(BigDecimal.valueOf(Double.MAX_VALUE), objects.get(idx++)); + assertEquals(BigDecimal.valueOf(Double.MIN_NORMAL), objects.get(idx++)); + } + + private File createTestFile() + throws Exception + { + File tempFile = createTempFile(new FileSetup() + { + @Override + public void setup(File f) + throws IOException + { + MessagePack.newDefaultPacker(new FileOutputStream(f)) + .packArrayHeader(1).packInt(1) + .packArrayHeader(1).packInt(1) + .close(); + } + }); + return tempFile; + } + + @Test(expected = IOException.class) + public void testEnableFeatureAutoCloseSource() + throws Exception + { + File tempFile = createTestFile(); + MessagePackFactory factory = new MessagePackFactory(); + FileInputStream in = new FileInputStream(tempFile); + ObjectMapper objectMapper = new ObjectMapper(factory); + objectMapper.readValue(in, new TypeReference>() {}); + objectMapper.readValue(in, new TypeReference>() {}); + } + + @Test + public void testDisableFeatureAutoCloseSource() + throws Exception + { + File tempFile = createTestFile(); + FileInputStream in = new FileInputStream(tempFile); + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false); + objectMapper.readValue(in, new TypeReference>() {}); + objectMapper.readValue(in, new TypeReference>() {}); + } + + @Test + public void testParseBigDecimal() + throws IOException + { + ArrayList list = new ArrayList(); + list.add(new BigDecimal(Long.MAX_VALUE)); + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + byte[] bytes = objectMapper.writeValueAsBytes(list); + + ArrayList result = objectMapper.readValue( + bytes, new TypeReference>() {}); + assertEquals(list, result); + } + + @Test + public void testReadPrimitiveObjectViaObjectMapper() + throws Exception + { + File tempFile = createTempFile(); + FileOutputStream out = new FileOutputStream(tempFile); + + MessagePacker packer = MessagePack.newDefaultPacker(out); + packer.packString("foo"); + packer.packLong(Long.MAX_VALUE); + packer.packDouble(3.14); + byte[] bytes = {0x00, 0x11, 0x22}; + packer.packBinaryHeader(bytes.length); + packer.writePayload(bytes); + packer.close(); + + FileInputStream in = new FileInputStream(tempFile); + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false); + assertEquals("foo", objectMapper.readValue(in, new TypeReference() {})); + long l = objectMapper.readValue(in, new TypeReference() {}); + assertEquals(Long.MAX_VALUE, l); + double d = objectMapper.readValue(in, new TypeReference() {}); + assertEquals(3.14, d, 0.001); + byte[] bs = objectMapper.readValue(in, new TypeReference() {}); + assertEquals(bytes.length, bs.length); + assertEquals(bytes[0], bs[0]); + assertEquals(bytes[1], bs[1]); + assertEquals(bytes[2], bs[2]); + } + + @Test + public void testBinaryKey() + throws Exception + { + File tempFile = createTempFile(); + FileOutputStream out = new FileOutputStream(tempFile); + MessagePacker packer = MessagePack.newDefaultPacker(out); + packer.packMapHeader(2); + packer.packString("foo"); + packer.packDouble(3.14); + byte[] bytes = "bar".getBytes(); + packer.packBinaryHeader(bytes.length); + packer.writePayload(bytes); + packer.packLong(42); + packer.close(); + + ObjectMapper mapper = new ObjectMapper(new MessagePackFactory()); + Map object = mapper.readValue(new FileInputStream(tempFile), new TypeReference>() {}); + assertEquals(2, object.size()); + assertEquals(3.14, object.get("foo")); + assertEquals(42, object.get("bar")); + } + + @Test + public void testBinaryKeyInNestedObject() + throws Exception + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + packer.packArrayHeader(2); + packer.packMapHeader(1); + byte[] bytes = "bar".getBytes(); + packer.packBinaryHeader(bytes.length); + packer.writePayload(bytes); + packer.packInt(12); + packer.packInt(1); + packer.close(); + + ObjectMapper mapper = new ObjectMapper(new MessagePackFactory()); + List objects = mapper.readValue(out.toByteArray(), new TypeReference>() {}); + assertEquals(2, objects.size()); + @SuppressWarnings(value = "unchecked") + Map map = (Map) objects.get(0); + assertEquals(1, map.size()); + assertEquals(12, map.get("bar")); + assertEquals(1, objects.get(1)); + } + + @Test + public void testByteArrayKey() + throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker messagePacker = MessagePack.newDefaultPacker(out).packMapHeader(2); + byte[] k0 = new byte[] {0}; + byte[] k1 = new byte[] {1}; + messagePacker.packBinaryHeader(1).writePayload(k0).packInt(2); + messagePacker.packBinaryHeader(1).writePayload(k1).packInt(3); + messagePacker.close(); + + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + SimpleModule module = new SimpleModule(); + module.addKeyDeserializer(byte[].class, new KeyDeserializer() + { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return key.getBytes(); + } + }); + objectMapper.registerModule(module); + + Map map = objectMapper.readValue( + out.toByteArray(), new TypeReference>() {}); + assertEquals(2, map.size()); + for (Map.Entry entry : map.entrySet()) { + if (Arrays.equals(entry.getKey(), k0)) { + assertEquals((Integer) 2, entry.getValue()); + } + else if (Arrays.equals(entry.getKey(), k1)) { + assertEquals((Integer) 3, entry.getValue()); + } + } + } + + @Test + public void testIntegerKey() + throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker messagePacker = MessagePack.newDefaultPacker(out).packMapHeader(3); + for (int i = 0; i < 2; i++) { + messagePacker.packInt(i).packInt(i + 2); + } + messagePacker.close(); + + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + SimpleModule module = new SimpleModule(); + module.addKeyDeserializer(Integer.class, new KeyDeserializer() + { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return Integer.valueOf(key); + } + }); + objectMapper.registerModule(module); + + Map map = objectMapper.readValue( + out.toByteArray(), new TypeReference>() {}); + assertEquals(2, map.size()); + assertEquals((Integer) 2, map.get(0)); + assertEquals((Integer) 3, map.get(1)); + } + + @Test + public void testFloatKey() + throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker messagePacker = MessagePack.newDefaultPacker(out).packMapHeader(3); + for (int i = 0; i < 2; i++) { + messagePacker.packFloat(i).packInt(i + 2); + } + messagePacker.close(); + + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + SimpleModule module = new SimpleModule(); + module.addKeyDeserializer(Float.class, new KeyDeserializer() + { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return Float.valueOf(key); + } + }); + objectMapper.registerModule(module); + + Map map = objectMapper.readValue( + out.toByteArray(), new TypeReference>() {}); + assertEquals(2, map.size()); + assertEquals((Integer) 2, map.get(0f)); + assertEquals((Integer) 3, map.get(1f)); + } + + @Test + public void testBooleanKey() + throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker messagePacker = MessagePack.newDefaultPacker(out).packMapHeader(3); + messagePacker.packBoolean(true).packInt(2); + messagePacker.packBoolean(false).packInt(3); + messagePacker.close(); + + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + SimpleModule module = new SimpleModule(); + module.addKeyDeserializer(Boolean.class, new KeyDeserializer() + { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) + throws IOException, JsonProcessingException + { + return Boolean.valueOf(key); + } + }); + objectMapper.registerModule(module); + + Map map = objectMapper.readValue( + out.toByteArray(), new TypeReference>() {}); + assertEquals(2, map.size()); + assertEquals((Integer) 2, map.get(true)); + assertEquals((Integer) 3, map.get(false)); + } } diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/Benchmarker.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/Benchmarker.java new file mode 100644 index 000000000..980348024 --- /dev/null +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/Benchmarker.java @@ -0,0 +1,98 @@ +// +// MessagePack for Java +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package org.msgpack.jackson.dataformat.benchmark; + +import org.apache.commons.math3.stat.StatUtils; +import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Benchmarker +{ + private final List benchmarkableList = new ArrayList(); + + public abstract static class Benchmarkable + { + private final String label; + + protected Benchmarkable(String label) + { + this.label = label; + } + + public abstract void run() throws Exception; + } + + public void addBenchmark(Benchmarkable benchmark) + { + benchmarkableList.add(benchmark); + } + + private static class Tuple + { + F first; + S second; + + public Tuple(F first, S second) + { + this.first = first; + this.second = second; + } + } + + public void run(int count, int warmupCount) + throws Exception + { + List> benchmarksResults = new ArrayList>(benchmarkableList.size()); + for (Benchmarkable benchmark : benchmarkableList) { + benchmarksResults.add(new Tuple(benchmark.label, new double[count])); + } + + for (int i = 0; i < count + warmupCount; i++) { + for (int bi = 0; bi < benchmarkableList.size(); bi++) { + Benchmarkable benchmark = benchmarkableList.get(bi); + long currentTimeNanos = System.nanoTime(); + benchmark.run(); + + if (i >= warmupCount) { + benchmarksResults.get(bi).second[i - warmupCount] = (System.nanoTime() - currentTimeNanos) / 1000000.0; + } + } + } + + for (Tuple benchmarkResult : benchmarksResults) { + printStat(benchmarkResult.first, benchmarkResult.second); + } + } + + private void printStat(String label, double[] origValues) + { + double[] values = origValues; + Arrays.sort(origValues); + if (origValues.length > 2) { + values = Arrays.copyOfRange(origValues, 1, origValues.length - 1); + } + StandardDeviation standardDeviation = new StandardDeviation(); + System.out.println(label + ":"); + System.out.println(String.format(" mean : %8.3f", StatUtils.mean(values))); + System.out.println(String.format(" min : %8.3f", StatUtils.min(values))); + System.out.println(String.format(" max : %8.3f", StatUtils.max(values))); + System.out.println(String.format(" stdev: %8.3f", standardDeviation.evaluate(values))); + System.out.println(""); + } +} diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatHugeDataBenchmarkTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatHugeDataBenchmarkTest.java index 504651456..b3a159111 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatHugeDataBenchmarkTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatHugeDataBenchmarkTest.java @@ -15,31 +15,37 @@ // package org.msgpack.jackson.dataformat.benchmark; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; -import org.msgpack.jackson.dataformat.MessagePackDataformatTestBase; import org.msgpack.jackson.dataformat.MessagePackFactory; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; -public class MessagePackDataformatHugeDataBenchmarkTest extends MessagePackDataformatTestBase { - private static final int ELM_NUM = 1000000; - private static final int SAMPLING_COUNT = 4; +public class MessagePackDataformatHugeDataBenchmarkTest +{ + private static final int ELM_NUM = 100000; + private static final int COUNT = 6; + private static final int WARMUP_COUNT = 4; private final ObjectMapper origObjectMapper = new ObjectMapper(); private final ObjectMapper msgpackObjectMapper = new ObjectMapper(new MessagePackFactory()); private static final List value; private static final byte[] packedByOriginal; private static final byte[] packedByMsgPack; + static { value = new ArrayList(); for (int i = 0; i < ELM_NUM; i++) { - value.add((long)i); + value.add((long) i); } for (int i = 0; i < ELM_NUM; i++) { - value.add((double)i); + value.add((double) i); } for (int i = 0; i < ELM_NUM; i++) { value.add(String.valueOf(i)); @@ -48,45 +54,83 @@ public class MessagePackDataformatHugeDataBenchmarkTest extends MessagePackDataf byte[] bytes = null; try { bytes = new ObjectMapper().writeValueAsBytes(value); - } catch (JsonProcessingException e) { + } + catch (JsonProcessingException e) { e.printStackTrace(); } packedByOriginal = bytes; try { bytes = new ObjectMapper(new MessagePackFactory()).writeValueAsBytes(value); - } catch (JsonProcessingException e) { + } + catch (JsonProcessingException e) { e.printStackTrace(); } packedByMsgPack = bytes; } + public MessagePackDataformatHugeDataBenchmarkTest() + { + origObjectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + msgpackObjectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + } + @Test - public void testBenchmark() throws Exception { - double durationOfSerializeWithJson[] = new double[SAMPLING_COUNT]; - double durationOfSerializeWithMsgPack[] = new double[SAMPLING_COUNT]; - double durationOfDeserializeWithJson[] = new double[SAMPLING_COUNT]; - double durationOfDeserializeWithMsgPack[] = new double[SAMPLING_COUNT]; - for (int si = 0; si < SAMPLING_COUNT; si++) { - long currentTimeMillis = System.currentTimeMillis(); - origObjectMapper.writeValueAsBytes(value); - durationOfSerializeWithJson[si] = System.currentTimeMillis() - currentTimeMillis; + public void testBenchmark() + throws Exception + { + Benchmarker benchmarker = new Benchmarker(); + + File tempFileJackson = File.createTempFile("msgpack-jackson-", "-huge-jackson"); + tempFileJackson.deleteOnExit(); + final OutputStream outputStreamJackson = new FileOutputStream(tempFileJackson); - currentTimeMillis = System.currentTimeMillis(); - msgpackObjectMapper.writeValueAsBytes(value); - durationOfSerializeWithMsgPack[si] = System.currentTimeMillis() - currentTimeMillis; + File tempFileMsgpack = File.createTempFile("msgpack-jackson-", "-huge-msgpack"); + tempFileMsgpack.deleteOnExit(); + final OutputStream outputStreamMsgpack = new FileOutputStream(tempFileMsgpack); - currentTimeMillis = System.currentTimeMillis(); - origObjectMapper.readValue(packedByOriginal, new TypeReference>() {}); - durationOfDeserializeWithJson[si] = System.currentTimeMillis() - currentTimeMillis; + benchmarker.addBenchmark(new Benchmarker.Benchmarkable("serialize(huge) with JSON") { + @Override + public void run() + throws Exception + { + origObjectMapper.writeValue(outputStreamJackson, value); + } + }); - currentTimeMillis = System.currentTimeMillis(); - msgpackObjectMapper.readValue(packedByMsgPack, new TypeReference>() {}); - durationOfDeserializeWithMsgPack[si] = System.currentTimeMillis() - currentTimeMillis; + benchmarker.addBenchmark(new Benchmarker.Benchmarkable("serialize(huge) with MessagePack") { + @Override + public void run() + throws Exception + { + msgpackObjectMapper.writeValue(outputStreamMsgpack, value); + } + }); + + benchmarker.addBenchmark(new Benchmarker.Benchmarkable("deserialize(huge) with JSON") { + @Override + public void run() + throws Exception + { + origObjectMapper.readValue(packedByOriginal, new TypeReference>() {}); + } + }); + + benchmarker.addBenchmark(new Benchmarker.Benchmarkable("deserialize(huge) with MessagePack") { + @Override + public void run() + throws Exception + { + msgpackObjectMapper.readValue(packedByMsgPack, new TypeReference>() {}); + } + }); + + try { + benchmarker.run(COUNT, WARMUP_COUNT); + } + finally { + outputStreamJackson.close(); + outputStreamMsgpack.close(); } - printStat("serialize(huge) with JSON", durationOfSerializeWithJson); - printStat("serialize(huge) with MessagePack", durationOfSerializeWithMsgPack); - printStat("deserialize(huge) with JSON", durationOfDeserializeWithJson); - printStat("deserialize(huge) with MessagePack", durationOfDeserializeWithMsgPack); } } diff --git a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java index d7dedc43e..179b09891 100644 --- a/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java +++ b/msgpack-jackson/src/test/java/org/msgpack/jackson/dataformat/benchmark/MessagePackDataformatPojoBenchmarkTest.java @@ -15,29 +15,38 @@ // package org.msgpack.jackson.dataformat.benchmark; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; -import org.msgpack.jackson.dataformat.MessagePackDataformatTestBase; import org.msgpack.jackson.dataformat.MessagePackFactory; +import static org.msgpack.jackson.dataformat.MessagePackDataformatTestBase.NormalPojo; +import static org.msgpack.jackson.dataformat.MessagePackDataformatTestBase.Suit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -public class MessagePackDataformatPojoBenchmarkTest extends MessagePackDataformatTestBase { - private static final int LOOP_MAX = 1000; - private static final int LOOP_FACTOR = 50; - private static final int SAMPLING_COUNT = 4; - private static final List pojos = new ArrayList(LOOP_MAX); - private static final List pojosSerWithOrig = new ArrayList(LOOP_MAX); - private static final List pojosSerWithMsgPack = new ArrayList(LOOP_MAX); +public class MessagePackDataformatPojoBenchmarkTest +{ + private static final int LOOP_MAX = 200; + private static final int LOOP_FACTOR_SER = 40; + private static final int LOOP_FACTOR_DESER = 200; + private static final int COUNT = 6; + private static final int WARMUP_COUNT = 4; + private final List pojos = new ArrayList(LOOP_MAX); + private final List pojosSerWithOrig = new ArrayList(LOOP_MAX); + private final List pojosSerWithMsgPack = new ArrayList(LOOP_MAX); private final ObjectMapper origObjectMapper = new ObjectMapper(); private final ObjectMapper msgpackObjectMapper = new ObjectMapper(new MessagePackFactory()); - static { - final ObjectMapper origObjectMapper = new ObjectMapper(); - final ObjectMapper msgpackObjectMapper = new ObjectMapper(new MessagePackFactory()); + public MessagePackDataformatPojoBenchmarkTest() + { + origObjectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); + msgpackObjectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); for (int i = 0; i < LOOP_MAX; i++) { NormalPojo pojo = new NormalPojo(); @@ -45,7 +54,11 @@ public class MessagePackDataformatPojoBenchmarkTest extends MessagePackDataforma pojo.l = i; pojo.f = Float.valueOf(i); pojo.d = Double.valueOf(i); - pojo.setS(String.valueOf(i)); + StringBuilder sb = new StringBuilder(); + for (int sbi = 0; sbi < i * 50; sbi++) { + sb.append("x"); + } + pojo.setS(sb.toString()); pojo.bool = i % 2 == 0; pojo.bi = BigInteger.valueOf(i); switch (i % 4) { @@ -69,58 +82,94 @@ public class MessagePackDataformatPojoBenchmarkTest extends MessagePackDataforma for (int i = 0; i < LOOP_MAX; i++) { try { pojosSerWithOrig.add(origObjectMapper.writeValueAsBytes(pojos.get(i))); - } catch (JsonProcessingException e) { - e.printStackTrace(); + } + catch (JsonProcessingException e) { + throw new RuntimeException("Failed to create test data"); } } for (int i = 0; i < LOOP_MAX; i++) { try { pojosSerWithMsgPack.add(msgpackObjectMapper.writeValueAsBytes(pojos.get(i))); - } catch (JsonProcessingException e) { - e.printStackTrace(); + } + catch (JsonProcessingException e) { + throw new RuntimeException("Failed to create test data"); } } } @Test - public void testBenchmark() throws Exception { - double durationOfSerializeWithJson[] = new double[SAMPLING_COUNT]; - double durationOfSerializeWithMsgPack[] = new double[SAMPLING_COUNT]; - double durationOfDeserializeWithJson[] = new double[SAMPLING_COUNT]; - double durationOfDeserializeWithMsgPack[] = new double[SAMPLING_COUNT]; - for (int si = 0; si < SAMPLING_COUNT; si++) { - long currentTimeMillis = System.currentTimeMillis(); - for (int j = 0; j < LOOP_FACTOR; j++) - for (int i = 0; i < LOOP_MAX; i++) { - origObjectMapper.writeValueAsBytes(pojos.get(i)); + public void testBenchmark() + throws Exception + { + Benchmarker benchmarker = new Benchmarker(); + + File tempFileJackson = File.createTempFile("msgpack-jackson-", "-huge-jackson"); + tempFileJackson.deleteOnExit(); + final OutputStream outputStreamJackson = new FileOutputStream(tempFileJackson); + + File tempFileMsgpack = File.createTempFile("msgpack-jackson-", "-huge-msgpack"); + tempFileMsgpack.deleteOnExit(); + final OutputStream outputStreamMsgpack = new FileOutputStream(tempFileMsgpack); + + benchmarker.addBenchmark(new Benchmarker.Benchmarkable("serialize(pojo) with JSON") { + @Override + public void run() + throws Exception + { + for (int j = 0; j < LOOP_FACTOR_SER; j++) { + for (int i = 0; i < LOOP_MAX; i++) { + origObjectMapper.writeValue(outputStreamJackson, pojos.get(i)); + } } - durationOfSerializeWithJson[si] = System.currentTimeMillis() - currentTimeMillis; + } + }); - currentTimeMillis = System.currentTimeMillis(); - for (int j = 0; j < LOOP_FACTOR; j++) - for (int i = 0; i < LOOP_MAX; i++) { - msgpackObjectMapper.writeValueAsBytes(pojos.get(i)); + benchmarker.addBenchmark(new Benchmarker.Benchmarkable("serialize(pojo) with MessagePack") { + @Override + public void run() + throws Exception + { + for (int j = 0; j < LOOP_FACTOR_SER; j++) { + for (int i = 0; i < LOOP_MAX; i++) { + msgpackObjectMapper.writeValue(outputStreamMsgpack, pojos.get(i)); + } } - durationOfSerializeWithMsgPack[si] = System.currentTimeMillis() - currentTimeMillis; + } + }); - currentTimeMillis = System.currentTimeMillis(); - for (int j = 0; j < LOOP_FACTOR; j++) - for (int i = 0; i < LOOP_MAX; i++) { - origObjectMapper.readValue(pojosSerWithOrig.get(i), NormalPojo.class); + benchmarker.addBenchmark(new Benchmarker.Benchmarkable("deserialize(pojo) with JSON") { + @Override + public void run() + throws Exception + { + for (int j = 0; j < LOOP_FACTOR_DESER; j++) { + for (int i = 0; i < LOOP_MAX; i++) { + origObjectMapper.readValue(pojosSerWithOrig.get(i), NormalPojo.class); + } } - durationOfDeserializeWithJson[si] = System.currentTimeMillis() - currentTimeMillis; + } + }); - currentTimeMillis = System.currentTimeMillis(); - for (int j = 0; j < LOOP_FACTOR; j++) - for (int i = 0; i < LOOP_MAX; i++) { - msgpackObjectMapper.readValue(pojosSerWithMsgPack.get(i), NormalPojo.class); + benchmarker.addBenchmark(new Benchmarker.Benchmarkable("deserialize(pojo) with MessagePack") { + @Override + public void run() + throws Exception + { + for (int j = 0; j < LOOP_FACTOR_DESER; j++) { + for (int i = 0; i < LOOP_MAX; i++) { + msgpackObjectMapper.readValue(pojosSerWithMsgPack.get(i), NormalPojo.class); + } } - durationOfDeserializeWithMsgPack[si] = System.currentTimeMillis() - currentTimeMillis; + } + }); + + try { + benchmarker.run(COUNT, WARMUP_COUNT); + } + finally { + outputStreamJackson.close(); + outputStreamMsgpack.close(); } - printStat("serialize(pojo) with JSON", durationOfSerializeWithJson); - printStat("serialize(pojo) with MessagePack", durationOfSerializeWithMsgPack); - printStat("deserialize(pojo) with JSON", durationOfDeserializeWithJson); - printStat("deserialize(pojo) with MessagePack", durationOfDeserializeWithMsgPack); } } diff --git a/project/Build.scala b/project/Build.scala deleted file mode 100644 index fa800c3d7..000000000 --- a/project/Build.scala +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2012 Taro L. Saito - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -import de.johoop.findbugs4sbt.ReportType -import sbt._ -import Keys._ -import de.johoop.findbugs4sbt.FindBugs._ -import de.johoop.jacoco4sbt._ -import JacocoPlugin._ -import sbtrelease.ReleasePlugin._ -import scala.util.Properties -import com.typesafe.sbt.pgp.PgpKeys - -object Build extends Build { - - val SCALA_VERSION = "2.11.1" - - lazy val buildSettings = Defaults.coreDefaultSettings ++ - releaseSettings ++ - findbugsSettings ++ - jacoco.settings ++ - Seq[Setting[_]]( - organization := "org.msgpack", - organizationName := "MessagePack", - organizationHomepage := Some(new URL("http://msgpack.org/")), - description := "MessagePack for Java", - scalaVersion in Global := SCALA_VERSION, - ReleaseKeys.publishArtifactsAction := PgpKeys.publishSigned.value, - logBuffered in Test := false, - //parallelExecution in Test := false, - autoScalaLibrary := false, - crossPaths := false, - concurrentRestrictions in Global := Seq( - Tags.limit(Tags.Test, 1) - ), - ReleaseKeys.tagName <<= (version in ThisBuild) map (v => v), - publishTo := { - val nexus = "https://oss.sonatype.org/" - if (isSnapshot.value) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") - }, - parallelExecution in jacoco.Config := false, - // Since sbt-0.13.2 - incOptions := incOptions.value.withNameHashing(true), - //resolvers += Resolver.mavenLocal, - scalacOptions ++= Seq("-encoding", "UTF-8", "-deprecation", "-unchecked", "-target:jvm-1.6", "-feature"), - javaOptions in Test ++= Seq("-ea"), - javacOptions in (Compile, compile) ++= Seq("-encoding", "UTF-8", "-Xlint:unchecked", "-Xlint:deprecation", "-source", "1.6", "-target", "1.6"), - javacOptions in doc := { - val opts = Seq("-source", "1.6") - if (Properties.isJavaAtLeast("1.8")) - opts ++ Seq("-Xdoclint:none") - else - opts - }, - findbugsReportType := Some(ReportType.FancyHtml), - findbugsReportPath := Some(crossTarget.value / "findbugs" / "report.html"), - pomExtra := { - http://msgpack.org/ - - - Apache 2 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - scm:git:github.com/msgpack/msgpack-java.git - scm:git:git@github.com:msgpack/msgpack-java.git - github.com/msgpack/msgpack-java.git - - - UTF-8 - - - - frsyuki - Sadayuki Furuhashi - frsyuki@users.sourceforge.jp - - - muga - Muga Nishizawa - muga.nishizawa@gmail.com - - - oza - Tsuyoshi Ozawa - https://github.com/oza - - - komamitsu - Mitsunori Komatsu - komamitsu@gmail.com - - - xerial - Taro L. Saito - leo@xerial.org - - - } - ) - - import Dependencies._ - - - lazy val root = Project( - id = "msgpack-java", - base = file("."), - settings = buildSettings ++ Seq( - findbugs := { - // do not run findbugs for the root project - }, - // Do not publish the root project - publishArtifact := false, - publish := {}, - publishLocal := {} - ) - ) aggregate(msgpackCore, msgpackJackson) - - - lazy val msgpackCore = Project( - id = "msgpack-core", - base = file("msgpack-core"), - settings = buildSettings ++ Seq( - description := "Core library of the MessagePack for Java", - libraryDependencies ++= testLib - ) - ) - - lazy val msgpackJackson = Project( - id = "msgpack-jackson", - base = file("msgpack-jackson"), - settings = buildSettings ++ Seq( - name := "jackson-dataformat-msgpack", - description := "Jackson extension that adds support for MessagePack", - libraryDependencies ++= jacksonLib, - testOptions += Tests.Argument(TestFrameworks.JUnit, "-v") - ) - ).dependsOn(msgpackCore) - - object Dependencies { - - val testLib = Seq( - "org.scalatest" % "scalatest_2.11" % "2.2.0" % "test", - "org.scalacheck" % "scalacheck_2.11" % "1.11.4" % "test", - "org.xerial" % "xerial-core" % "3.3.0" % "test", - "org.msgpack" % "msgpack" % "0.6.9" % "test", - "com.novocode" % "junit-interface" % "0.10" % "test", - "commons-codec" % "commons-codec" % "1.9" % "test" - ) - - val jacksonLib = Seq( - "com.fasterxml.jackson.core" % "jackson-databind" % "2.4.4", - "com.novocode" % "junit-interface" % "0.10" % "test", - "org.apache.commons" % "commons-math3" % "3.3" % "test" - ) - } - -} - - - - - - - - diff --git a/project/build.properties b/project/build.properties index 005786e4d..02cb92b32 100755 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ -sbt.version=0.13.6 +sbt.version=0.13.9 diff --git a/project/plugins.sbt b/project/plugins.sbt index 2ce2dad4c..be8535f05 100755 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,14 +1,14 @@ -addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8.5") +addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.2.1") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3") - -addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("de.johoop" % "findbugs4sbt" % "1.3.0") -addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.5") +addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.6") + +addSbtPlugin("org.xerial.sbt" % "sbt-jcheckstyle" % "0.1.2") scalacOptions ++= Seq("-deprecation", "-feature") diff --git a/sbt b/sbt index 3721584c9..c475bc64b 100755 --- a/sbt +++ b/sbt @@ -1,36 +1,27 @@ #!/usr/bin/env bash # # A more capable sbt runner, coincidentally also called sbt. -# Author: Paul Phillips +# Author: Paul Phillips + +set -o pipefail # todo - make this dynamic -declare -r sbt_release_version="0.13.1" -declare -r sbt_unreleased_version="0.13.2-SNAPSHOT" # -sbt-dev doesn't work at present +declare -r sbt_release_version="0.13.9" +declare -r sbt_unreleased_version="0.13.9" declare -r buildProps="project/build.properties" -declare sbt_jar sbt_dir sbt_create sbt_launch_dir -declare scala_version java_home sbt_explicit_version -declare verbose debug quiet noshare batch trace_level log_level -declare sbt_saved_stty - -echoerr () { [[ -z "$quiet" ]] && echo "$@" >&2; } -vlog () { [[ -n "$verbose$debug" ]] && echoerr "$@"; } -dlog () { [[ -n "$debug" ]] && echoerr "$@"; } - -# we'd like these set before we get around to properly processing arguments -for arg in "$@"; do - case "$arg" in - -q|-quiet) quiet=true ;; - -d|-debug) debug=true ;; - -v|-verbose) verbose=true ;; - *) ;; - esac -done +declare sbt_jar sbt_dir sbt_create sbt_version +declare scala_version sbt_explicit_version +declare verbose noshare batch trace_level log_level +declare sbt_saved_stty debugUs + +echoerr () { echo >&2 "$@"; } +vlog () { [[ -n "$verbose" ]] && echoerr "$@"; } # spaces are possible, e.g. sbt.version = 0.13.0 build_props_sbt () { [[ -r "$buildProps" ]] && \ - grep '^sbt\.version' "$buildProps" | tr '=' ' ' | awk '{ print $2; }' + grep '^sbt\.version' "$buildProps" | tr '=\r' ' ' | awk '{ print $2; }' } update_build_props_sbt () { @@ -41,31 +32,24 @@ update_build_props_sbt () { perl -pi -e "s/^sbt\.version\b.*\$/sbt.version=${ver}/" "$buildProps" grep -q '^sbt.version[ =]' "$buildProps" || printf "\nsbt.version=%s\n" "$ver" >> "$buildProps" - echoerr "!!!" - echoerr "!!! Updated file $buildProps setting sbt.version to: $ver" - echoerr "!!! Previous value was: $old" - echoerr "!!!" + vlog "!!!" + vlog "!!! Updated file $buildProps setting sbt.version to: $ver" + vlog "!!! Previous value was: $old" + vlog "!!!" } } -sbt_version () { - if [[ -n "$sbt_explicit_version" ]]; then - echo "$sbt_explicit_version" - else - local v="$(build_props_sbt)" - if [[ -n "$v" ]]; then - echo "$v" - else - echo "$sbt_release_version" - fi - fi +set_sbt_version () { + sbt_version="${sbt_explicit_version:-$(build_props_sbt)}" + [[ -n "$sbt_version" ]] || sbt_version=$sbt_release_version + export sbt_version } # restore stty settings (echo in particular) onSbtRunnerExit() { [[ -n "$sbt_saved_stty" ]] || return - dlog "" - dlog "restoring stty: $sbt_saved_stty" + vlog "" + vlog "restoring stty: $sbt_saved_stty" stty "$sbt_saved_stty" unset sbt_saved_stty } @@ -73,7 +57,7 @@ onSbtRunnerExit() { # save stty and trap exit, to ensure echo is reenabled if we are interrupted. trap onSbtRunnerExit EXIT sbt_saved_stty="$(stty -g 2>/dev/null)" -dlog "Saved stty: $sbt_saved_stty" +vlog "Saved stty: $sbt_saved_stty" # this seems to cover the bases on OSX, and someone will # have to tell me about the others. @@ -119,12 +103,13 @@ init_default_option_file () { declare -r cms_opts="-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" declare -r jit_opts="-XX:ReservedCodeCacheSize=256m -XX:+TieredCompilation" -declare -r default_jvm_opts="-ea -Dfile.encoding=UTF8 -XX:MaxPermSize=384m -Xms512m -Xmx1536m -Xss2m $jit_opts $cms_opts" +declare -r default_jvm_opts_common="-Xms512m -Xmx1536m -Xss2m $jit_opts $cms_opts" declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy" declare -r latest_28="2.8.2" declare -r latest_29="2.9.3" -declare -r latest_210="2.10.3" -declare -r latest_211="2.11.0-M5" +declare -r latest_210="2.10.5" +declare -r latest_211="2.11.7" +declare -r latest_212="2.12.0-M3" declare -r script_path="$(get_script_path "$BASH_SOURCE")" declare -r script_name="${script_path##*/}" @@ -133,7 +118,7 @@ declare -r script_name="${script_path##*/}" declare java_cmd="java" declare sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)" declare jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)" -declare sbt_launch_repo="http://typesafe.artifactoryonline.com/typesafe/ivy-releases" +declare sbt_launch_repo="http://repo.typesafe.com/typesafe/ivy-releases" # pull -J and -D options to give to java. declare -a residual_args @@ -144,14 +129,79 @@ declare -a sbt_commands # args to jvm/sbt via files or environment variables declare -a extra_jvm_opts extra_sbt_opts -# if set, use JAVA_HOME over java found in path -[[ -e "$JAVA_HOME/bin/java" ]] && java_cmd="$JAVA_HOME/bin/java" +addJava () { + vlog "[addJava] arg = '$1'" + java_args+=("$1") +} +addSbt () { + vlog "[addSbt] arg = '$1'" + sbt_commands+=("$1") +} +setThisBuild () { + vlog "[addBuild] args = '$@'" + local key="$1" && shift + addSbt "set $key in ThisBuild := $@" +} +addScalac () { + vlog "[addScalac] arg = '$1'" + scalac_args+=("$1") +} +addResidual () { + vlog "[residual] arg = '$1'" + residual_args+=("$1") +} +addResolver () { + addSbt "set resolvers += $1" +} +addDebugger () { + addJava "-Xdebug" + addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1" +} +setScalaVersion () { + [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")' + addSbt "++ $1" +} +setJavaHome () { + java_cmd="$1/bin/java" + setThisBuild javaHome "Some(file(\"$1\"))" + export JAVA_HOME="$1" + export JDK_HOME="$1" + export PATH="$JAVA_HOME/bin:$PATH" +} +setJavaHomeQuietly () { + addSbt warn + setJavaHome "$1" + addSbt info +} + +# if set, use JDK_HOME/JAVA_HOME over java found in path +if [[ -e "$JDK_HOME/lib/tools.jar" ]]; then + setJavaHomeQuietly "$JDK_HOME" +elif [[ -e "$JAVA_HOME/bin/java" ]]; then + setJavaHomeQuietly "$JAVA_HOME" +fi # directory to store sbt launchers declare sbt_launch_dir="$HOME/.sbt/launchers" [[ -d "$sbt_launch_dir" ]] || mkdir -p "$sbt_launch_dir" [[ -w "$sbt_launch_dir" ]] || sbt_launch_dir="$(mktemp -d -t sbt_extras_launchers.XXXXXX)" +java_version () { + local version=$("$java_cmd" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d \") + vlog "Detected Java version: $version" + echo "${version:2:1}" +} + +# MaxPermSize critical on pre-8 jvms but incurs noisy warning on 8+ +default_jvm_opts () { + local v="$(java_version)" + if [[ $v -ge 8 ]]; then + echo "$default_jvm_opts_common" + else + echo "-XX:MaxPermSize=384m $default_jvm_opts_common" + fi +} + build_props_scala () { if [[ -r "$buildProps" ]]; then versionLine="$(grep '^build.scala.versions' "$buildProps")" @@ -162,22 +212,20 @@ build_props_scala () { execRunner () { # print the arguments one to a line, quoting any containing spaces - [[ "$verbose" || "$debug" ]] && echo "# Executing command line:" && { + vlog "# Executing command line:" && { for arg; do if [[ -n "$arg" ]]; then if printf "%s\n" "$arg" | grep -q ' '; then - printf "\"%s\"\n" "$arg" + printf >&2 "\"%s\"\n" "$arg" else - printf "%s\n" "$arg" + printf >&2 "%s\n" "$arg" fi fi done - echo "" + vlog "" } - if [[ -n "$batch" ]]; then - exec /dev/null; then - curl --fail --silent "$url" --output "$jar" + curl --fail --silent --location "$url" --output "$jar" elif which wget >/dev/null; then wget --quiet -O "$jar" "$url" fi @@ -207,9 +255,8 @@ download_url () { } acquire_sbt_jar () { - for_sbt_version="$(sbt_version)" - sbt_url="$(jar_url "$for_sbt_version")" - sbt_jar="$(jar_file "$for_sbt_version")" + sbt_url="$(jar_url "$sbt_version")" + sbt_jar="$(jar_file "$sbt_version")" [[ -r "$sbt_jar" ]] || download_url "$sbt_url" "$sbt_jar" } @@ -218,11 +265,23 @@ usage () { cat < display stack traces with a max of frames (default: -1, traces suppressed) + -debug-inc enable debugging log for the incremental compiler -no-colors disable ANSI color codes -sbt-create start sbt even if current directory contains no sbt project -sbt-dir path to global settings/plugins directory (default: ~/.sbt/) @@ -234,12 +293,12 @@ Usage: $script_name [options] -batch Disable interactive mode -prompt Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted - # sbt version (default: from $buildProps if present, else latest release) - !!! The only way to accomplish this pre-0.12.0 if there is a build.properties file which - !!! contains an sbt.version property is to update the file on disk. That's what this does. + # sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version) + -sbt-force-latest force the use of the latest release of sbt: $sbt_release_version -sbt-version use the specified version of sbt (default: $sbt_release_version) + -sbt-dev use the latest pre-release version of sbt: $sbt_unreleased_version -sbt-jar use the specified jar as the sbt launcher - -sbt-launch-dir directory to hold sbt launchers (default: $sbt_launch_dir) + -sbt-launch-dir directory to hold sbt launchers (default: ~/.sbt/launchers) -sbt-launch-repo repo url for downloading sbt launcher jar (default: $sbt_launch_repo) # scala version (default: as chosen by sbt) @@ -247,6 +306,7 @@ Usage: $script_name [options] -29 use $latest_29 -210 use $latest_210 -211 use $latest_211 + -212 use $latest_212 -scala-home use the scala build at the specified directory -scala-version use the specified version of scala -binary-version use the specified scala version when searching for dependencies @@ -256,7 +316,7 @@ Usage: $script_name [options] # passing options to the jvm - note it does NOT use JAVA_OPTS due to pollution # The default set is used if JVM_OPTS is unset and no -jvm-opts file is found - $default_jvm_opts + $(default_jvm_opts) JVM_OPTS environment variable holding either the jvm args directly, or the reference to a file containing jvm args if given path is prepended by '@' (e.g. '@/etc/jvmopts') Note: "@"-file is overridden by local '.jvmopts' or '-jvm-opts' argument. @@ -273,34 +333,6 @@ Usage: $script_name [options] EOM } -addJava () { - dlog "[addJava] arg = '$1'" - java_args=( "${java_args[@]}" "$1" ) -} -addSbt () { - dlog "[addSbt] arg = '$1'" - sbt_commands=( "${sbt_commands[@]}" "$1" ) -} -addScalac () { - dlog "[addScalac] arg = '$1'" - scalac_args=( "${scalac_args[@]}" "$1" ) -} -addResidual () { - dlog "[residual] arg = '$1'" - residual_args=( "${residual_args[@]}" "$1" ) -} -addResolver () { - addSbt "set resolvers += $1" -} -addDebugger () { - addJava "-Xdebug" - addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1" -} -setScalaVersion () { - [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")' - addSbt "++ $1" -} - process_args () { require_arg () { @@ -314,45 +346,51 @@ process_args () } while [[ $# -gt 0 ]]; do case "$1" in - -h|-help) usage; exit 1 ;; - -v|-verbose) verbose=true && log_level=Info && shift ;; - -d|-debug) debug=true && log_level=Debug && shift ;; - -q|-quiet) quiet=true && log_level=Error && shift ;; - - -trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;; - -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;; - -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;; - -no-share) noshare=true && shift ;; - -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;; - -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;; - -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;; - -offline) addSbt "set offline := true" && shift ;; - -jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;; - -batch) batch=true && shift ;; - -prompt) require_arg "expr" "$1" "$2" && addSbt "set shellPrompt in ThisBuild := (s => { val e = Project.extract(s) ; $2 })" && shift 2 ;; - - -sbt-create) sbt_create=true && shift ;; - -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;; - -sbt-version) require_arg version "$1" "$2" && sbt_explicit_version="$2" && shift 2 ;; - -sbt-dev) sbt_explicit_version="$sbt_unreleased_version" && shift ;; --sbt-launch-dir) require_arg path "$1" "$2" && sbt_launch_dir="$2" && shift 2 ;; --sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;; - -scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;; --binary-version) require_arg version "$1" "$2" && addSbt "set scalaBinaryVersion in ThisBuild := \"$2\"" && shift 2 ;; - -scala-home) require_arg path "$1" "$2" && addSbt "set every scalaHome := Some(file(\"$2\"))" && shift 2 ;; - -java-home) require_arg path "$1" "$2" && java_cmd="$2/bin/java" && shift 2 ;; - -sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;; - -jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;; - - -D*) addJava "$1" && shift ;; - -J*) addJava "${1:2}" && shift ;; - -S*) addScalac "${1:2}" && shift ;; - -28) setScalaVersion "$latest_28" && shift ;; - -29) setScalaVersion "$latest_29" && shift ;; - -210) setScalaVersion "$latest_210" && shift ;; - -211) setScalaVersion "$latest_211" && shift ;; - - *) addResidual "$1" && shift ;; + -h|-help) usage; exit 1 ;; + -v) verbose=true && shift ;; + -d) addSbt "--debug" && addSbt debug && shift ;; + -w) addSbt "--warn" && addSbt warn && shift ;; + -q) addSbt "--error" && addSbt error && shift ;; + -x) debugUs=true && shift ;; + -trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;; + -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;; + -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;; + -no-share) noshare=true && shift ;; + -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;; + -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;; + -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;; + -offline) addSbt "set offline := true" && shift ;; + -jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;; + -batch) batch=true && shift ;; + -prompt) require_arg "expr" "$1" "$2" && setThisBuild shellPrompt "(s => { val e = Project.extract(s) ; $2 })" && shift 2 ;; + + -sbt-create) sbt_create=true && shift ;; + -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;; + -sbt-version) require_arg version "$1" "$2" && sbt_explicit_version="$2" && shift 2 ;; + -sbt-force-latest) sbt_explicit_version="$sbt_release_version" && shift ;; + -sbt-dev) sbt_explicit_version="$sbt_unreleased_version" && shift ;; + -sbt-launch-dir) require_arg path "$1" "$2" && sbt_launch_dir="$2" && shift 2 ;; + -sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;; + -scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;; + -binary-version) require_arg version "$1" "$2" && setThisBuild scalaBinaryVersion "\"$2\"" && shift 2 ;; + -scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "Some(file(\"$2\"))" && shift 2 ;; + -java-home) require_arg path "$1" "$2" && setJavaHome "$2" && shift 2 ;; + -sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;; + -jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;; + + -D*) addJava "$1" && shift ;; + -J*) addJava "${1:2}" && shift ;; + -S*) addScalac "${1:2}" && shift ;; + -28) setScalaVersion "$latest_28" && shift ;; + -29) setScalaVersion "$latest_29" && shift ;; + -210) setScalaVersion "$latest_210" && shift ;; + -211) setScalaVersion "$latest_211" && shift ;; + -212) setScalaVersion "$latest_212" && shift ;; + + --debug) addSbt debug && addResidual "$1" && shift ;; + --warn) addSbt warn && addResidual "$1" && shift ;; + --error) addSbt error && addResidual "$1" && shift ;; + *) addResidual "$1" && shift ;; esac done } @@ -379,21 +417,20 @@ else vlog "No extra sbt options have been defined" fi -[[ -n "$extra_sbt_opts" ]] && process_args "${extra_sbt_opts[@]}" +[[ -n "${extra_sbt_opts[*]}" ]] && process_args "${extra_sbt_opts[@]}" # reset "$@" to the residual args set -- "${residual_args[@]}" argumentCount=$# +# set sbt version +set_sbt_version + # only exists in 0.12+ setTraceLevel() { - case "$(sbt_version)" in - "0.7."* | "0.10."* | "0.11."* ) - echoerr "Cannot set trace level in sbt version $(sbt_version)" - ;; - *) - addSbt "set every traceLevel := $trace_level" - ;; + case "$sbt_version" in + "0.7."* | "0.10."* | "0.11."* ) echoerr "Cannot set trace level in sbt version $sbt_version" ;; + *) setThisBuild traceLevel $trace_level ;; esac } @@ -402,9 +439,9 @@ setTraceLevel() { # Update build.properties on disk to set explicit version - sbt gives us no choice [[ -n "$sbt_explicit_version" ]] && update_build_props_sbt "$sbt_explicit_version" -vlog "Detected sbt version $(sbt_version)" +vlog "Detected sbt version $sbt_version" -[[ -n "$scala_version" ]] && echoerr "Overriding scala version to $scala_version" +[[ -n "$scala_version" ]] && vlog "Overriding scala version to $scala_version" # no args - alert them there's stuff in here (( argumentCount > 0 )) || { @@ -438,10 +475,10 @@ if [[ -n "$noshare" ]]; then addJava "$opt" done else - case "$(sbt_version)" in + case "$sbt_version" in "0.7."* | "0.10."* | "0.11."* | "0.12."* ) [[ -n "$sbt_dir" ]] || { - sbt_dir="$HOME/.sbt/$(sbt_version)" + sbt_dir="$HOME/.sbt/$sbt_version" vlog "Using $sbt_dir as sbt dir, -sbt-dir to override." } ;; @@ -460,22 +497,52 @@ elif [[ -n "$JVM_OPTS" && ! ("$JVM_OPTS" =~ ^@.*) ]]; then extra_jvm_opts=( $JVM_OPTS ) else vlog "Using default jvm options" - extra_jvm_opts=( $default_jvm_opts ) + extra_jvm_opts=( $(default_jvm_opts) ) fi # traceLevel is 0.12+ [[ -n "$trace_level" ]] && setTraceLevel +main () { + execRunner "$java_cmd" \ + "${extra_jvm_opts[@]}" \ + "${java_args[@]}" \ + -jar "$sbt_jar" \ + "${sbt_commands[@]}" \ + "${residual_args[@]}" +} -if [[ -n "$log_level" ]] && [[ "$log_level" != Info ]]; then - sbt_commands=("set logLevel in Global := Level.$log_level" "${sbt_commands[@]}") -fi +# sbt inserts this string on certain lines when formatting is enabled: +# val OverwriteLine = "\r\u001BM\u001B[2K" +# ...in order not to spam the console with a million "Resolving" lines. +# Unfortunately that makes it that much harder to work with when +# we're not going to print those lines anyway. We strip that bit of +# line noise, but leave the other codes to preserve color. +mainFiltered () { + local ansiOverwrite='\r\x1BM\x1B[2K' + local excludeRegex=$(egrep -v '^#|^$' ~/.sbtignore | paste -sd'|' -) + + echoLine () { + local line="$1" + local line1="$(echo "$line" | sed -r 's/\r\x1BM\x1B\[2K//g')" # This strips the OverwriteLine code. + local line2="$(echo "$line1" | sed -r 's/\x1B\[[0-9;]*[JKmsu]//g')" # This strips all codes - we test regexes against this. + + if [[ $line2 =~ $excludeRegex ]]; then + [[ -n $debugUs ]] && echo "[X] $line1" + else + [[ -n $debugUs ]] && echo " $line1" || echo "$line1" + fi + } + + echoLine "Starting sbt with output filtering enabled." + main | while read -r line; do echoLine "$line"; done +} +# Only filter if there's a filter file and we don't see a known interactive command. +# Obviously this is super ad hoc but I don't know how to improve on it. Testing whether +# stdin is a terminal is useless because most of my use cases for this filtering are +# exactly when I'm at a terminal, running sbt non-interactively. +shouldFilter () { [[ -f ~/.sbtignore ]] && ! egrep -q '\b(shell|console|consoleProject)\b' <<<"${residual_args[@]}"; } # run sbt -execRunner "$java_cmd" \ - "${extra_jvm_opts[@]}" \ - "${java_args[@]}" \ - -jar "$sbt_jar" \ - "${sbt_commands[@]}" \ - "${residual_args[@]}" +if shouldFilter; then mainFiltered; else main; fi diff --git a/sonatype.sbt b/sonatype.sbt index 1220a4a51..b94a33d4c 100644 --- a/sonatype.sbt +++ b/sonatype.sbt @@ -1 +1,46 @@ -sonatypeSettings +sonatypeProfileName := "org.msgpack" + +pomExtra in Global := { + http://msgpack.org/ + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + scm:git:github.com/msgpack/msgpack-java.git + scm:git:git@github.com:msgpack/msgpack-java.git + github.com/msgpack/msgpack-java.git + + + UTF-8 + + + + frsyuki + Sadayuki Furuhashi + frsyuki@users.sourceforge.jp + + + muga + Muga Nishizawa + muga.nishizawa@gmail.com + + + oza + Tsuyoshi Ozawa + https://github.com/oza + + + komamitsu + Mitsunori Komatsu + komamitsu@gmail.com + + + xerial + Taro L. Saito + leo@xerial.org + + +} diff --git a/version.sbt b/version.sbt index ab66eef97..93d712300 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.7.0-p7-SNAPSHOT" \ No newline at end of file +version in ThisBuild := "0.8.1-SNAPSHOT" \ No newline at end of file