Skip to content

Commit bb9ee3b

Browse files
committed
Merge branch 'develop': release 1.5
2 parents 5096111 + 05d09e3 commit bb9ee3b

File tree

11 files changed

+342
-87
lines changed

11 files changed

+342
-87
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,19 @@ kinfra-commons
2323

2424
Более безопасная альтернатива `Optional.orElse(null)`: возвращаемый тип - `T?`, а не `T!`.
2525

26+
### Работа с time-based UUID
27+
28+
#### generateTimeBasedUuid()
29+
30+
Создаёт новый time-based UUID.
31+
32+
#### UUID.isTimeBased
33+
34+
Определяет, является ли данный UUID time-based (версии 1).
35+
2636
#### UUID.instant()
2737

28-
Возвращает `Instant`, соответствующий полю `timestamp` UUID версии 4 (time-based).
38+
Возвращает `Instant`, соответствующий полю `timestamp`.
2939

3040
### Работа с `enum class`
3141

build.gradle

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,22 @@
11
plugins {
2-
id 'ru.kontur.library' version '4.4'
3-
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
2+
id 'ru.kontur.library' version '5.6'
3+
id 'org.jetbrains.kotlin.jvm' version '1.4.32'
44
}
55

66
ext {
7-
release = '1.4'
7+
release = '1.5'
88

99
versions = [
1010
assertj: '3.14.0',
11-
coroutines: '1.3.3',
11+
coroutines: '1.4.3',
1212
]
13-
14-
branch = project.properties["branch"]?.toString()
15-
isStableBranch = (branch == "master") || branch?.startsWith("release-")
16-
version = release + (isStableBranch ? '' : '-SNAPSHOT')
1713
}
1814

1915
group = "ru.kontur.kinfra"
20-
version = ext.version
21-
22-
sourceCompatibility = 8
16+
version = buildVersion(release) { libraryDefaults() }
2317

2418
dependencies {
25-
allMain platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:${versions.coroutines}")
26-
19+
implementation platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:${versions.coroutines}")
2720
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8"
2821

2922
testImplementation "org.assertj:assertj-core:${versions.assertj}"

gradle/wrapper/gradle-wrapper.jar

3.5 KB
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

gradlew

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ esac
8282

8383
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
8484

85+
8586
# Determine the Java command to use to start the JVM.
8687
if [ -n "$JAVA_HOME" ] ; then
8788
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -125,10 +126,11 @@ if $darwin; then
125126
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126127
fi
127128

128-
# For Cygwin, switch paths to Windows format before running java
129-
if $cygwin ; then
129+
# For Cygwin or MSYS, switch paths to Windows format before running java
130+
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130131
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131132
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133+
132134
JAVACMD=`cygpath --unix "$JAVACMD"`
133135

134136
# We build the pattern for arguments to be converted via cygpath
@@ -154,19 +156,19 @@ if $cygwin ; then
154156
else
155157
eval `echo args$i`="\"$arg\""
156158
fi
157-
i=$((i+1))
159+
i=`expr $i + 1`
158160
done
159161
case $i in
160-
(0) set -- ;;
161-
(1) set -- "$args0" ;;
162-
(2) set -- "$args0" "$args1" ;;
163-
(3) set -- "$args0" "$args1" "$args2" ;;
164-
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165-
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166-
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167-
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168-
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169-
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
162+
0) set -- ;;
163+
1) set -- "$args0" ;;
164+
2) set -- "$args0" "$args1" ;;
165+
3) set -- "$args0" "$args1" "$args2" ;;
166+
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167+
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168+
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169+
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170+
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171+
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170172
esac
171173
fi
172174

@@ -175,14 +177,9 @@ save () {
175177
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176178
echo " "
177179
}
178-
APP_ARGS=$(save "$@")
180+
APP_ARGS=`save "$@"`
179181

180182
# Collect all arguments for the java command, following the shell quoting and substitution rules
181183
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182184

183-
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184-
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185-
cd "$(dirname "$0")"
186-
fi
187-
188185
exec "$JAVACMD" "$@"

gradlew.bat

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
2929
set APP_BASE_NAME=%~n0
3030
set APP_HOME=%DIRNAME%
3131

32+
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
33+
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34+
3235
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
3336
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
3437

@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
3740

3841
set JAVA_EXE=java.exe
3942
%JAVA_EXE% -version >NUL 2>&1
40-
if "%ERRORLEVEL%" == "0" goto init
43+
if "%ERRORLEVEL%" == "0" goto execute
4144

