Initial commit

This commit is contained in:
Peter Niederwieser
2016-01-19 14:51:19 +01:00
committed by Dan Chao
commit ecad035dca
2972 changed files with 211653 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ajalt.clikt:clikt-jvm:3.5.1=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ajalt.clikt:clikt:3.5.1=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.squareup:javapoet:1.13.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.tunnelvisionlabs:antlr4-runtime:4.9.0=default,runtimeClasspath,testRuntimeClasspath
io.leangen.geantyref:geantyref:1.3.14=testRuntimeClasspath
net.bytebuddy:byte-buddy:1.12.21=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeOnlyDependenciesMetadata
org.assertj:assertj-core:3.24.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.graalvm.sdk:graal-sdk:22.3.1=default,runtimeClasspath,testRuntimeClasspath
org.graalvm.truffle:truffle-api:22.3.1=default,runtimeClasspath,testRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.7.10=kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-reflect:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-script-runtime:1.7.10=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:1.7.10=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains:annotations:13.0=apiDependenciesMetadata,compileClasspath,default,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.junit.jupiter:junit-jupiter-engine:5.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.junit.jupiter:junit-jupiter-params:5.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.junit.platform:junit-platform-engine:1.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.junit:junit-bom:5.9.3=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath,testRuntimeOnlyDependenciesMetadata
org.organicdesign:Paguro:3.10.3=default,runtimeClasspath,testRuntimeClasspath
org.snakeyaml:snakeyaml-engine:2.5=default,runtimeClasspath,testRuntimeClasspath
empty=annotationProcessor,archives,compile,compileOnly,compileOnlyDependenciesMetadata,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDef,kotlinScriptDefExtensions,runtime,runtimeOnlyDependenciesMetadata,sourcesJar,testAnnotationProcessor,testApiDependenciesMetadata,testCompile,testCompileOnly,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDef,testKotlinScriptDefExtensions,testRuntime

View File

