From 16d3d2f764a2d4d940a9afb264e9e3fe44aff0cf Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Sat, 22 Apr 2017 02:24:17 +0300 Subject: [PATCH] Make Regex class serializable on JVM #KT-16447 Fixed Refactor helper functions for serialization tests. --- .../stdlib/src/kotlin/text/regex/Regex.kt | 17 ++++++-- .../stdlib/test/collections/ComparisonDSL.kt | 6 +++ libraries/stdlib/test/io/SerializableTest.kt | 23 ++++++---- libraries/stdlib/test/text/RegexJVMTest.kt | 43 +++++++++++++++++++ .../kotlin-stdlib-runtime-merged.txt | 2 +- .../reference-public-api/kotlin-stdlib.txt | 2 +- 6 files changed, 80 insertions(+), 13 deletions(-) diff --git a/libraries/stdlib/src/kotlin/text/regex/Regex.kt b/libraries/stdlib/src/kotlin/text/regex/Regex.kt index 2bcd603efba..32bdbaa20db 100644 --- a/libraries/stdlib/src/kotlin/text/regex/Regex.kt +++ b/libraries/stdlib/src/kotlin/text/regex/Regex.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,7 +95,7 @@ public data class MatchGroup(public val value: String, public val range: IntRang */ public class Regex @PublishedApi -internal constructor(private val nativePattern: Pattern) { +internal constructor(private val nativePattern: Pattern) : Serializable { /** Creates a regular expression from the specified [pattern] string and the default options. */ @@ -112,8 +112,9 @@ internal constructor(private val nativePattern: Pattern) { public val pattern: String get() = nativePattern.pattern() + private var _options: Set? = null /** The set of options that were used to create this regular expression. */ - public val options: Set = fromInt(nativePattern.flags()) + public val options: Set get() = _options ?: fromInt(nativePattern.flags()).also { _options = it } /** Indicates whether the regular expression matches the entire [input]. */ public infix fun matches(input: CharSequence): Boolean = nativePattern.matcher(input).matches() @@ -203,6 +204,16 @@ internal constructor(private val nativePattern: Pattern) { */ public fun toPattern(): Pattern = nativePattern + private fun writeReplace(): Any = Serialized(nativePattern.pattern(), nativePattern.flags()) + + private class Serialized(val pattern: String, val flags: Int) : Serializable { + companion object { + private const val serialVersionUID: Long = 0L + } + + private fun readResolve(): Any = Regex(Pattern.compile(pattern, flags)) + } + companion object { /** Returns a literal regex for the specified [literal] string. */ public fun fromLiteral(literal: String): Regex = literal.toRegex(RegexOption.LITERAL) diff --git a/libraries/stdlib/test/collections/ComparisonDSL.kt b/libraries/stdlib/test/collections/ComparisonDSL.kt index d50b70503af..9900d3abce6 100644 --- a/libraries/stdlib/test/collections/ComparisonDSL.kt +++ b/libraries/stdlib/test/collections/ComparisonDSL.kt @@ -1,6 +1,7 @@ package test.collections import test.* +import kotlin.reflect.KProperty1 import kotlin.test.* public fun compare(expected: T, actual: T, block:CompareContext.() -> Unit) { @@ -12,6 +13,11 @@ public class CompareContext(public val expected: T, public val actual: T) public fun equals(message: String = "") { assertEquals(expected, actual, message) } + + public fun

propertyEquals(property: KProperty1) { + propertyEquals(property.name, property) + } + public fun