4245
echo.
4346
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +54,7 @@ goto fail
5154
set JAVA_HOME=%JAVA_HOME:"=%
5255
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
5356

54-
if exist "%JAVA_EXE%" goto init
57+
if exist "%JAVA_EXE%" goto execute
5558

5659
echo.
5760
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,28 +64,14 @@ echo location of your Java installation.
6164

6265
goto fail
6366

64-
:init
65-
@rem Get command-line arguments, handling Windows variants
66-
67-
if not "%OS%" == "Windows_NT" goto win9xME_args
68-
69-
:win9xME_args
70-
@rem Slurp the command line arguments.
71-
set CMD_LINE_ARGS=
72-
set _SKIP=2
73-
74-
:win9xME_args_slurp
75-
if "x%~1" == "x" goto execute
76-
77-
set CMD_LINE_ARGS=%*
78-
7967
:execute
8068
@rem Setup the command line
8169

8270
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
8371

72+
8473
@rem Execute Gradle
85-
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
74+
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
8675

8776
:end
8877
@rem End local scope for the variables with windows NT shell
Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package ru.kontur.kinfra.commons
22

3-
import ru.kontur.kinfra.commons.time.TimeTicks
43
import java.time.Instant
54
import java.util.*
65

@@ -11,12 +10,8 @@ import java.util.*
1110
*/
1211
fun <T> Optional<T>.unwrap(): T? = orElse(null)
1312

