Skip to content

Commit

Permalink
Proof of concept--using Kotlin in Ion Java
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt committed May 23, 2023
1 parent 6694dc7 commit 98ce1c9
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 234 deletions.
55 changes: 55 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import java.util.Properties


plugins {
kotlin("jvm") version "1.8.21"
java
`maven-publish`
jacoco
signing
id("com.github.johnrengelman.shadow") version "8.1.1"

id("org.cyclonedx.bom") version "1.7.2"
id("com.github.spotbugs") version "5.0.13"
// TODO: more static analysis. E.g.:
Expand All @@ -16,15 +19,22 @@ plugins {

repositories {
mavenCentral()
google()
}

val r8 = configurations.create("r8")

dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.0")

testImplementation("org.junit.jupiter:junit-jupiter:5.7.1")
testCompileOnly("junit:junit:4.13")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
testImplementation("org.hamcrest:hamcrest:2.2")
testImplementation("pl.pragmatists:JUnitParams:1.1.1")
testImplementation("com.google.code.tempus-fugit:tempus-fugit:1.1")

r8("com.android.tools:r8:8.0.40")
}

group = "com.amazon.ion"
Expand All @@ -50,12 +60,47 @@ sourceSets {
}
}

kotlin {
jvmToolchain(8)
}

tasks {
withType<JavaCompile> {
options.encoding = "UTF-8"
// In Java 9+ we can use `release` but for now we're still building with JDK 8, 11
}

shadowJar {
relocate("kotlin", "com.amazon.ion_.shaded.kotlin")
minimize()
}

val minifiedJar by register<JavaExec>("r8Jar") {
val rules = file("config/r8/rules.txt")

inputs.file("build/libs/ion-java-$version-all.jar")
inputs.file(rules)
outputs.file("build/libs/ion-java-$version-r8.jar")

dependsOn(shadowJar)
dependsOn(configurations.runtimeClasspath)

classpath(r8)
mainClass.set("com.android.tools.r8.R8")
args = listOf(
"--release",
"--classfile",
"--output", "build/libs/ion-java-$version-minified.jar",
"--pg-conf", rules.toString(),
"--lib", System.getProperty("java.home").toString(),
"build/libs/ion-java-$version-all.jar",
)
}

build {
dependsOn(minifiedJar)
}

javadoc {
// Suppressing Javadoc warnings is clunky, but there doesn't seem to be any nicer way to do it.
// https://stackoverflow.com/questions/62894307/option-xdoclintnone-does-not-work-in-gradle-to-ignore-javadoc-warnings
Expand Down Expand Up @@ -165,6 +210,16 @@ tasks {
finalizedBy(jacocoTestReport)
}

val testMinified by register<Test>("testMinified") {
maxHeapSize = "1g" // When this line was added Xmx 512m was the default, and we saw OOMs
maxParallelForks = Math.max(1, Runtime.getRuntime().availableProcessors() / 2)
group = "verification"
testClassesDirs = project.sourceSets.test.get().output.classesDirs
classpath = project.sourceSets.test.get().runtimeClasspath + minifiedJar.outputs.files
dependsOn(minifiedJar)
useJUnitPlatform()
}

withType<Sign> {
setOnlyIf { isReleaseVersion && gradle.taskGraph.hasTask(":publish") }
}
Expand Down
6 changes: 6 additions & 0 deletions config/r8/rules.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-keepattributes SourceFile, LineNumberTable

-keep class com.amazon.ion.***
-keep interface com.amazon.ion.***
-keep enum com.amazon.ion.***
-keeppackagenames ***
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
Expand All @@ -12,33 +12,30 @@
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package com.amazon.ion;
package com.amazon.ion

/**
* Indicates the smallest-possible Java type of an Ion {@code int} value.
* Indicates the smallest-possible Java type for an Ion `int` value.
*/
public enum IntegerSize
{
enum class IntegerSize {
/**
* Fits in the Java {@code int} primitive (four bytes).
* The value can be retrieved through methods like {@link IonReader#intValue()}
* or {@link IonInt#intValue()} without data loss.
* Fits in the Java `int` primitive (four bytes).
* The value can be retrieved through methods like [IonReader.intValue]
* or [IonInt.intValue] without data loss.
*/
INT,

/**
* Fits in the Java {@code int} primitive (eight bytes).
* The value can be retrieved through methods like {@link IonReader#longValue()}
* or {@link IonInt#longValue()} without data loss.
* Fits in the Java `int` primitive (eight bytes).
* The value can be retrieved through methods like [IonReader.longValue]
* or [IonInt.longValue] without data loss.
*/
LONG,

/**
* Larger than eight bytes. This value can be retrieved through methods like
* {@link IonReader#bigIntegerValue()} or {@link IonInt#bigIntegerValue()}
* [IonReader.bigIntegerValue] or [IonInt.bigIntegerValue]
* without data loss.
*/
BIG_INTEGER,

BIG_INTEGER
}
116 changes: 116 additions & 0 deletions src/com/amazon/ion/IonExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.amazon.ion

import java.math.BigInteger
import java.time.Instant

/**
* Returns `this` value, without annotations.
* If this value has no annotations, returns `this`;
* otherwise, returns a clone of `this` with the annotations removed.
*/
inline fun <reified T : IonValue> T.withoutTypeAnnotations(): T =
if (typeAnnotations.isNotEmpty()) {
clone().apply { clearTypeAnnotations() } as T
} else {
this
}

/**
* Makes an IonValue instance read-only.
*/
fun <T : IonValue> T.markReadOnly(): T {
this.makeReadOnly()
return this
}

/**
* Create a [Timestamp] from [Instant] with millisecond precision and UTC offset.
*/
fun Instant.toTimestamp(): Timestamp = Timestamp.forMillis(this.toEpochMilli(), 0)

/**
* Convert an [Timestamp] into an [Instant], truncating any fractional seconds smaller than millisecond precision.
*/
fun Timestamp.toInstant(): Instant = Instant.ofEpochMilli(this.millis)

/**
* Kotlin-friendly extension function for building a new [IonStruct].
*/
inline fun ValueFactory.buildStruct(init: IonStruct.() -> Unit): IonStruct = newEmptyStruct().apply(init)

/**
* Kotlin-friendly extension function for building a new [IonSexp].
*/
inline fun ValueFactory.buildSexp(init: IonSexp.() -> Unit): IonSexp = newEmptySexp().apply(init)

/**
* Kotlin-friendly extension function for building a new [IonList].
*/
inline fun ValueFactory.buildList(init: IonList.() -> Unit): IonList = newEmptyList().apply(init)

/**
* Returns the value of an [IonString] or [IonSymbol], or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.stringValueOrNull(): String? = (this as? IonText)?.stringValue()

/**
* Returns the value of an [IonBlob] or [IonClob], or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.bytesValueOrNull(): ByteArray? = (this as? IonLob)?.bytes

/**
* Returns the value of an [IonBool] or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.boolValueOrNull(): Boolean? = (this as? IonBool)?.booleanValue()

/**
* Returns the value of an [IonInt] or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.bigIntValueOrNull(): BigInteger? = (this as? IonInt)?.bigIntegerValue()

/**
* Returns the value of an [IonFloat] or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.doubleValueOrNull(): Double? = (this as? IonFloat)?.doubleValue()

/**
* Returns the value of an [IonDecimal] or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.decimalValueOrNull(): Decimal? = (this as? IonDecimal)?.decimalValue()

/**
* Returns the value of an [IonTimestamp] or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.timestampValueOrNull(): Timestamp? = (this as? IonTimestamp)?.timestampValue()

/**
* Returns the child values of an [IonList] or [IonSexp] or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.childValuesOrNull(): List<IonValue>? = (this as? IonSequence)?.takeIf { !it.isNullValue }

/**
* Returns the fields of an [IonStruct] or `null` if this [IonValue] is another type or an Ion null.
*/
fun IonValue.fieldsOrNull(): List<Pair<String, IonValue>>? = (this as? IonStruct)?.takeIf { !it.isNullValue }?.map { it.fieldName to it }

/**
* If this [IonValue] is a container type, returns an iterator over the contained values. Otherwise returns an iterator
* containing this [IonValue].
*/
fun IonValue.asIterator(): Iterator<IonValue> {
return when (this) {
is IonContainer -> iterator()
else -> SingletonIterator(this)
}
}

/**
* An iterator over one value.
*/
private class SingletonIterator<E: Any>(value: E): Iterator<E> {
private var nextValue: E? = value

override fun hasNext(): Boolean = nextValue != null

override fun next(): E = nextValue.also { nextValue = null } ?: throw NoSuchElementException()
}
Loading

0 comments on commit 98ce1c9

Please sign in to comment.