Verified Commit e2399846 authored by Konstantin Kopper's avatar Konstantin Kopper
Browse files

Kotlin 1.4.31 and some dependency upgrades

parent 95509ae3
Pipeline #27084 passed with stages
in 6 minutes and 22 seconds
......@@ -15,7 +15,6 @@ indent_size = 2
indent_style = space
indent_size = 4
# Disables the import-ordering rule for linting until behaviour matches the formatting of IntelliJ.
# See https://github.com/pinterest/ktlint/issues/527 for more information.
# Disables the indent rule for linting until behaviour matches the formatting of IntelliJ.
# noinspection EditorConfigKeyCorrectness
disabled_rules = import-ordering
disabled_rules = indent
......@@ -15,8 +15,10 @@ variables:
ktlint:
stage: lint
inherit:
default: false
variables: false
image: kkopper/ktlint:latest
variables: {}
script:
- ktlint
......@@ -40,13 +42,13 @@ classes:
dokka:
stage: documentation
only:
- master
- tags
rules:
- if: $CI_COMMIT_BRANCH == "master"
- if: $CI_COMMIT_TAG
dependencies:
- classes
script:
- gradle dokka
- gradle dokkaHtml
after_script:
- cp -R build/dokka .
artifacts:
......@@ -84,8 +86,10 @@ tests:
jar:
extends: .jar
except:
- tags
rules:
- if: $CI_COMMIT_TAG
when: never
- when: on_success
after_script:
- cp build/libs/pseuco-ide-*.jar pseuco-ide-${CI_PIPELINE_ID}.jar
artifacts:
......@@ -96,8 +100,8 @@ jar:
release:
extends: .jar
only:
- tags
rules:
- if: $CI_COMMIT_TAG
after_script:
- cp build/libs/pseuco-ide-*.jar pseuco-ide-${CI_COMMIT_TAG}.jar
artifacts:
......
......@@ -5,18 +5,18 @@ if (!JavaVersion.current().isJava8Compatible)
error("Only JDK 8 or newer is supported!")
plugins {
kotlin("jvm") version "1.3.72"
kotlin("plugin.serialization") version "1.3.72"
id("org.jetbrains.dokka") version "0.10.1"
kotlin("jvm") version "1.4.31"
kotlin("plugin.serialization") version "1.4.31"
id("org.jetbrains.dokka") version "1.4.30"
}
repositories {
jcenter()
}
val ktorVersion = "1.3.2"
val junitVersion = "5.6.2"
val aspectjVersion = "1.9.5"
val ktorVersion = "1.5.2"
val junitVersion = "5.7.1"
val aspectjVersion = "1.9.6"
dependencies {
// The pseuco-java-compiler
......@@ -26,27 +26,28 @@ dependencies {
implementation(kotlin("stdlib-jdk8"))
// Coroutines
implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.3.5")
implementation("org.jetbrains.kotlinx", "kotlinx-coroutines-core", "1.4.3")
// Kotlin serialization
implementation("org.jetbrains.kotlinx", "kotlinx-serialization-runtime", "0.20.0")
for (pkg in listOf("core", "json"))
implementation("org.jetbrains.kotlinx", "kotlinx-serialization-$pkg", "1.1.0")
// OpenJFX
if (JavaVersion.current().isJava11Compatible) {
for (pkg in listOf("base", "controls", "fxml", "graphics"))
for (os in listOf("linux", "mac", "win"))
implementation("org.openjfx", "javafx-$pkg", "14.0.1", classifier = os)
implementation("org.openjfx", "javafx-$pkg", "15.0.1", classifier = os)
}
// Provides RichText TextAreas. Used for code input area.
implementation("org.fxmisc.richtext", "richtextfx", "0.10.5")
implementation("org.fxmisc.richtext", "richtextfx", "0.10.6")
// Google GSON library for JSON
implementation("com.google.code.gson", "gson", "2.8.6")
// Apache Commons
implementation("commons-io", "commons-io", "2.6")
implementation("org.apache.commons", "commons-lang3", "3.10")
implementation("commons-io", "commons-io", "2.8.0")
implementation("org.apache.commons", "commons-lang3", "3.12.0")
// Ktor Apache client and server framework
implementation("io.ktor", "ktor-client-apache", ktorVersion)
......@@ -56,7 +57,7 @@ dependencies {
implementation("io.ktor", "ktor-freemarker", ktorVersion)
// http4k WebSocket client library
implementation("org.http4k", "http4k-client-websocket", "3.244.0")
implementation("org.http4k", "http4k-client-websocket", "4.5.0.1")
// AspectJ runtime
runtimeOnly("org.aspectj", "aspectjrt", aspectjVersion)
......@@ -68,7 +69,7 @@ dependencies {
testImplementation("org.junit.jupiter", "junit-jupiter-params", junitVersion)
testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine", junitVersion)
testImplementation("org.mockito", "mockito-junit-jupiter", "3.3.3")
testImplementation("org.mockito", "mockito-junit-jupiter", "3.8.0")
}
version = "2.0.4"
......@@ -114,20 +115,26 @@ val createBuildProperties by tasks.creating(WriteProperties::class) {
comment = " Build properties. Set during build process."
outputFile = File("src/main/resources/build.properties")
property("branch", try {
"git rev-parse --abbrev-ref HEAD".exec().text
} catch (e: Exception) {
logger.warn("Could not gather branch name from git.")
logger.debug("Stacktrace:", e)
System.getenv("CI_COMMIT_REF_NAME") ?: ""
})
property("commit", try {
"git rev-parse --short HEAD".exec().text
} catch (e: Exception) {
logger.warn("Could not gather commit sha from git.")
logger.debug("Stacktrace:", e)
System.getenv("CI_COMMIT_SHA")?.substring(0, 8) ?: ""
})
property(
"branch",
try {
"git rev-parse --abbrev-ref HEAD".exec().text
} catch (e: Exception) {
logger.warn("Could not gather branch name from git.")
logger.debug("Stacktrace:", e)
System.getenv("CI_COMMIT_REF_NAME") ?: ""
}
)
property(
"commit",
try {
"git rev-parse --short HEAD".exec().text
} catch (e: Exception) {
logger.warn("Could not gather commit sha from git.")
logger.debug("Stacktrace:", e)
System.getenv("CI_COMMIT_SHA")?.substring(0, 8) ?: ""
}
)
property("pipeline", System.getenv("CI_PIPELINE_ID") ?: "")
}
......@@ -232,7 +239,10 @@ open class AspectJTask : DefaultTask() {
)
ant.withGroovyBuilder {
"taskdef"("resource" to "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", "classpath" to taskClasspath)
"taskdef"(
"resource" to "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties",
"classpath" to taskClasspath
)
"iajc"(iajcArgs)
}
}
......@@ -258,47 +268,39 @@ pseucoJavaCompilerProject.tasks {
/* --- Dokka --- */
val dokka by tasks.getting(DokkaTask::class) {
impliedPlatforms = mutableListOf("JVM")
configuration {
jdkVersion = 8
includeNonPublic = true
externalDocumentationLink {
if (JavaVersion.current().isJava11Compatible) {
val path = "https://openjfx.io/javadoc/${dependencyVersion("javafx").substringBefore(".")}/"
url = url(path)
packageListUrl = url(path + "element-list")
} else
url = url("https://docs.oracle.com/javase/8/javafx/api/")
}
externalDocumentationLink {
url = url("https://fxmisc.github.io/richtext/javadoc/${dependencyVersion("richtextfx")}/")
}
externalDocumentationLink {
url = url("https://www.javadoc.io/com.google.code.gson/gson/${dependencyVersion("gson")}/")
packageListUrl = url("$url/element-list")
}
externalDocumentationLink {
url = url("https://commons.apache.org/proper/commons-lang/javadocs/api-${dependencyVersion("commons-lang3")}/")
}
externalDocumentationLink {
url = url("https://commons.apache.org/proper/commons-io/javadocs/api-${dependencyVersion("commons-io")}/")
}
externalDocumentationLink {
url = url("https://http4k.org/api/")
}
sourceLink {
path = "src/main/kotlin"
url = "https://dgit.cs.uni-saarland.de/pseuco/pseuco-ide/blob/master/src/main/kotlin"
lineSuffix = "#L"
tasks.withType<DokkaTask>().configureEach {
dokkaSourceSets {
configureEach {
jdkVersion.set(8)
includeNonPublic.set(true)
platform.set(org.jetbrains.dokka.Platform.jvm)
externalDocumentationLink {
if (JavaVersion.current().isJava11Compatible) {
val path = "https://openjfx.io/javadoc/${dependencyVersion("javafx").substringBefore(".")}/"
url.set(url(path))
packageListUrl.set(url(path + "element-list"))
} else
url.set(url("https://docs.oracle.com/javase/8/javafx/api/"))
}
externalDocumentationLink("https://fxmisc.github.io/richtext/javadoc/${dependencyVersion("richtextfx")}/")
externalDocumentationLink("https://commons.apache.org/proper/commons-lang/javadocs/api-release/")
externalDocumentationLink("https://commons.apache.org/proper/commons-io/javadocs/api-release/")
externalDocumentationLink("https://http4k.org/api/")
externalDocumentationLink {
val path = "https://www.javadoc.io/com.google.code.gson/gson/${dependencyVersion("gson")}/"
url.set(url(path))
packageListUrl.set(url("${path}element-list"))
}
sourceLink {
localDirectory.set(file("src/main/kotlin"))
remoteUrl.set(url("https://dgit.cs.uni-saarland.de/pseuco/pseuco-ide/blob/master/src/main/kotlin"))
remoteLineSuffix.set("#L")
}
}
}
}
......
package com.pseuco
import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Required
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.UnionKind
import kotlinx.serialization.builtins.serializer
/**
* Class representing sharable pseuCo.com files.
......@@ -24,38 +19,25 @@ data class PseuCoComFile(val name: String, val content: String, @Required val ty
/**
* Enum representing the file types supported by the sharing API.
*/
@Serializable(with = Type.Companion::class)
@Serializable
enum class Type {
/**
* A pseuCo file.
*/
@SerialName("pseuco")
PSEUCO,
/**
* A CCS file.
*/
@SerialName("ccs")
CCS,
/**
* A LTS file.
*/
@SerialName("lts")
LTS;
/**
* Serializer converting enum values to lower case strings.
*
* @author Konstantin Kopper
* @since 2.0.4
*/
companion object : KSerializer<Type> {
override val descriptor = SerialDescriptor("PseuCoComFile.Type", UnionKind.ENUM_KIND) {
values().forEach { element(it.name.toLowerCase(), String.serializer().descriptor) }
}
override fun deserialize(decoder: Decoder): Type = values()[descriptor.getElementIndex(decoder.decodeString())]
override fun serialize(encoder: Encoder, value: Type) = encoder.encodeString(descriptor.getElementName(value.ordinal))
}
}
}
package com.pseuco.api
import com.pseuco.PseuCoComFile
import kotlinx.serialization.CompositeDecoder
import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Class mimicking the JSON object format used by the [pseuCo.com](https://pseuco.com) sharing API.
......@@ -22,6 +22,10 @@ import kotlinx.serialization.builtins.serializer
@Serializable(PseuCoComExchange.Companion::class)
data class PseuCoComExchange(val file: PseuCoComFile, val temporary: Boolean, val sharingAgreementVersion: Int?) {
init {
require(temporary or (sharingAgreementVersion != null))
}
/**
* Creates a new wrapper for a file to be not stored permanently.
*
......@@ -42,7 +46,7 @@ data class PseuCoComExchange(val file: PseuCoComFile, val temporary: Boolean, va
* @since 2.0.4
*/
companion object : KSerializer<PseuCoComExchange> {
override val descriptor = SerialDescriptor("PseuCoComExchange") {
override val descriptor = buildClassSerialDescriptor("PseuCoComExchange") {
element("file", PseuCoComFile.serializer().descriptor)
element("temporary", Boolean.serializer().descriptor)
element("sharingAgreementVersion", Int.serializer().descriptor, isOptional = true)
......@@ -57,7 +61,7 @@ data class PseuCoComExchange(val file: PseuCoComFile, val temporary: Boolean, va
tailrec fun decodeAttributes(): PseuCoComExchange {
when (structure.decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> {
CompositeDecoder.DECODE_DONE -> {
structure.endStructure(descriptor)
return PseuCoComExchange(file, temporary, sharingAgreementVersion)
}
......
......@@ -8,15 +8,18 @@ import io.ktor.client.request.accept
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.statement.HttpStatement
import io.ktor.content.TextContent
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.content.TextContent
import io.ktor.http.withCharset
import io.ktor.utils.io.jvm.javaio.toInputStream
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.JsonException
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import java.awt.Desktop
import java.net.URI
import java.net.URISyntaxException
......@@ -73,9 +76,12 @@ object PseuCoShare : FileSharer {
path("api", "paste", "add")
}
accept(ContentType.Application.Json.withCharset(Charsets.UTF_8))
body = TextContent(Json.stringify(PseuCoComExchange.serializer(),
if (temporary) PseuCoComExchange(file) else PseuCoComExchange(file, 1)),
contentType = ContentType.Application.Json)
body = TextContent(
Json.encodeToString(
if (temporary) PseuCoComExchange(file) else PseuCoComExchange(file, 1)
),
contentType = ContentType.Application.Json
)
}.execute()
if (c.status != HttpStatusCode.OK)
......@@ -84,8 +90,11 @@ object PseuCoShare : FileSharer {
?.let { IllegalStateException("Response body: $it") })
return try {
URI(Json(JsonConfiguration.Stable).parseJson(c.content.toInputStream().reader().use { it.readText() }).jsonObject["url"]!!.primitive.content)
} catch (e: JsonException) {
URI(
Json { allowStructuredMapKeys = true }.parseToJsonElement(
c.content.toInputStream().reader().use { it.readText() }).jsonObject["url"]!!.jsonPrimitive.content
)
} catch (e: SerializationException) {
throw PseuCoShareException("Parsing the API response failed.", e)
} catch (e: URISyntaxException) {
throw PseuCoShareException("Malformed URL.", e)
......@@ -120,8 +129,8 @@ object PseuCoShare : FileSharer {
?.let { IllegalStateException("Response body: $it") })
return try {
Json.parse(PseuCoComExchange.serializer(), c.content.toInputStream().reader().use { it.readText() }).file
} catch (e: JsonException) {
Json.decodeFromString<PseuCoComExchange>(c.content.toInputStream().reader().use { it.readText() }).file
} catch (e: SerializationException) {
throw PseuCoShareException("Parsing the API response failed.", e)
}
}
......
......@@ -25,9 +25,11 @@ import io.ktor.websocket.WebSocketServerSession
import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket
import kotlinx.coroutines.launch
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.JsonException
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject
import util.Workspace
import java.io.File
import java.util.Random
......@@ -78,14 +80,6 @@ object PseuCoWebSocket : PseuCoServer {
*/
private val connections = mutableListOf<WebSocketServerSession>()
/**
* [Json] instance used to parse and serialize [SocketMessage]s.
*
* @author Konstantin Kopper
* @since 2.0.4
*/
private val json = Json(JsonConfiguration.Stable)
/**
* [Json] instance used to parse and serialize [SocketMessage]s.
* Ignores unknown keys during parsing.
......@@ -93,7 +87,7 @@ object PseuCoWebSocket : PseuCoServer {
* @author Konstantin Kopper
* @since 2.0.4
*/
private val jsonNonStrict = Json(JsonConfiguration.Stable.copy(ignoreUnknownKeys = true))
private val jsonNonStrict = Json { ignoreUnknownKeys = true }
/**
* The actual server.
......@@ -117,10 +111,10 @@ object PseuCoWebSocket : PseuCoServer {
}
@Suppress("MoveVariableDeclarationIntoWhen")
val request = json.parseJson(frame.readText()).let {
val request = Json.parseToJsonElement(frame.readText()).let {
val o = try {
it.jsonObject
} catch (e: JsonException) {
} catch (e: IllegalArgumentException) {
sendError("Expected a JSON object.")
return@let null
}
......@@ -131,8 +125,8 @@ object PseuCoWebSocket : PseuCoServer {
}
try {
jsonNonStrict.fromJson(SocketMessage.serializer(), it)
} catch (e: JsonException) {
jsonNonStrict.decodeFromJsonElement<SocketMessage>(it)
} catch (e: SerializationException) {
sendError("Parsing the request failed.")
null
} catch (e: Throwable) {
......@@ -199,7 +193,7 @@ object PseuCoWebSocket : PseuCoServer {
* @author Konstantin Kopper
* @since 2.0.0
*/
private fun createFrame(msg: SocketMessage): Frame = Frame.Text(Json.stringify(SocketMessage.serializer(), msg))
private fun createFrame(msg: SocketMessage): Frame = Frame.Text(Json.encodeToString(msg))
/**
* Send [msg] over a WebSockets connection.
......
package com.pseuco.websocket
import com.pseuco.PseuCoComFile
import kotlinx.serialization.CompositeDecoder
import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.Required
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.UnionKind
import kotlinx.serialization.builtins.serializer
/**
* All valid messages used in the pseuCo WebSockets file sharing protocol.
......@@ -21,8 +15,8 @@ import kotlinx.serialization.builtins.serializer
* @param type A string representation of the message type.
* @constructor Creates a new Socket message.
*/
@Serializable(with = SocketMessage.Companion::class)
sealed class SocketMessage(val type: String) {
@Serializable
sealed class SocketMessage {
/**
* All valid request messages.
......@@ -31,7 +25,8 @@ sealed class SocketMessage(val type: String) {
* @since 2.0.0
* @constructor Creates a new request with [type].
*/
sealed class Request(type: String) : SocketMessage(type) {
@Serializable
sealed class Request : SocketMessage() {
/**
* A request of [Request.type] `get`.
......@@ -42,7 +37,9 @@ sealed class SocketMessage(val type: String) {
* @since 2.0.0
* @param id Unique identifier of the requested file.
*/
data class Get(val id: Int) : Request("get")
@Serializable
@SerialName("get")
data class Get(val id: Int) : Request()
/**
* A request of [Request.type] `open`.
......@@ -53,7 +50,9 @@ sealed class SocketMessage(val type: String) {
* @since 2.0.0
* @param file The file to be opened.
*/
data class Open(val file: PseuCoComFile) : Request("open")
@Serializable
@SerialName("open")
data class Open(val file: PseuCoComFile) : Request()
}
/**
......@@ -63,7 +62,8 @@ sealed class SocketMessage(val type: String) {
* @since 2.0.0
* @constructor Creates a new response with [type].
*/
sealed class Response(type: String) : SocketMessage(type) {
@Serializable
sealed class Response : SocketMessage() {
/**
* A response of [Response.type] `file`.
......@@ -74,7 +74,9 @@ sealed class SocketMessage(val type: String) {
* @since 2.0.0
* @param file The requested file.
*/
data class File(val file: PseuCoComFile) : Response("file")
@Serializable
@SerialName("file")
data class File(val file: PseuCoComFile) : Response()
/**
* A response of [Response.type] `success`.
......@@ -83,7 +85,9 @@ sealed class SocketMessage(val type: String) {
* @author Konstantin Kopper
* @since 2.0.0
*/
object Success : Response("success")
@Serializable
@SerialName("success")
object Success : Response()
/**
* A response of [Response.type] `error`.
......@@ -95,13 +99,16 @@ sealed class SocketMessage(val type: String) {
* @param message A description of the error.
* @param code An identifier which may identify the error precisely.
*/
data class Error(val message: String, val code: Code = Code.GENERAL) : Response("error") {
@Serializable
@SerialName("error")
data class Error(val message: String, @Required val code: Code = Code.GENERAL) : Response() {
/**
* Possible error codes used in the [Error] messages.
*
* @author Konstantin Kopper
*/
@Serializable
enum class Code {
/**
......@@ -110,6 +117,7 @@ sealed class SocketMessage(val type: String) {