14-
/**
15-
* Return timestamp of this time-based (version 1) UUID as an [Instant].
16-
*
17-
* @throws UnsupportedOperationException if this UUID is not time-based
18-
* @see UUID.timestamp
19-
*/
20-
fun UUID.instant(): Instant {
21-
return TimeTicks.UuidTimestamp(timestamp()).toInstant()
13+
@Deprecated(message = "for compatibility", level = DeprecationLevel.HIDDEN)
14+
@JvmName("instant")
15+
fun UUID.instantCompatibilityBridge(): Instant {
16+
return instant()
2217
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package ru.kontur.kinfra.commons
2+
3+
import ru.kontur.kinfra.commons.time.TimeTicks
4+
import ru.kontur.kinfra.commons.time.toTicks
5+
import ru.kontur.kinfra.commons.time.toUuidTime
6+
import java.time.Clock
7+
import java.time.Duration
8+
import java.time.Instant
9+
import java.util.*
10+
import java.util.concurrent.atomic.AtomicLong
11+
12+
/**
13+
* Determines if this UUID is time-based (version 1)
14+
*/
15+
val UUID.isTimeBased: Boolean
16+
get() = version() == 1
17+
18+
/**
19+
* Return timestamp of this time-based (version 1) UUID as an [Instant].
20+
*
21+
* @throws UnsupportedOperationException if this UUID is not time-based
22+
* @see UUID.timestamp
23+
*/
24+
fun UUID.instant(): Instant {
25+
return TimeTicks.UuidTimestamp(timestamp()).toInstant()
26+
}
27+
28+
/**
29+
* Creates a new time-based (version 1) UUID.
30+
*/
31+
fun generateTimeBasedUuid(): UUID {
32+
return TimeBasedUuidGenerator.generate()
33+
}
34+
35+
internal object TimeBasedUuidGenerator {
36+
// See https://www.ietf.org/rfc/rfc4122.html
37+
38+
private val random = Random()
39+
private val timeSource = Clock.systemUTC()
40+
private val timestampAccuracy = Duration.ofMillis(1).toTicks().value
41+
42+
private val lastTimestamp = AtomicLong()
43+
private val lsb = AtomicLong(generateLsb())
44+
45+
fun generate(): UUID {
46+
val timestamp: Long
47+
var currentLsb: Long
48+
while (true) {
49+
val last = lastTimestamp.get()
50+
val now = timeSource.instant().toUuidTime().value
51+
// Validity of this value is checked by CAS on lastTimestamp
52+
currentLsb = lsb.get()
53+
if (now > last) {
54+
if (lastTimestamp.compareAndSet(last, now)) {
55+
timestamp = now
56+
break
57+
}
58+
} else if (now + timestampAccuracy > last) {
59+
val next = last + 1
60+
// Use a next timestamp value as long as it does not go far from the current time
61+
if (now + timestampAccuracy > next) {
62+
if (lastTimestamp.compareAndSet(last, next)) {
63+
timestamp = next
64+
break
65+
}
66+
} else {
67+
// Wait for a next timestamp from system clock
68+
Thread.yield()
69+
}
70+
} else {
71+
// Last timestamp is much greater than current time
72+
// Hence the clock jumped backwards
73+
// Generate a new clock sequence and node to safely reuse the same timestamps
74+
val newLsb = generateLsb()
75+
if (lsb.compareAndSet(currentLsb, newLsb)) {
76+
currentLsb = newLsb
77+
if (lastTimestamp.compareAndSet(last, now)) {
78+
timestamp = now
79+
break
80+
}
81+
}
82+
}
83+
}
84+
return UUID(makeMsb(timestamp), currentLsb)
85+
}
86+
87+
fun makeMsb(timestamp: Long): Long {
88+
require(timestamp ushr 60 == 0L) { "Invalid timestamp: $timestamp" }
89+
90+
val hiMask = makeMask(12) shl 48
91+
val midMask = makeMask(16) shl 32
92+
val lowMask = makeMask(32)
93+
return timestamp.and(lowMask).shl(32) or
94+
timestamp.and(midMask).ushr(16) or
95+
timestamp.and(hiMask).ushr(48) or
96+
(1 shl 12)
97+
}
98+
99+
private fun generateLsb(): Long {
100+
val variant = 1L shl 63
101+
val clock = (random.nextInt() and 0x3fff).toLong() shl 48
102+
// Random node is used, as described in section 4.5
103+
val node = run {
104+
val randomData = random.nextLong()
105+
val macMask = makeMask(48)
106+
val multicastBit = 1L shl 40
107+
multicastBit or (randomData and macMask)
108+
}
109+
return variant or clock or node
110+
}
111+
112+
private fun makeMask(bitSize: Int): Long {
113+
return (1L shl bitSize) - 1
114+
}
115+
116+
}

src/main/kotlin/ru/kontur/kinfra/commons/binary/Hex.kt

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
11
package ru.kontur.kinfra.commons.binary
22

3-
private val hexDigits = "0123456789abcdef".toCharArray()
3+
private val hexDigitsLower = "0123456789abcdef".toCharArray()
4+
private val hexDigitsUpper = "0123456789ABCDEF".toCharArray()
45

56
/**
67
* Returns this byte in form of two hexadecimal digits.
78
*/
8-
fun Byte.toHexString(): String {
9+
fun Byte.toHexString(upperCase: Boolean = false): String {
910
val unsigned = asUnsigned()
11+
val hexDigits = if (upperCase) hexDigitsUpper else hexDigitsLower
1012
val chars = CharArray(2)
1113
chars[0] = hexDigits[unsigned shr 4]
1214
chars[1] = hexDigits[unsigned and 0xF]
1315
return String(chars)
1416
}
1517

18+
@Deprecated(message = "for compatibility", level = DeprecationLevel.HIDDEN)
19+
fun Byte.toHexString(): String {
20+
return toHexString(upperCase = false)
21+
}
22+
1623
/**
1724
* Returns hexadecimal representation of bytes in this array.
1825
*
1926
* Inverse transformation is possible with [byteArrayOfHex].
2027
*/
21-
fun ByteArray.toHexString(): String = buildString(size * 2) {
28+
fun ByteArray.toHexString(upperCase: Boolean = false): String = buildString(size * 2) {
2229
for (byte in this@toHexString) {
23-
appendHexByte(byte)
30+
appendHexByte(byte, upperCase)
2431
}
2532
}
2633

34+
@Deprecated(message = "for compatibility", level = DeprecationLevel.HIDDEN)
35+
fun ByteArray.toHexString(): String {
36+
return toHexString(upperCase = false)
37+
}
38+
2739
/**
2840
* Constructs array of bytes from its hexadecimal representation.
2941
*
@@ -45,10 +57,16 @@ fun byteArrayOfHex(hex: String): ByteArray {
4557

4658
/**
4759
* Append hexadecimal representation of a [byte] to this builder.
48-
* Exactly two hexadecimal digits in lower case are appended.
60+
* Exactly two hexadecimal digits are appended.
4961
*/
50-
fun StringBuilder.appendHexByte(byte: Byte) {
62+
fun StringBuilder.appendHexByte(byte: Byte, upperCase: Boolean = false) {
5163
val unsigned = byte.asUnsigned()
64+
val hexDigits = if (upperCase) hexDigitsUpper else hexDigitsLower
5265
append(hexDigits[unsigned shr 4])
5366
append(hexDigits[unsigned and 0xF])
5467
}
68+
69+
@Deprecated(message = "for compatibility", level = DeprecationLevel.HIDDEN)
70+
fun StringBuilder.appendHexByte(byte: Byte) {
71+
appendHexByte(byte, upperCase = false)
72+
}

0 commit comments

Comments
 (0)