@@ -0,0 +1,44 @@
plugins {
pklAllProjects
pklKotlinLibrary
pklPublishLibrary
}
dependencies {
// CliJavaCodeGeneratorOptions exposes CliBaseOptions
api(project(":pkl-commons-cli"))
implementation(project(":pkl-commons"))
implementation(project(":pkl-core"))
implementation(libs.javaPoet)
testImplementation(project(":pkl-config-java"))
testImplementation(project(":pkl-commons-test"))
}
// with `org.gradle.parallel=true` and without the line below, `test` strangely runs into:
// java.lang.NoClassDefFoundError: Lorg/pkl/config/java/ConfigEvaluator;
// perhaps somehow related to InMemoryJavaCompiler?
tasks.test {
mustRunAfter(":pkl-config-java:testFatJar")
}
tasks.jar {
manifest {
attributes += mapOf("Main-Class" to "org.pkl.codegen.java.Main")
}
}
publishing {
publications {
named<MavenPublication>("library") {
pom {
url.set("https://github.com/apple/pkl/tree/main/pkl-codegen-java")
description.set("""
Java source code generator that generates corresponding Java classes for Pkl classes,
simplifying consumption of Pkl configuration as statically typed Java objects.
""".trimIndent())
}
}
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://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.pkl.codegen.java
import java.io.IOException
import org.pkl.commons.cli.CliCommand
import org.pkl.commons.cli.CliException
import org.pkl.commons.createParentDirectories
import org.pkl.commons.writeString
import org.pkl.core.ModuleSource
import org.pkl.core.module.ModuleKeyFactories
/** API for the Java code generator CLI. */
class CliJavaCodeGenerator(private val options: CliJavaCodeGeneratorOptions) :
CliCommand(options.base) {
override fun doRun() {
val builder = evaluatorBuilder()
try {
builder.build().use { evaluator ->
for (moduleUri in options.base.normalizedSourceModules) {
val schema = evaluator.evaluateSchema(ModuleSource.uri(moduleUri))
val codeGenerator = JavaCodeGenerator(schema, options.toJavaCodegenOptions())
try {
for ((fileName, fileContents) in codeGenerator.output) {
val outputFile = options.outputDir.resolve(fileName)
try {
outputFile.createParentDirectories().writeString(fileContents)
} catch (e: IOException) {
throw CliException("I/O error writing file `$outputFile`.\nCause: ${e.message}")
}
}
} catch (e: JavaCodeGeneratorException) {
throw CliException(e.message!!)
}
}
}
} finally {
ModuleKeyFactories.closeQuietly(builder.moduleKeyFactories)
}
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://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.pkl.codegen.java
import java.nio.file.Path
import org.pkl.commons.cli.CliBaseOptions
/** Configuration options for [CliJavaCodeGenerator]. */
data class CliJavaCodeGeneratorOptions(
/** Base options shared between CLI commands. */
val base: CliBaseOptions,
/** The directory where generated source code is placed. */
val outputDir: Path,
/** The characters to use for indenting generated source code. */
val indent: String = " ",
/**
* Whether to generate public getter methods and private/protected fields instead of public
* fields.
*/
val generateGetters: Boolean = false,
/** Whether to generate Javadoc based on doc comments for Pkl modules, classes, and properties. */
val generateJavadoc: Boolean = false,
/** Whether to generate config classes for use with Spring Boot. */
val generateSpringBootConfig: Boolean = false,
/**
* Fully qualified name of the annotation to use on constructor parameters. If this options is not
* set, [org.pkl.config.java.mapper.Named] will be used.
*/
val paramsAnnotation: String? = null,
/**
* Fully qualified name of the annotation to use on non-null properties. If this option is not
* set, [org.pkl.config.java.mapper.NonNull] will be used.
*/
val nonNullAnnotation: String? = null,
/** Whether to make generated classes implement [java.io.Serializable] */
val implementSerializable: Boolean = false
) {
fun toJavaCodegenOptions() =
JavaCodegenOptions(
indent,
generateGetters,
generateJavadoc,
generateSpringBootConfig,
paramsAnnotation,
nonNullAnnotation,
implementSerializable
)
}

View File

@@ -0,0 +1,933 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://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.pkl.codegen.java
import com.squareup.javapoet.*
import java.io.StringWriter
import java.lang.Deprecated
import java.net.URI
import java.util.*
import java.util.regex.Pattern
import javax.lang.model.element.Modifier
import kotlin.AssertionError
import kotlin.Boolean
import kotlin.Int
import kotlin.Long
import kotlin.RuntimeException
import kotlin.String
import kotlin.Suppress
import kotlin.Unit
import kotlin.apply
import kotlin.let
import kotlin.takeIf
import kotlin.to
import org.pkl.core.*
import org.pkl.core.util.CodeGeneratorUtils
class JavaCodeGeneratorException(message: String) : RuntimeException(message)
data class JavaCodegenOptions(
/** The characters to use for indenting generated Java code. */
val indent: String = " ",
/**
* Whether to generate public getter methods and protected final fields instead of public final
* fields.
*/
val generateGetters: Boolean = false,
/** Whether to generate Javadoc based on doc comments for Pkl modules, classes, and properties. */
val generateJavadoc: Boolean = false,
/** Whether to generate config classes for use with Spring Boot. */
val generateSpringBootConfig: Boolean = false,
/**
* Fully qualified name of the annotation to use on constructor parameters. If this options is not
* set, [org.pkl.config.java.mapper.Named] will be used.
*/
val paramsAnnotation: String? = null,
/**
* Fully qualified name of the annotation to use on non-null properties. If this option is not
* set, [org.pkl.config.java.mapper.NonNull] will be used.
*/
val nonNullAnnotation: String? = null,
/** Whether to make generated classes implement [java.io.Serializable] */
val implementSerializable: Boolean = false
)
/** Entrypoint for the Java code generator API. */
class JavaCodeGenerator(
private val schema: ModuleSchema,
private val codegenOptions: JavaCodegenOptions
) {
companion object {
private val STRING = ClassName.get(String::class.java)
private val DURATION = ClassName.get(Duration::class.java)
private val DURATION_UNIT = ClassName.get(DurationUnit::class.java)
private val DATA_SIZE = ClassName.get(DataSize::class.java)
private val DATASIZE_UNIT = ClassName.get(DataSizeUnit::class.java)
private val PAIR = ClassName.get(Pair::class.java)
private val COLLECTION = ClassName.get(Collection::class.java)
private val LIST = ClassName.get(List::class.java)
private val SET = ClassName.get(Set::class.java)
private val MAP = ClassName.get(Map::class.java)
private val PMODULE = ClassName.get(PModule::class.java)
private val PCLASS = ClassName.get(PClass::class.java)
private val PATTERN = ClassName.get(Pattern::class.java)
private val URI = ClassName.get(URI::class.java)
private val VERSION = ClassName.get(Version::class.java)
private const val PROPERTY_PREFIX: String = "org.pkl.config.java.mapper."
private fun toClassName(fqn: String): ClassName {
val index = fqn.lastIndexOf(".")
if (index == -1) {
throw JavaCodeGeneratorException(
"""
Annotation `$fqn` is not a valid Java class.
The name of the annotation should be the canonical Java name of the class, for example, `com.example.Foo`.
"""
.trimIndent()
)
}
val packageName = fqn.substring(0, index)
val classParts = fqn.substring(index + 1).split('$')
return if (classParts.size == 1) {
ClassName.get(packageName, classParts.first())
} else {
ClassName.get(packageName, classParts.first(), *classParts.drop(1).toTypedArray())
}
}
}
val output: Map<String, String>
get() {
return mapOf(javaFileName to javaFile, propertyFileName to propertiesFile)
}
private val propertyFileName: String
get() = "resources/META-INF/org/pkl/config/java/mapper/classes/${schema.moduleName}.properties"
private val propertiesFile: String
get() {
val props = Properties()
props["$PROPERTY_PREFIX${schema.moduleClass.qualifiedName}"] =
schema.moduleClass.toJavaPoetName().reflectionName()
for (pClass in schema.classes.values) {
props["$PROPERTY_PREFIX${pClass.qualifiedName}"] = pClass.toJavaPoetName().reflectionName()
}
return StringWriter()
.apply { props.store(this, "Java mappings for Pkl module `${schema.moduleName}`") }
.toString()
}
private val nonNullAnnotation: AnnotationSpec
get() {
val annotation = codegenOptions.nonNullAnnotation
val className =
if (annotation == null) {
ClassName.get("org.pkl.config.java.mapper", "NonNull")
} else {
toClassName(annotation)
}
return AnnotationSpec.builder(className).build()
}
val javaFileName: String
get() = relativeOutputPathFor(schema.moduleName)
val javaFile: String
get() {
if (schema.moduleUri.scheme == "pkl") {
throw JavaCodeGeneratorException(
"Cannot generate Java code for a Pkl standard library module (`${schema.moduleUri}`)."
)
}
val pModuleClass = schema.moduleClass
val moduleClass = generateTypeSpec(pModuleClass, schema)
for (pClass in schema.classes.values) {
moduleClass.addType(generateTypeSpec(pClass, schema).build())
}
for (typeAlias in schema.typeAliases.values) {
val stringLiterals = mutableSetOf<String>()
if (CodeGeneratorUtils.isRepresentableAsEnum(typeAlias.aliasedType, stringLiterals)) {
moduleClass.addType(generateEnumTypeSpec(typeAlias, stringLiterals).build())
}
}
// generate static append method for module classes w/o parent class; reuse in subclasses and
// nested classes
if (pModuleClass.superclass!!.info == PClassInfo.Module) {
val modifier =
if (pModuleClass.isOpen || pModuleClass.isAbstract) Modifier.PROTECTED
else Modifier.PRIVATE
moduleClass.addMethod(appendPropertyMethod().addModifiers(modifier).build())
}
val moduleName = schema.moduleName
val index = moduleName.lastIndexOf(".")
val packageName = if (index == -1) "" else moduleName.substring(0, index)
return JavaFile.builder(packageName, moduleClass.build())
.indent(codegenOptions.indent)
.build()
.toString()
}
private fun relativeOutputPathFor(moduleName: String): String {
val moduleNameParts = moduleName.split(".")
val dirPath = moduleNameParts.dropLast(1).joinToString("/")
val fileName = moduleNameParts.last().replaceFirstChar { it.titlecaseChar() }
return if (dirPath.isEmpty()) {
"java/$fileName.java"
} else {
"java/$dirPath/$fileName.java"
}
}
@Suppress("NAME_SHADOWING")
private fun generateTypeSpec(pClass: PClass, schema: ModuleSchema): TypeSpec.Builder {
val isModuleClass = pClass == schema.moduleClass
val javaPoetClassName = pClass.toJavaPoetName()
val superclass =
pClass.superclass?.takeIf { it.info != PClassInfo.Typed && it.info != PClassInfo.Module }
val superProperties =
superclass?.let { renameIfReservedWord(it.allProperties) }?.filterValues { !it.isHidden }
?: mapOf()
val properties = renameIfReservedWord(pClass.properties).filterValues { !it.isHidden }
val allProperties = superProperties + properties
fun addCtorParameter(
builder: MethodSpec.Builder,
propJavaName: String,
property: PClass.Property
) {
builder.addParameter(
ParameterSpec.builder(property.type.toJavaPoetName(), propJavaName)
.addAnnotation(
AnnotationSpec.builder(namedAnnotationName)
.addMember("value", "\$S", property.simpleName)
.build()
)
.build()
)
}
fun generateConstructor(): MethodSpec {
val builder =
MethodSpec.constructorBuilder()
// choose most restrictive access modifier possible
.addModifiers(
when {
pClass.isAbstract -> Modifier.PROTECTED
allProperties.isNotEmpty() -> Modifier.PUBLIC // if `false`, has no state
pClass.isOpen -> Modifier.PROTECTED
else -> Modifier.PRIVATE
}
)
if (superProperties.isNotEmpty()) {
for ((name, property) in superProperties) {
if (properties.containsKey(name)) continue
addCtorParameter(builder, name, property)
}
// $W inserts space or newline (automatic line wrapping)
val callArgsStr = superProperties.keys.joinToString(",\$W")
// use kotlin interpolation rather than javapoet $L interpolation
// as otherwise the $W won't get processed
builder.addStatement("super($callArgsStr)")
}
for ((name, property) in properties) {
addCtorParameter(builder, name, property)
builder.addStatement("this.\$N = \$N", name, name)
}
return builder.build()
}
fun generateEqualsMethod(): MethodSpec {
val builder =
MethodSpec.methodBuilder("equals")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override::class.java)
.addParameter(Object::class.java, "obj")
.returns(Boolean::class.java)
.addStatement("if (this == obj) return true")
.addStatement("if (obj == null) return false")
// generating this.getClass() instead of class literal avoids a SpotBugs warning
.addStatement("if (this.getClass() != obj.getClass()) return false")
.addStatement("\$T other = (\$T) obj", javaPoetClassName, javaPoetClassName)
for ((propertyName, property) in allProperties) {
val accessor =
if ((property.type as? PType.Class)?.pClass?.info == PClassInfo.Regex) "\$N.pattern()"
else "\$N"
builder.addStatement(
"if (!\$T.equals(this.$accessor, other.$accessor)) return false",
Objects::class.java,
propertyName,
propertyName
)
}
builder.addStatement("return true")
return builder.build()
}
fun generateHashCodeMethod(): MethodSpec {
val builder =
MethodSpec.methodBuilder("hashCode")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override::class.java)
.returns(Int::class.java)
.addStatement("int result = 1")
for (propertyName in allProperties.keys) {
builder.addStatement(
"result = 31 * result + \$T.hashCode(this.\$N)",
Objects::class.java,
propertyName
)
}
builder.addStatement("return result")
return builder.build()
}
fun generateToStringMethod(): MethodSpec {
val builder =
MethodSpec.methodBuilder("toString")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override::class.java)
.returns(String::class.java)
var builderSize = 50
val appendBuilder = CodeBlock.builder()
for (propertyName in allProperties.keys) {
builderSize += 50
appendBuilder.addStatement(
"appendProperty(builder, \$S, this.\$N)",
propertyName,
propertyName
)
}
builder
.addStatement(
"\$T builder = new \$T(\$L)",
StringBuilder::class.java,
StringBuilder::class.java,
builderSize
)
.addStatement("builder.append(\$T.class.getSimpleName()).append(\" {\")", javaPoetClassName)
.addCode(appendBuilder.build())
// not using $S here because it generates `"\n" + "{"`
// with a line break in the generated code after `+`
.addStatement("builder.append(\"\\n}\")")
.addStatement("return builder.toString()")
return builder.build()
}
// do the minimum work necessary to avoid (most) java compile errors
// generating idiomatic Javadoc would require parsing doc comments, converting member links,
// etc.
fun renderAsJavadoc(docComment: String): String {
val escaped = docComment.replace("*/", "*&#47;")
return if (escaped[escaped.length - 1] != '\n') escaped + '\n' else escaped
}
fun generateDeprecation(
annotations: Collection<PObject>,
hasJavadoc: Boolean,
addAnnotation: (Class<*>) -> Unit,
addJavadoc: (String) -> Unit
) {
annotations
.firstOrNull { it.classInfo == PClassInfo.Deprecated }
?.let { deprecation ->
addAnnotation(Deprecated::class.java)
if (codegenOptions.generateJavadoc) {
(deprecation["message"] as String?)?.let {
if (hasJavadoc) {
addJavadoc("\n")
}
addJavadoc(renderAsJavadoc("@deprecated $it"))
}
}
}
}
fun generateField(propertyName: String, property: PClass.Property): FieldSpec {
val builder = FieldSpec.builder(property.type.toJavaPoetName(), propertyName)
val docComment = property.docComment
val hasJavadoc =
docComment != null && codegenOptions.generateJavadoc && !codegenOptions.generateGetters
if (hasJavadoc) {
builder.addJavadoc(renderAsJavadoc(docComment!!))
}
if (codegenOptions.generateGetters) {
builder.addModifiers(
if (pClass.isAbstract || pClass.isOpen) Modifier.PROTECTED else Modifier.PRIVATE
)
} else {
generateDeprecation(
property.annotations,
hasJavadoc,
{ builder.addAnnotation(it) },
{ builder.addJavadoc(it) }
)
builder.addModifiers(Modifier.PUBLIC)
}
builder.addModifiers(Modifier.FINAL)
return builder.build()
}
@Suppress("DuplicatedCode")
fun generateGetter(
propertyName: String,
property: PClass.Property,
isOverridden: Boolean
): MethodSpec {
val propertyType = property.type
val isBooleanProperty =
propertyType is PType.Class && propertyType.pClass.info == PClassInfo.Boolean
val methodName =
(if (isBooleanProperty) "is" else "get") +
// can use original name here (property.name rather than propertyName)
// because getter name cannot possibly conflict with reserved words
property.simpleName.replaceFirstChar { it.titlecaseChar() }
val builder =
MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.PUBLIC)
.returns(propertyType.toJavaPoetName())
.addStatement("return \$N", propertyName)
if (isOverridden) {
builder.addAnnotation(Override::class.java)
}
val docComment = property.docComment
val hasJavadoc = docComment != null && codegenOptions.generateJavadoc
if (hasJavadoc) {
builder.addJavadoc(renderAsJavadoc(docComment!!))
}
generateDeprecation(
property.annotations,
hasJavadoc,
{ builder.addAnnotation(it) },
{ builder.addJavadoc(it) }
)
return builder.build()
}
fun generateWithMethod(propertyName: String, property: PClass.Property): MethodSpec {
val methodName = "with" + property.simpleName.replaceFirstChar { it.titlecaseChar() }
val methodBuilder =
MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.PUBLIC)
.addParameter(property.type.toJavaPoetName(), propertyName)
.returns(javaPoetClassName)
generateDeprecation(
property.annotations,
false,
{ methodBuilder.addAnnotation(it) },
{ methodBuilder.addJavadoc(it) }
)
val codeBuilder = CodeBlock.builder()
codeBuilder.add("return new \$T(", javaPoetClassName)
var firstProperty = true
for (name in superProperties.keys) {
if (name in properties) continue
if (firstProperty) {
firstProperty = false
codeBuilder.add("\$N", name)
} else {
codeBuilder.add(", \$N", name)
}
}
for (name in properties.keys) {
if (firstProperty) {
firstProperty = false
codeBuilder.add("\$N", name)
} else {
codeBuilder.add(", \$N", name)
}
}
codeBuilder.add(");\n")
methodBuilder.addCode(codeBuilder.build())
return methodBuilder.build()
}
fun generateSpringBootAnnotations(builder: TypeSpec.Builder) {
builder.addAnnotation(
ClassName.get("org.springframework.boot.context.properties", "ConstructorBinding")
)
if (isModuleClass) {
builder.addAnnotation(
ClassName.get("org.springframework.boot.context.properties", "ConfigurationProperties")
)
} else {
// not very efficient to repeat computing module property base types for every class
val modulePropertiesWithMatchingType =
schema.moduleClass.allProperties.values.filter { property ->
var propertyType = property.type
while (propertyType is PType.Constrained || propertyType is PType.Nullable) {
if (propertyType is PType.Constrained) {
propertyType = propertyType.baseType
} else if (propertyType is PType.Nullable) {
propertyType = propertyType.baseType
}
}
propertyType is PType.Class && propertyType.pClass == pClass
}
if (modulePropertiesWithMatchingType.size == 1) {
// exactly one module property has this type -> make it available for direct injection
// (potential improvement: make type available for direct injection if it occurs exactly
// once in property tree)
builder.addAnnotation(
AnnotationSpec.builder(
ClassName.get(
"org.springframework.boot.context.properties",
"ConfigurationProperties"
)
)
// use "value" instead of "prefix" to entice JavaPoet to generate a single-line
// annotation
// that can easily be filtered out by JavaCodeGeneratorTest.`spring boot config`
.addMember("value", "\$S", modulePropertiesWithMatchingType.first().simpleName)
.build()
)
}
}
}
@Suppress("DuplicatedCode")
fun generateClass(): TypeSpec.Builder {
val builder =
TypeSpec.classBuilder(javaPoetClassName.simpleName()).addModifiers(Modifier.PUBLIC)
if (codegenOptions.implementSerializable && !isModuleClass) {
builder.addSuperinterface(java.io.Serializable::class.java)
builder.addField(generateSerialVersionUIDField())
}
val docComment = pClass.docComment
val hasJavadoc = docComment != null && codegenOptions.generateJavadoc
if (hasJavadoc) {
builder.addJavadoc(renderAsJavadoc(docComment!!))
}
generateDeprecation(
pClass.annotations,
hasJavadoc,
{ builder.addAnnotation(it) },
{ builder.addJavadoc(it) }
)
if (!isModuleClass) {
builder.addModifiers(Modifier.STATIC)
}
if (pClass.isAbstract) {
builder.addModifiers(Modifier.ABSTRACT)
} else if (!pClass.isOpen) {
builder.addModifiers(Modifier.FINAL)
}
if (codegenOptions.generateSpringBootConfig) {
generateSpringBootAnnotations(builder)
}
builder.addMethod(generateConstructor())
superclass?.let { builder.superclass(it.toJavaPoetName()) }
// generate fields, plus getter methods and either setters or `with` methods in alternating
// order
// `with` methods also need to be generated for superclass properties so that return type is
// self type
for ((name, property) in allProperties) {
if (name in properties) {
builder.addField(generateField(name, property))
if (codegenOptions.generateGetters) {
val isOverridden = name in superProperties
builder.addMethod(generateGetter(name, property, isOverridden))
}
}
if (!pClass.isAbstract) {
builder.addMethod(generateWithMethod(name, property))
}
}
if (properties.isNotEmpty()) {
builder
.addMethod(generateEqualsMethod())
.addMethod(generateHashCodeMethod())
.addMethod(generateToStringMethod())
}
return builder
}
return generateClass()
}
private fun generateSerialVersionUIDField(): FieldSpec {
return FieldSpec.builder(Long::class.java, "serialVersionUID", Modifier.PRIVATE)
.addModifiers(Modifier.STATIC, Modifier.FINAL)
.initializer("0L")
.build()
}
private fun generateEnumTypeSpec(
typeAlias: TypeAlias,
stringLiterals: Set<String>
): TypeSpec.Builder {
val enumConstantToPklNames =
stringLiterals
.groupingBy { literal ->
CodeGeneratorUtils.toEnumConstantName(literal)
?: throw JavaCodeGeneratorException(
"Cannot generate Java enum class for Pkl type alias `${typeAlias.displayName}` " +
"because string literal type \"$literal\" cannot be converted to a valid enum constant name."
)
}
.reduce { enumConstantName, firstLiteral, secondLiteral ->
throw JavaCodeGeneratorException(
"Cannot generate Java enum class for Pkl type alias `${typeAlias.displayName}` " +
"because string literal types \"$firstLiteral\" and \"$secondLiteral\" " +
"would both be converted to enum constant name `$enumConstantName`."
)
}
val builder =
TypeSpec.enumBuilder(typeAlias.simpleName)
.addModifiers(Modifier.PUBLIC)
.addField(String::class.java, "value", Modifier.PRIVATE)
.addMethod(
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PRIVATE)
.addParameter(String::class.java, "value")
.addStatement("this.value = value")
.build()
)
.addMethod(
MethodSpec.methodBuilder("toString")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override::class.java)
.returns(String::class.java)
.addStatement("return this.value")
.build()
)
for ((enumConstantName, pklName) in enumConstantToPklNames) {
builder.addEnumConstant(
enumConstantName,
TypeSpec.anonymousClassBuilder("\$S", pklName).build()
)
}
return builder
}
private val namedAnnotationName =
if (codegenOptions.paramsAnnotation != null) {
toClassName(codegenOptions.paramsAnnotation)
} else {
ClassName.get("org.pkl.config.java.mapper", "Named")
}
private fun appendPropertyMethod() =
MethodSpec.methodBuilder("appendProperty")
.addModifiers(Modifier.STATIC)
.addParameter(StringBuilder::class.java, "builder")
.addParameter(String::class.java, "name")
.addParameter(Object::class.java, "value")
.addStatement("builder.append(\"\\n \").append(name).append(\" = \")")
.addStatement(
"\$T lines = \$T.toString(value).split(\"\\n\")",
ArrayTypeName.of(String::class.java),
Objects::class.java
)
.addStatement("builder.append(lines[0])")
.beginControlFlow("for (int i = 1; i < lines.length; i++)")
.addStatement("builder.append(\"\\n \").append(lines[i])")
.endControlFlow()
private fun PClass.toJavaPoetName(): ClassName {
val index = moduleName.lastIndexOf(".")
val packageName = if (index == -1) "" else moduleName.substring(0, index)
val moduleClassName = moduleName.substring(index + 1).replaceFirstChar { it.titlecaseChar() }
return if (isModuleClass) {
ClassName.get(packageName, moduleClassName)
} else {
ClassName.get(packageName, moduleClassName, simpleName)
}
}
// generated type is a nested enum class
private fun TypeAlias.toJavaPoetName(): ClassName {
val index = moduleName.lastIndexOf(".")
val packageName = if (index == -1) "" else moduleName.substring(0, index)
val moduleClassName = moduleName.substring(index + 1).replaceFirstChar { it.titlecaseChar() }
return ClassName.get(packageName, moduleClassName, simpleName)
}
/** Generate `List<? extends Foo>` if `Foo` is `abstract` or `open`, to allow subclassing. */
private fun PType.toJavaPoetTypeArgumentName(): TypeName {
val baseName = toJavaPoetName(nullable = false, boxed = true)
return if (this is PType.Class && (pClass.isAbstract || pClass.isOpen)) {
WildcardTypeName.subtypeOf(baseName)
} else {
baseName
}
}
private fun PType.toJavaPoetName(nullable: Boolean = false, boxed: Boolean = false): TypeName =
when (this) {
PType.UNKNOWN -> TypeName.OBJECT.nullableIf(nullable)
PType.NOTHING -> TypeName.VOID
is PType.StringLiteral -> STRING.nullableIf(nullable)
is PType.Class -> {
// if in doubt, spell it out
when (val classInfo = pClass.info) {
PClassInfo.Any -> TypeName.OBJECT
PClassInfo.Typed,
PClassInfo.Dynamic -> TypeName.OBJECT.nullableIf(nullable)
PClassInfo.Boolean -> TypeName.BOOLEAN.boxIf(boxed).nullableIf(nullable)
PClassInfo.String -> STRING.nullableIf(nullable)
// seems more useful to generate `double` than `java.lang.Number`
PClassInfo.Number -> TypeName.DOUBLE.boxIf(boxed).nullableIf(nullable)
PClassInfo.Int -> TypeName.LONG.boxIf(boxed).nullableIf(nullable)
PClassInfo.Float -> TypeName.DOUBLE.boxIf(boxed).nullableIf(nullable)
PClassInfo.Duration -> DURATION.nullableIf(nullable)
PClassInfo.DataSize -> DATA_SIZE.nullableIf(nullable)
PClassInfo.Pair ->
ParameterizedTypeName.get(
PAIR,
if (typeArguments.isEmpty()) {
TypeName.OBJECT
} else {
typeArguments[0].toJavaPoetTypeArgumentName()
},
if (typeArguments.isEmpty()) {
TypeName.OBJECT
} else {
typeArguments[1].toJavaPoetTypeArgumentName()
}
)
.nullableIf(nullable)
PClassInfo.Collection ->
ParameterizedTypeName.get(
COLLECTION,
if (typeArguments.isEmpty()) {
TypeName.OBJECT
} else {
typeArguments[0].toJavaPoetTypeArgumentName()
}
)
.nullableIf(nullable)
PClassInfo.List,
PClassInfo.Listing -> {
ParameterizedTypeName.get(
LIST,
if (typeArguments.isEmpty()) {
TypeName.OBJECT
} else {
typeArguments[0].toJavaPoetTypeArgumentName()
}
)
.nullableIf(nullable)
}
PClassInfo.Set ->
ParameterizedTypeName.get(
SET,
if (typeArguments.isEmpty()) {
TypeName.OBJECT
} else {
typeArguments[0].toJavaPoetTypeArgumentName()
}
)
.nullableIf(nullable)
PClassInfo.Map,
PClassInfo.Mapping ->
ParameterizedTypeName.get(
MAP,
if (typeArguments.isEmpty()) {
TypeName.OBJECT
} else {
typeArguments[0].toJavaPoetTypeArgumentName()
},
if (typeArguments.isEmpty()) {
TypeName.OBJECT
} else {
typeArguments[1].toJavaPoetTypeArgumentName()
}
)
.nullableIf(nullable)
PClassInfo.Module -> PMODULE.nullableIf(nullable)
PClassInfo.Class -> PCLASS.nullableIf(nullable)
PClassInfo.Regex -> PATTERN.nullableIf(nullable)
PClassInfo.Version -> VERSION.nullableIf(nullable)
else ->
when {
!classInfo.isStandardLibraryClass -> pClass.toJavaPoetName().nullableIf(nullable)
else ->
throw JavaCodeGeneratorException(
"Standard library class `${pClass.qualifiedName}` is not supported by Java code generator. " +
"If you think this is an omission, please let us know."
)
}
}
}
is PType.Nullable -> baseType.toJavaPoetName(nullable = true, boxed = true)
is PType.Constrained -> baseType.toJavaPoetName(nullable = nullable, boxed = boxed)
is PType.Alias ->
when (typeAlias.qualifiedName) {
"pkl.base#NonNull" -> TypeName.OBJECT.nullableIf(nullable)
"pkl.base#Int8" -> TypeName.BYTE.boxIf(boxed).nullableIf(nullable)
"pkl.base#Int16",
"pkl.base#UInt8" -> TypeName.SHORT.boxIf(boxed).nullableIf(nullable)
"pkl.base#Int32",
"pkl.base#UInt16" -> TypeName.INT.boxIf(boxed).nullableIf(nullable)
"pkl.base#UInt",
"pkl.base#UInt32" -> TypeName.LONG.boxIf(boxed).nullableIf(nullable)
"pkl.base#DurationUnit" -> DURATION_UNIT.nullableIf(nullable)
"pkl.base#DataSizeUnit" -> DATASIZE_UNIT.nullableIf(nullable)
"pkl.base#Uri" -> URI.nullableIf(nullable)
else -> {
if (CodeGeneratorUtils.isRepresentableAsEnum(aliasedType, null)) {
if (typeAlias.isStandardLibraryMember) {
throw JavaCodeGeneratorException(
"Standard library typealias `${typeAlias.qualifiedName}` is not supported by Java code generator. " +
"If you think this is an omission, please let us know."
)
} else {
// reference generated enum class
typeAlias.toJavaPoetName().nullableIf(nullable)
}
} else {
// inline type alias
aliasedType.toJavaPoetName(nullable)
}
}
}
is PType.Function ->
throw JavaCodeGeneratorException(
"Pkl function types are not supported by the Java code generator."
)
is PType.Union ->
if (CodeGeneratorUtils.isRepresentableAsString(this)) STRING.nullableIf(nullable)
else
throw JavaCodeGeneratorException(
"Pkl union types are not supported by the Java code generator."
)
else ->
// should never encounter PType.TypeVariableNode because it can only occur in stdlib classes
throw AssertionError("Encountered unexpected PType subclass: $this")
}
private fun TypeName.nullableIf(isNullable: Boolean): TypeName =
if (isPrimitive && isNullable) box()
else if (isPrimitive || isNullable) this else annotated(nonNullAnnotation)
private fun TypeName.boxIf(shouldBox: Boolean): TypeName = if (shouldBox) box() else this
private fun <T> renameIfReservedWord(map: Map<String, T>): Map<String, T> {
return map.mapKeys { (key, _) ->
if (key in javaReservedWords) {
generateSequence("_$key") { "_$it" }.first { it !in map.keys }
} else key
}
}
}
internal val javaReservedWords =
setOf(
"_", // java 9+
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"double",
"do",
"else",
"enum",
"extends",
"false",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"true",
"try",
"void",
"volatile",
"while"
)