propertyEquals(message: String = "", getter: T.() -> P) { assertEquals(expected.getter(), actual.getter(), message) } diff --git a/libraries/stdlib/test/io/SerializableTest.kt b/libraries/stdlib/test/io/SerializableTest.kt index 3d0272a0a3f..305a34d1734 100644 --- a/libraries/stdlib/test/io/SerializableTest.kt +++ b/libraries/stdlib/test/io/SerializableTest.kt @@ -57,25 +57,32 @@ class SerializableTest { } } -public fun serializeAndDeserialize(value: T): T { +public fun serializeToByteArray(value: T): ByteArray { val outputStream = ByteArrayOutputStream() val objectOutputStream = ObjectOutputStream(outputStream) objectOutputStream.writeObject(value) objectOutputStream.close() outputStream.close() + return outputStream.toByteArray() +} - val inputStream = ByteArrayInputStream(outputStream.toByteArray()) +public fun deserializeFromByteArray(bytes: ByteArray): T { + val inputStream = ByteArrayInputStream(bytes) val inputObjectStream = ObjectInputStream(inputStream) @Suppress("UNCHECKED_CAST") return inputObjectStream.readObject() as T } +public fun serializeAndDeserialize(value: T): T { + val bytes = serializeToByteArray(value) + return deserializeFromByteArray(bytes) +} + private fun hexToBytes(value: String): ByteArray = value.split(" ").map { Integer.parseInt(it, 16).toByte() }.toByteArray() -public fun deserializeFromHex(value: String) = hexToBytes(value).let { - val inputStream = ByteArrayInputStream(it) - val inputObjectStream = ObjectInputStream(inputStream) - @Suppress("UNCHECKED_CAST") - inputObjectStream.readObject() as T -} +public fun deserializeFromHex(value: String) = deserializeFromByteArray(hexToBytes(value)) + +public fun serializeToHex(value: T) = + serializeToByteArray(value).joinToString(" ") { (it.toInt() and 0xFF).toString(16).padStart(2, '0') } + diff --git a/libraries/stdlib/test/text/RegexJVMTest.kt b/libraries/stdlib/test/text/RegexJVMTest.kt index 752e3c69a55..3b9578c241d 100644 --- a/libraries/stdlib/test/text/RegexJVMTest.kt +++ b/libraries/stdlib/test/text/RegexJVMTest.kt @@ -1,8 +1,27 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + @file:kotlin.jvm.JvmVersion package test.text import kotlin.test.* import org.junit.Test +import test.collections.compare +import test.io.* +import java.util.regex.Pattern class RegexJVMTest { @@ -27,4 +46,28 @@ class RegexJVMTest { assertEquals("b", m2.groups[2]?.value) assertEquals(4..4, m2.groups[2]?.range) } + + + private fun compareRegex(expected: Regex, actual: Regex) = compare(expected, actual) { + propertyEquals(Regex::pattern) + propertyEquals(Regex::options) + propertyEquals("flags") { toPattern().flags() } + } + + private fun equivalentAfterDeserialization(regex: Regex) = compareRegex(regex, serializeAndDeserialize(regex)) + + @Test fun serializeDeserializeRegex() { + equivalentAfterDeserialization(Regex("")) + equivalentAfterDeserialization(Regex("\\w+")) + equivalentAfterDeserialization(Regex("\\w+", RegexOption.IGNORE_CASE)) + equivalentAfterDeserialization(Regex("\\w+", setOf(RegexOption.LITERAL, RegexOption.MULTILINE))) + equivalentAfterDeserialization(Pattern.compile("\\w+", Pattern.UNICODE_CASE).toRegex()) + } + + @Test fun deserializeRegexFromHex() { + val expected = Regex("\\w+", RegexOption.IGNORE_CASE) + // println(serializeToHex(expected)) + val deserialized = deserializeFromHex("ac ed 00 05 73 72 00 1c 6b 6f 74 6c 69 6e 2e 74 65 78 74 2e 52 65 67 65 78 24 53 65 72 69 61 6c 69 7a 65 64 00 00 00 00 00 00 00 00 02 00 02 49 00 05 66 6c 61 67 73 4c 00 07 70 61 74 74 65 72 6e 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 78 70 00 00 00 42 74 00 03 5c 77 2b") + compareRegex(expected, deserialized) + } } diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index d5d3d04c39a..10ffa4ac78c 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -3746,7 +3746,7 @@ public final class kotlin/text/MatchResult$Destructured { public final fun toList ()Ljava/util/List; } -public final class kotlin/text/Regex { +public final class kotlin/text/Regex : java/io/Serializable { public static final field Companion Lkotlin/text/Regex$Companion; public fun (Ljava/lang/String;)V public fun (Ljava/lang/String;Ljava/util/Set;)V diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt index c3f3b7291f8..7201a853b59 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt @@ -2441,7 +2441,7 @@ public final class kotlin/text/MatchResult$Destructured { public final fun toList ()Ljava/util/List; } -public final class kotlin/text/Regex { +public final class kotlin/text/Regex : java/io/Serializable { public static final field Companion Lkotlin/text/Regex$Companion; public fun (Ljava/lang/String;)V public fun (Ljava/lang/String;Ljava/util/Set;)V