View File

@@ -0,0 +1,126 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://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:JvmName("Main")
package org.pkl.codegen.java
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.path
import java.nio.file.Path
import org.pkl.commons.cli.CliBaseOptions
import org.pkl.commons.cli.cliMain
import org.pkl.commons.cli.commands.ModulesCommand
import org.pkl.commons.toPath
import org.pkl.core.Release
/** Main method for the Java code generator CLI. */
internal fun main(args: Array<String>) {
cliMain { PklJavaCodegenCommand().main(args) }
}
class PklJavaCodegenCommand :
ModulesCommand(
name = "pkl-codegen-java",
helpLink = Release.current().documentation().homepage(),
) {
private val defaults = CliJavaCodeGeneratorOptions(CliBaseOptions(), "".toPath())
private val outputDir: Path by
option(
names = arrayOf("-o", "--output-dir"),
metavar = "<path>",
help = "The directory where generated source code is placed."
)
.path()
.default(defaults.outputDir)
private val indent: String by
option(
names = arrayOf("--indent"),
metavar = "<chars>",
help = "The characters to use for indenting generated source code."
)
.default(defaults.indent)
private val generateGetters: Boolean by
option(
names = arrayOf("--generate-getters"),
help =
"Whether to generate public getter methods and " +
"private final fields instead of public final fields."
)
.flag()
private val generateJavadoc: Boolean by
option(
names = arrayOf("--generate-javadoc"),
help =
"Whether to generate Javadoc based on doc comments " +
"for Pkl modules, classes, and properties."
)
.flag()
private val generateSpringboot: Boolean by
option(
names = arrayOf("--generate-spring-boot"),
help = "Whether to generate config classes for use with Spring boot."
)
.flag()
private val paramsAnnotation: String? by
option(
names = arrayOf("--params-annotation"),
help = "Fully qualified name of the annotation to use on constructor parameters."
)
private val nonNullAnnotation: String? by
option(
names = arrayOf("--non-null-annotation"),
help =
"""
Fully qualified named of the annotation class to use for non-null types.
This annotation is required to have `java.lang.annotation.ElementType.TYPE_USE` as a `@Target`
or it may generate code that does not compile.
"""
.trimIndent()
)
private val implementSerializable: Boolean by
option(
names = arrayOf("--implement-serializable"),
help = "Whether to make generated classes implement java.io.Serializable."
)
.flag()
override fun run() {
val options =
CliJavaCodeGeneratorOptions(
base = baseOptions.baseOptions(modules, projectOptions),
outputDir = outputDir,
indent = indent,
generateGetters = generateGetters,
generateJavadoc = generateJavadoc,
generateSpringBootConfig = generateSpringboot,
paramsAnnotation = paramsAnnotation,
nonNullAnnotation = nonNullAnnotation,
implementSerializable = implementSerializable
)
CliJavaCodeGenerator(options).run()
}
}

View File

@@ -0,0 +1,184 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://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.pkl.codegen.java
import java.nio.file.Path
import kotlin.io.path.listDirectoryEntries
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import org.pkl.commons.cli.CliBaseOptions
import org.pkl.commons.readString
class CliJavaCodeGeneratorTest {
private val dollar = "$"
@Test
fun `module inheritance`(@TempDir tempDir: Path) {
val module1 =
PklModule(
"org.mod1",
"""
open module org.mod1
pigeon: Person
class Person {
name: String
age: Int
}
"""
)
val module2 =
PklModule(
"org.mod2",
"""
module org.mod2
extends "mod1.pkl"
parrot: Person
"""
)
val module1File = module1.writeToDisk(tempDir.resolve("org/mod1.pkl"))
val module2File = module2.writeToDisk(tempDir.resolve("org/mod2.pkl"))
val outputDir = tempDir.resolve("output")
val generator =
CliJavaCodeGenerator(
CliJavaCodeGeneratorOptions(
CliBaseOptions(listOf(module1File.toUri(), module2File.toUri())),
outputDir
)
)
generator.run()
val javaDir = outputDir.resolve("java")
val moduleJavaFiles = javaDir.resolve("org").listDirectoryEntries()
assertThat(moduleJavaFiles.map { it.fileName.toString() })
.containsExactlyInAnyOrder("Mod1.java", "Mod2.java")
val module1JavaFile = javaDir.resolve("org/Mod1.java")
assertContains(
"""
|public class Mod1 {
| public final @NonNull Person pigeon;
""",
module1JavaFile.readString()
)
val module2JavaFile = javaDir.resolve("org/Mod2.java")
assertContains(
"""
|public final class Mod2 extends Mod1 {
| public final Mod1. @NonNull Person parrot;
""",
module2JavaFile.readString()
)
val resourcesDir = outputDir.resolve("resources/META-INF/org/pkl/config/java/mapper/classes/")
val module1PropertiesFile = resourcesDir.resolve("org.mod1.properties")
assertContains(
"""
org.pkl.config.java.mapper.org.mod1\#Person=org.Mod1${dollar}Person
org.pkl.config.java.mapper.org.mod1\#ModuleClass=org.Mod1
"""
.trimIndent(),
module1PropertiesFile.readString()
)
val module2PropertiesFile = resourcesDir.resolve("org.mod2.properties")
assertContains(
"""
org.pkl.config.java.mapper.org.mod2\#ModuleClass=org.Mod2
"""
.trimIndent(),
module2PropertiesFile.readString()
)
}
@Test
fun `class name clashes`(@TempDir tempDir: Path) {
val module1 =
PklModule(
"org.mod1",
"""
module org.mod1
class Person {
name: String
}
"""
)
val module2 =
PklModule(
"org.mod2",
"""
module org.mod2
import "mod1.pkl"
person1: mod1.Person
person2: Person
class Person {
age: Int
}
"""
)
val module1PklFile = module1.writeToDisk(tempDir.resolve("org/mod1.pkl"))
val module2PklFile = module2.writeToDisk(tempDir.resolve("org/mod2.pkl"))
val outputDir = tempDir.resolve("output")
val generator =
CliJavaCodeGenerator(
CliJavaCodeGeneratorOptions(
CliBaseOptions(listOf(module1PklFile.toUri(), module2PklFile.toUri())),
outputDir
)
)
generator.run()
val module2JavaFile = outputDir.resolve("java/org/Mod2.java")
assertContains(
"""
|public final class Mod2 {
| public final Mod1. @NonNull Person person1;
|
| public final @NonNull Person person2;
""",
module2JavaFile.readString()
)
}
private fun assertContains(part: String, code: String) {
val trimmedPart = part.trim().trimMargin()
if (!code.contains(trimmedPart)) {
// check for equality to get better error output (ide diff dialog)
assertThat(code).isEqualTo(trimmedPart)
}
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://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.pkl.codegen.java
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.net.URI
import javax.tools.*
class CompilationFailedException(msg: String) : RuntimeException(msg)
object InMemoryJavaCompiler {
fun compile(sourceFiles: Map<String, String>): Map<String, Class<*>> {
val compiler = ToolProvider.getSystemJavaCompiler()
val diagnosticsCollector = DiagnosticCollector<JavaFileObject>()
val fileManager =
InMemoryFileManager(compiler.getStandardFileManager(diagnosticsCollector, null, null))
val sourceObjects =
sourceFiles.map { (filename, contents) -> ReadableSourceFileObject(filename, contents) }
val task = compiler.getTask(null, fileManager, diagnosticsCollector, null, null, sourceObjects)
val result = task.call()
if (!result) {
throw CompilationFailedException(
buildString {
appendLine("Compilation failed. Error(s):")
for (diagnostic in diagnosticsCollector.diagnostics) {
appendLine(diagnostic.getMessage(null))
}
}
)
}
val loader = ClassFileObjectLoader(fileManager.outputFiles)
return fileManager.outputFiles.mapValues { loader.loadClass(it.key) }
}
}
private class ClassFileObjectLoader(val fileObjects: Map<String, WritableBinaryFileObject>) :
ClassLoader(ClassFileObjectLoader::class.java.classLoader) {
override fun findClass(name: String): Class<*> {
val obj = fileObjects[name]
if (obj == null || obj.kind != JavaFileObject.Kind.CLASS) {
throw ClassNotFoundException(name)
}
val array = obj.out.toByteArray()
return defineClass(name, array, 0, array.size)
}
}
private class ReadableSourceFileObject(path: String, private val contents: String) :
SimpleJavaFileObject(URI(path), JavaFileObject.Kind.SOURCE) {
override fun openInputStream(): InputStream = contents.byteInputStream()
override fun getCharContent(ignoreEncodingErrors: Boolean): CharSequence = contents
}
private class WritableBinaryFileObject(className: String, kind: JavaFileObject.Kind) :
SimpleJavaFileObject(URI("/${className.replace(".", "/")}.${kind.extension}"), kind) {
val out = ByteArrayOutputStream()
override fun openOutputStream(): OutputStream = out
}
private class InMemoryFileManager(delegate: JavaFileManager) :
ForwardingJavaFileManager<JavaFileManager>(delegate) {
val outputFiles = mutableMapOf<String, WritableBinaryFileObject>()
override fun getJavaFileForOutput(
location: JavaFileManager.Location,
className: String,
kind: JavaFileObject.Kind,
sibling: FileObject
): JavaFileObject {
return WritableBinaryFileObject(className, kind).also { outputFiles[className] = it }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://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.pkl.codegen.java
import java.nio.file.Path
import org.pkl.commons.createParentDirectories
import org.pkl.commons.writeString
data class PklModule(val name: String, val content: String) {
fun writeToDisk(path: Path): Path {
return path.createParentDirectories().writeString(content)
}
}

View File

@@ -0,0 +1,125 @@
package my;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.Objects;
import org.pkl.config.java.mapper.Named;
import org.pkl.config.java.mapper.NonNull;
import org.pkl.core.DataSize;
import org.pkl.core.Duration;
public final class Mod {
private Mod() {
}
private static void appendProperty(StringBuilder builder, String name, Object value) {
builder.append("\n ").append(name).append(" = ");
String[] lines = Objects.toString(value).split("\n");
builder.append(lines[0]);
for (int i = 1; i < lines.length; i++) {
builder.append("\n ").append(lines[i]);
}
}
public static final class GenerateGetters {
private final boolean urgent;
private final @NonNull String url;
private final @NonNull DataSize diskSize;
private final @NonNull Duration ETA;
private final @NonNull String _package;
public GenerateGetters(@Named("urgent") boolean urgent, @Named("url") @NonNull String url,
@Named("diskSize") @NonNull DataSize diskSize, @Named("ETA") @NonNull Duration ETA,
@Named("package") @NonNull String _package) {
this.urgent = urgent;
this.url = url;
this.diskSize = diskSize;
this.ETA = ETA;
this._package = _package;
}
public boolean isUrgent() {
return urgent;
}
public GenerateGetters withUrgent(boolean urgent) {
return new GenerateGetters(urgent, url, diskSize, ETA, _package);
}
public @NonNull String getUrl() {
return url;
}
public GenerateGetters withUrl(@NonNull String url) {
return new GenerateGetters(urgent, url, diskSize, ETA, _package);
}
public @NonNull DataSize getDiskSize() {
return diskSize;
}
public GenerateGetters withDiskSize(@NonNull DataSize diskSize) {
return new GenerateGetters(urgent, url, diskSize, ETA, _package);
}
public @NonNull Duration getETA() {
return ETA;
}
public GenerateGetters withETA(@NonNull Duration ETA) {
return new GenerateGetters(urgent, url, diskSize, ETA, _package);
}
public @NonNull String getPackage() {
return _package;
}
public GenerateGetters withPackage(@NonNull String _package) {
return new GenerateGetters(urgent, url, diskSize, ETA, _package);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
GenerateGetters other = (GenerateGetters) obj;
if (!Objects.equals(this.urgent, other.urgent)) return false;
if (!Objects.equals(this.url, other.url)) return false;
if (!Objects.equals(this.diskSize, other.diskSize)) return false;
if (!Objects.equals(this.ETA, other.ETA)) return false;
if (!Objects.equals(this._package, other._package)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this.urgent);
result = 31 * result + Objects.hashCode(this.url);
result = 31 * result + Objects.hashCode(this.diskSize);
result = 31 * result + Objects.hashCode(this.ETA);
result = 31 * result + Objects.hashCode(this._package);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(300);
builder.append(GenerateGetters.class.getSimpleName()).append(" {");
appendProperty(builder, "urgent", this.urgent);
appendProperty(builder, "url", this.url);
appendProperty(builder, "diskSize", this.diskSize);
appendProperty(builder, "ETA", this.ETA);
appendProperty(builder, "_package", this._package);
builder.append("\n}");
return builder.toString();
}
}
}

View File

@@ -0,0 +1,180 @@
package my;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.Objects;
import org.pkl.config.java.mapper.Named;
import org.pkl.config.java.mapper.NonNull;
import org.pkl.core.Duration;
public final class Mod {
private Mod() {
}
private static void appendProperty(StringBuilder builder, String name, Object value) {
builder.append("\n ").append(name).append(" = ");
String[] lines = Objects.toString(value).split("\n");
builder.append(lines[0]);
for (int i = 1; i < lines.length; i++) {
builder.append("\n ").append(lines[i]);
}
}
public abstract static class Foo {
protected final long one;
protected Foo(@Named("one") long one) {
this.one = one;
}
public long getOne() {
return one;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Foo other = (Foo) obj;
if (!Objects.equals(this.one, other.one)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this.one);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(100);
builder.append(Foo.class.getSimpleName()).append(" {");
appendProperty(builder, "one", this.one);
builder.append("\n}");
return builder.toString();
}
}
public static class None extends Foo {
public None(@Named("one") long one) {
super(one);
}
public None withOne(long one) {
return new None(one);
}
}
public static class Bar extends None {
protected final String two;
public Bar(@Named("one") long one, @Named("two") String two) {
super(one);
this.two = two;
}
public Bar withOne(long one) {
return new Bar(one, two);
}
public String getTwo() {
return two;
}
public Bar withTwo(String two) {
return new Bar(one, two);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Bar other = (Bar) obj;
if (!Objects.equals(this.one, other.one)) return false;
if (!Objects.equals(this.two, other.two)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this.one);
result = 31 * result + Objects.hashCode(this.two);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(150);
builder.append(Bar.class.getSimpleName()).append(" {");
appendProperty(builder, "one", this.one);
appendProperty(builder, "two", this.two);
builder.append("\n}");
return builder.toString();
}
}
public static final class Baz extends Bar {
private final @NonNull Duration three;
public Baz(@Named("one") long one, @Named("two") String two,
@Named("three") @NonNull Duration three) {
super(one, two);
this.three = three;
}
public Baz withOne(long one) {
return new Baz(one, two, three);
}
public Baz withTwo(String two) {
return new Baz(one, two, three);
}
public @NonNull Duration getThree() {
return three;
}
public Baz withThree(@NonNull Duration three) {
return new Baz(one, two, three);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Baz other = (Baz) obj;
if (!Objects.equals(this.one, other.one)) return false;
if (!Objects.equals(this.two, other.two)) return false;
if (!Objects.equals(this.three, other.three)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this.one);
result = 31 * result + Objects.hashCode(this.two);
result = 31 * result + Objects.hashCode(this.three);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(200);
builder.append(Baz.class.getSimpleName()).append(" {");
appendProperty(builder, "one", this.one);
appendProperty(builder, "two", this.two);
appendProperty(builder, "three", this.three);
builder.append("\n}");
return builder.toString();
}
}
}

View File

@@ -0,0 +1,110 @@
package my;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.Objects;
import org.pkl.config.java.mapper.Named;
import org.pkl.config.java.mapper.NonNull;
/**
* module comment.
* *emphasized* `code`.
*/
public final class Mod {
/**
* module property comment.
* *emphasized* `code`.
*/
public final @NonNull Person pigeon;
public Mod(@Named("pigeon") @NonNull Person pigeon) {
this.pigeon = pigeon;
}
public Mod withPigeon(@NonNull Person pigeon) {
return new Mod(pigeon);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Mod other = (Mod) obj;
if (!Objects.equals(this.pigeon, other.pigeon)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this.pigeon);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(100);
builder.append(Mod.class.getSimpleName()).append(" {");
appendProperty(builder, "pigeon", this.pigeon);
builder.append("\n}");
return builder.toString();
}
private static void appendProperty(StringBuilder builder, String name, Object value) {
builder.append("\n ").append(name).append(" = ");
String[] lines = Objects.toString(value).split("\n");
builder.append(lines[0]);
for (int i = 1; i < lines.length; i++) {
builder.append("\n ").append(lines[i]);
}
}
/**
* class comment.
* *emphasized* `code`.
*/
public static final class Person {
/**
* class property comment.
* *emphasized* `code`.
*/
public final @NonNull String name;
public Person(@Named("name") @NonNull String name) {
this.name = name;
}
public Person withName(@NonNull String name) {
return new Person(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Person other = (Person) obj;
if (!Objects.equals(this.name, other.name)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this.name);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(100);
builder.append(Person.class.getSimpleName()).append(" {");
appendProperty(builder, "name", this.name);
builder.append("\n}");
return builder.toString();
}
}
}

View File

@@ -0,0 +1,410 @@
package my;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import org.pkl.config.java.mapper.Named;
import org.pkl.config.java.mapper.NonNull;
import org.pkl.core.DataSize;
import org.pkl.core.DataSizeUnit;
import org.pkl.core.Duration;
import org.pkl.core.DurationUnit;
import org.pkl.core.Pair;
public final class Mod {
private Mod() {
}
private static void appendProperty(StringBuilder builder, String name, Object value) {
builder.append("\n ").append(name).append(" = ");
String[] lines = Objects.toString(value).split("\n");
builder.append(lines[0]);
for (int i = 1; i < lines.length; i++) {
builder.append("\n ").append(lines[i]);
}
}
public static final class PropertyTypes {
public final boolean _boolean;
public final long _int;
public final double _float;
public final @NonNull String string;
public final @NonNull Duration duration;
public final @NonNull DurationUnit durationUnit;
public final @NonNull DataSize dataSize;
public final @NonNull DataSizeUnit dataSizeUnit;
public final String nullable;
public final String nullable2;
public final @NonNull Pair<Object, Object> pair;
public final @NonNull Pair<@NonNull String, @NonNull Other> pair2;
public final @NonNull Collection<Object> coll;
public final @NonNull Collection<@NonNull Other> coll2;
public final @NonNull List<Object> list;
public final @NonNull List<@NonNull Other> list2;
public final @NonNull Set<Object> set;
public final @NonNull Set<@NonNull Other> set2;
public final @NonNull Map<Object, Object> map;
public final @NonNull Map<@NonNull String, @NonNull Other> map2;
public final @NonNull Map<Object, Object> container;
public final @NonNull Map<@NonNull String, @NonNull Other> container2;
public final @NonNull Other other;
public final @NonNull Pattern regex;
public final Object any;
public final @NonNull Object nonNull;
public final @NonNull Direction _enum;
public PropertyTypes(@Named("boolean") boolean _boolean, @Named("int") long _int,
@Named("float") double _float, @Named("string") @NonNull String string,
@Named("duration") @NonNull Duration duration,
@Named("durationUnit") @NonNull DurationUnit durationUnit,
@Named("dataSize") @NonNull DataSize dataSize,
@Named("dataSizeUnit") @NonNull DataSizeUnit dataSizeUnit,
@Named("nullable") String nullable, @Named("nullable2") String nullable2,
@Named("pair") @NonNull Pair<Object, Object> pair,
@Named("pair2") @NonNull Pair<@NonNull String, @NonNull Other> pair2,
@Named("coll") @NonNull Collection<Object> coll,
@Named("coll2") @NonNull Collection<@NonNull Other> coll2,
@Named("list") @NonNull List<Object> list,
@Named("list2") @NonNull List<@NonNull Other> list2, @Named("set") @NonNull Set<Object> set,
@Named("set2") @NonNull Set<@NonNull Other> set2,
@Named("map") @NonNull Map<Object, Object> map,
@Named("map2") @NonNull Map<@NonNull String, @NonNull Other> map2,
@Named("container") @NonNull Map<Object, Object> container,
@Named("container2") @NonNull Map<@NonNull String, @NonNull Other> container2,
@Named("other") @NonNull Other other, @Named("regex") @NonNull Pattern regex,
@Named("any") Object any, @Named("nonNull") @NonNull Object nonNull,
@Named("enum") @NonNull Direction _enum) {
this._boolean = _boolean;
this._int = _int;
this._float = _float;
this.string = string;
this.duration = duration;
this.durationUnit = durationUnit;
this.dataSize = dataSize;
this.dataSizeUnit = dataSizeUnit;
this.nullable = nullable;
this.nullable2 = nullable2;
this.pair = pair;
this.pair2 = pair2;
this.coll = coll;
this.coll2 = coll2;
this.list = list;
this.list2 = list2;
this.set = set;
this.set2 = set2;
this.map = map;
this.map2 = map2;
this.container = container;
this.container2 = container2;
this.other = other;
this.regex = regex;
this.any = any;
this.nonNull = nonNull;
this._enum = _enum;
}
public PropertyTypes withBoolean(boolean _boolean) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withInt(long _int) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withFloat(double _float) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withString(@NonNull String string) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withDuration(@NonNull Duration duration) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withDurationUnit(@NonNull DurationUnit durationUnit) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withDataSize(@NonNull DataSize dataSize) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withDataSizeUnit(@NonNull DataSizeUnit dataSizeUnit) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withNullable(String nullable) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withNullable2(String nullable2) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withPair(@NonNull Pair<Object, Object> pair) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withPair2(@NonNull Pair<@NonNull String, @NonNull Other> pair2) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withColl(@NonNull Collection<Object> coll) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withColl2(@NonNull Collection<@NonNull Other> coll2) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withList(@NonNull List<Object> list) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withList2(@NonNull List<@NonNull Other> list2) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withSet(@NonNull Set<Object> set) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withSet2(@NonNull Set<@NonNull Other> set2) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withMap(@NonNull Map<Object, Object> map) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withMap2(@NonNull Map<@NonNull String, @NonNull Other> map2) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withContainer(@NonNull Map<Object, Object> container) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withContainer2(@NonNull Map<@NonNull String, @NonNull Other> container2) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withOther(@NonNull Other other) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withRegex(@NonNull Pattern regex) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withAny(Object any) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withNonNull(@NonNull Object nonNull) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
public PropertyTypes withEnum(@NonNull Direction _enum) {
return new PropertyTypes(_boolean, _int, _float, string, duration, durationUnit, dataSize, dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map, map2, container, container2, other, regex, any, nonNull, _enum);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
PropertyTypes other = (PropertyTypes) obj;
if (!Objects.equals(this._boolean, other._boolean)) return false;
if (!Objects.equals(this._int, other._int)) return false;
if (!Objects.equals(this._float, other._float)) return false;
if (!Objects.equals(this.string, other.string)) return false;
if (!Objects.equals(this.duration, other.duration)) return false;
if (!Objects.equals(this.durationUnit, other.durationUnit)) return false;
if (!Objects.equals(this.dataSize, other.dataSize)) return false;
if (!Objects.equals(this.dataSizeUnit, other.dataSizeUnit)) return false;
if (!Objects.equals(this.nullable, other.nullable)) return false;
if (!Objects.equals(this.nullable2, other.nullable2)) return false;
if (!Objects.equals(this.pair, other.pair)) return false;
if (!Objects.equals(this.pair2, other.pair2)) return false;
if (!Objects.equals(this.coll, other.coll)) return false;
if (!Objects.equals(this.coll2, other.coll2)) return false;
if (!Objects.equals(this.list, other.list)) return false;
if (!Objects.equals(this.list2, other.list2)) return false;
if (!Objects.equals(this.set, other.set)) return false;
if (!Objects.equals(this.set2, other.set2)) return false;
if (!Objects.equals(this.map, other.map)) return false;
if (!Objects.equals(this.map2, other.map2)) return false;
if (!Objects.equals(this.container, other.container)) return false;
if (!Objects.equals(this.container2, other.container2)) return false;
if (!Objects.equals(this.other, other.other)) return false;
if (!Objects.equals(this.regex.pattern(), other.regex.pattern())) return false;
if (!Objects.equals(this.any, other.any)) return false;
if (!Objects.equals(this.nonNull, other.nonNull)) return false;
if (!Objects.equals(this._enum, other._enum)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this._boolean);
result = 31 * result + Objects.hashCode(this._int);
result = 31 * result + Objects.hashCode(this._float);
result = 31 * result + Objects.hashCode(this.string);
result = 31 * result + Objects.hashCode(this.duration);
result = 31 * result + Objects.hashCode(this.durationUnit);
result = 31 * result + Objects.hashCode(this.dataSize);
result = 31 * result + Objects.hashCode(this.dataSizeUnit);
result = 31 * result + Objects.hashCode(this.nullable);
result = 31 * result + Objects.hashCode(this.nullable2);
result = 31 * result + Objects.hashCode(this.pair);
result = 31 * result + Objects.hashCode(this.pair2);
result = 31 * result + Objects.hashCode(this.coll);
result = 31 * result + Objects.hashCode(this.coll2);
result = 31 * result + Objects.hashCode(this.list);
result = 31 * result + Objects.hashCode(this.list2);
result = 31 * result + Objects.hashCode(this.set);
result = 31 * result + Objects.hashCode(this.set2);
result = 31 * result + Objects.hashCode(this.map);
result = 31 * result + Objects.hashCode(this.map2);
result = 31 * result + Objects.hashCode(this.container);
result = 31 * result + Objects.hashCode(this.container2);
result = 31 * result + Objects.hashCode(this.other);
result = 31 * result + Objects.hashCode(this.regex);
result = 31 * result + Objects.hashCode(this.any);
result = 31 * result + Objects.hashCode(this.nonNull);
result = 31 * result + Objects.hashCode(this._enum);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(1400);
builder.append(PropertyTypes.class.getSimpleName()).append(" {");
appendProperty(builder, "_boolean", this._boolean);
appendProperty(builder, "_int", this._int);
appendProperty(builder, "_float", this._float);
appendProperty(builder, "string", this.string);
appendProperty(builder, "duration", this.duration);
appendProperty(builder, "durationUnit", this.durationUnit);
appendProperty(builder, "dataSize", this.dataSize);
appendProperty(builder, "dataSizeUnit", this.dataSizeUnit);
appendProperty(builder, "nullable", this.nullable);
appendProperty(builder, "nullable2", this.nullable2);
appendProperty(builder, "pair", this.pair);
appendProperty(builder, "pair2", this.pair2);
appendProperty(builder, "coll", this.coll);
appendProperty(builder, "coll2", this.coll2);
appendProperty(builder, "list", this.list);
appendProperty(builder, "list2", this.list2);
appendProperty(builder, "set", this.set);
appendProperty(builder, "set2", this.set2);
appendProperty(builder, "map", this.map);
appendProperty(builder, "map2", this.map2);
appendProperty(builder, "container", this.container);
appendProperty(builder, "container2", this.container2);
appendProperty(builder, "other", this.other);
appendProperty(builder, "regex", this.regex);
appendProperty(builder, "any", this.any);
appendProperty(builder, "nonNull", this.nonNull);
appendProperty(builder, "_enum", this._enum);
builder.append("\n}");
return builder.toString();
}
}
public static final class Other {
public final @NonNull String name;
public Other(@Named("name") @NonNull String name) {
this.name = name;
}
public Other withName(@NonNull String name) {
return new Other(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Other other = (Other) obj;
if (!Objects.equals(this.name, other.name)) return false;
return true;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + Objects.hashCode(this.name);
return result;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(100);
builder.append(Other.class.getSimpleName()).append(" {");
appendProperty(builder, "name", this.name);
builder.append("\n}");
return builder.toString();
}
}
public enum Direction {
NORTH("north"),
EAST("east"),
SOUTH("south"),
WEST("west");
private String value;
private Direction(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
}