diff options
author | 2023-04-21 01:43:47 +0000 | |
---|---|---|
committer | 2023-04-20 19:13:53 -0700 | |
commit | f89fc1df277ae570c942e89e2ae8803258741f17 (patch) | |
tree | 475643a763639b4982ce2e169a1313c220fa0949 | |
parent | 9fc312cdcd15668141c65d7df41f027602623a41 (diff) |
Remove unused old tool.
This codegen was never actually put into production.
Test: build
Change-Id: I0f8b5a994bf9fd32b4b5cbb4ab469ede8988edbb
-rw-r--r-- | tools/xmlpersistence/Android.bp | 20 | ||||
-rw-r--r-- | tools/xmlpersistence/OWNERS | 1 | ||||
-rw-r--r-- | tools/xmlpersistence/manifest.txt | 1 | ||||
-rw-r--r-- | tools/xmlpersistence/src/main/kotlin/Generator.kt | 577 | ||||
-rw-r--r-- | tools/xmlpersistence/src/main/kotlin/Main.kt | 45 | ||||
-rw-r--r-- | tools/xmlpersistence/src/main/kotlin/Parser.kt | 248 | ||||
-rw-r--r-- | tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt | 44 |
7 files changed, 0 insertions, 936 deletions
diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp deleted file mode 100644 index 0b6dba626794..000000000000 --- a/tools/xmlpersistence/Android.bp +++ /dev/null @@ -1,20 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -java_binary_host { - name: "xmlpersistence_cli", - manifest: "manifest.txt", - srcs: [ - "src/**/*.kt", - ], - static_libs: [ - "javaparser-symbol-solver", - "javapoet", - ], -} diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS deleted file mode 100644 index 4f4d06a32676..000000000000 --- a/tools/xmlpersistence/OWNERS +++ /dev/null @@ -1 +0,0 @@ -zhanghai@google.com diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt deleted file mode 100644 index 6d9771998efc..000000000000 --- a/tools/xmlpersistence/manifest.txt +++ /dev/null @@ -1 +0,0 @@ -Main-class: MainKt diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt deleted file mode 100644 index 8e62388c860f..000000000000 --- a/tools/xmlpersistence/src/main/kotlin/Generator.kt +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.FieldSpec -import com.squareup.javapoet.JavaFile -import com.squareup.javapoet.MethodSpec -import com.squareup.javapoet.NameAllocator -import com.squareup.javapoet.ParameterSpec -import com.squareup.javapoet.TypeSpec -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.nio.charset.StandardCharsets -import java.time.Year -import java.util.Objects -import javax.lang.model.element.Modifier - -// JavaPoet only supports line comments, and can't add a newline after file level comments. -val FILE_HEADER = """ - /* - * Copyright (C) ${Year.now().value} The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - // Generated by xmlpersistence. DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // @formatter:off -""".trimIndent() + "\n\n" - -private val atomicFileType = ClassName.get("android.util", "AtomicFile") - -fun generate(persistence: PersistenceInfo): JavaFile { - val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type } - val type = TypeSpec.classBuilder(persistence.name) - .addJavadoc( - """ - Generated class implementing XML persistence for${'$'}W{@link $1T}. - <p> - This class provides atomicity for persistence via {@link $2T}, however it does not provide - thread safety, so please bring your own synchronization mechanism. - """.trimIndent(), persistence.root.type, atomicFileType - ) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addField(generateFileField()) - .addMethod(generateConstructor()) - .addMethod(generateReadMethod(persistence.root)) - .addMethod(generateParseMethod(persistence.root)) - .addMethods(distinctClassFields.map { generateParseClassMethod(it) }) - .addMethod(generateWriteMethod(persistence.root)) - .addMethod(generateSerializeMethod(persistence.root)) - .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) }) - .addMethod(generateDeleteMethod()) - .build() - return JavaFile.builder(persistence.root.type.packageName(), type) - .skipJavaLangImports(true) - .indent(" ") - .build() -} - -private val nonNullType = ClassName.get("android.annotation", "NonNull") - -private fun generateFileField(): FieldSpec = - FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL) - .addAnnotation(nonNullType) - .build() - -private fun generateConstructor(): MethodSpec = - MethodSpec.constructorBuilder() - .addJavadoc( - """ - Create an instance of this class. - - @param file the XML file for persistence - """.trimIndent() - ) - .addModifiers(Modifier.PUBLIC) - .addParameter( - ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build() - ) - .addStatement("mFile = new \$1T(file)", atomicFileType) - .build() - -private val nullableType = ClassName.get("android.annotation", "Nullable") - -private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser") - -private val xmlType = ClassName.get("android.util", "Xml") - -private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException") - -private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder("read") - .addJavadoc( - """ - Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile. - - @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist - @throws IllegalArgumentException if an error occurred while reading - """.trimIndent(), rootField.type - ) - .addAnnotation(nullableType) - .addModifiers(Modifier.PUBLIC) - .returns(rootField.type) - .addControlFlow("try (\$1T inputStream = mFile.openRead())", FileInputStream::class.java) { - addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType) - addStatement("parser.setInput(inputStream, null)") - addStatement("return parse(parser)") - nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java) - addStatement("return null") - nextControlFlow( - "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType - ) - addStatement("throw new IllegalArgumentException(e)") - } - .build() - -private val ClassFieldInfo.allClassFields: List<ClassFieldInfo> - get() = - mutableListOf<ClassFieldInfo>().apply { - this += this@allClassFields - for (field in fields) { - when (field) { - is ClassFieldInfo -> this += field.allClassFields - is ListFieldInfo -> this += field.element.allClassFields - else -> {} - } - } - } - -private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder("parse") - .addAnnotation(nonNullType) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .returns(rootField.type) - .addParameter( - ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() - ) - .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)) - .apply { - addStatement("int type") - addStatement("int depth") - addStatement("int innerDepth = parser.getDepth() + 1") - addControlFlow( - "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" - + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", - xmlPullParserType - ) { - addControlFlow( - "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType - ) { - addStatement("continue") - } - addControlFlow( - "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java, - rootField.tagName - ) { - addStatement("return \$1L(parser)", rootField.parseMethodName) - } - } - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Missing root tag <${rootField.tagName}>" - ) - } - .build() - -private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder(classField.parseMethodName) - .addAnnotation(nonNullType) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .returns(classField.type) - .addParameter( - ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() - ) - .apply { - val (attributeFields, tagFields) = classField.fields - .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } - if (tagFields.isNotEmpty()) { - addExceptions( - listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType) - ) - } - val nameAllocator = NameAllocator().apply { - newName("parser") - newName("type") - newName("depth") - newName("innerDepth") - } - for (field in attributeFields) { - val variableName = nameAllocator.newName(field.variableName, field) - when (field) { - is PrimitiveFieldInfo -> { - val stringVariableName = - nameAllocator.newName("${field.variableName}String") - addStatement( - "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", - stringVariableName, field.attributeName - ) - if (field.isRequired) { - addControlFlow("if (\$1L == null)", stringVariableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Missing attribute \"${field.attributeName}\"" - ) - } - } - val boxedType = field.type.box() - val parseTypeMethodName = if (field.type.isPrimitive) { - "parse${field.type.toString().capitalize()}" - } else { - "valueOf" - } - if (field.isRequired) { - addStatement( - "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName, - boxedType, parseTypeMethodName, stringVariableName - ) - } else { - addStatement( - "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null", - field.type, variableName, stringVariableName, boxedType, - parseTypeMethodName - ) - } - } - is StringFieldInfo -> - addStatement( - "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", - variableName, field.attributeName - ) - else -> error(field) - } - } - if (tagFields.isNotEmpty()) { - for (field in tagFields) { - val variableName = nameAllocator.newName(field.variableName, field) - when (field) { - is ClassFieldInfo -> - addStatement("\$1T \$2L =\$Wnull", field.type, variableName) - is ListFieldInfo -> - addStatement( - "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName, - ArrayList::class.java - ) - else -> error(field) - } - } - addStatement("int type") - addStatement("int depth") - addStatement("int innerDepth = parser.getDepth() + 1") - addControlFlow( - "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" - + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", - xmlPullParserType - ) { - addControlFlow( - "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType - ) { - addStatement("continue") - } - addControlFlow("switch (parser.getName())") { - for (field in tagFields) { - addControlFlow("case \$1S:", field.tagName) { - val variableName = nameAllocator.get(field) - when (field) { - is ClassFieldInfo -> { - addControlFlow("if (\$1L != null)", variableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Duplicate tag \"${field.tagName}\"" - ) - } - addStatement( - "\$1L =\$W\$2L(parser)", variableName, - field.parseMethodName - ) - addStatement("break") - } - is ListFieldInfo -> { - val elementNameAllocator = nameAllocator.clone() - val elementVariableName = elementNameAllocator.newName( - field.element.xmlName!!.toLowerCamelCase() - ) - addStatement( - "final \$1T \$2L =\$W\$3L(parser)", field.element.type, - elementVariableName, field.element.parseMethodName - ) - addStatement( - "\$1L.add(\$2L)", variableName, elementVariableName - ) - addStatement("break") - } - else -> error(field) - } - } - } - } - } - } - for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) { - addControlFlow("if ($1L == null)", nameAllocator.get(field)) { - addStatement( - "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>" - ) - } - } - addStatement( - classField.fields.joinToString(",\$W", "return new \$1T(", ")") { - nameAllocator.get(it) - }, classField.type - ) - } - .build() - -private val ClassFieldInfo.parseMethodName: String - get() = "parse${type.simpleName().toUpperCamelCase()}" - -private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer") - -private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder("write") - .apply { - val nameAllocator = NameAllocator().apply { - newName("outputStream") - newName("serializer") - } - val parameterName = nameAllocator.newName(rootField.variableName) - addJavadoc( - """ - Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile. - - @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist - """.trimIndent(), rootField.type, parameterName - ) - addAnnotation(nullableType) - addModifiers(Modifier.PUBLIC) - addParameter( - ParameterSpec.builder(rootField.type, parameterName) - .addAnnotation(nonNullType) - .build() - ) - addStatement("\$1T outputStream = null", FileOutputStream::class.java) - addControlFlow("try") { - addStatement("outputStream = mFile.startWrite()") - addStatement( - "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType - ) - addStatement( - "serializer.setOutput(outputStream, \$1T.UTF_8.name())", - StandardCharsets::class.java - ) - addStatement( - "serializer.setFeature(\$1S, true)", - "http://xmlpull.org/v1/doc/features.html#indent-output" - ) - addStatement("serializer.startDocument(null, true)") - addStatement("serialize(serializer,\$W\$1L)", parameterName) - addStatement("serializer.endDocument()") - addStatement("mFile.finishWrite(outputStream)") - nextControlFlow("catch (Exception e)") - addStatement("e.printStackTrace()") - addStatement("mFile.failWrite(outputStream)") - } - } - .build() - -private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder("serialize") - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .addParameter( - ParameterSpec.builder(xmlSerializerType, "serializer") - .addAnnotation(nonNullType) - .build() - ) - .apply { - val nameAllocator = NameAllocator().apply { newName("serializer") } - val parameterName = nameAllocator.newName(rootField.variableName) - addParameter( - ParameterSpec.builder(rootField.type, parameterName) - .addAnnotation(nonNullType) - .build() - ) - addException(IOException::class.java) - addStatement("serializer.startTag(null, \$1S)", rootField.tagName) - addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName) - addStatement("serializer.endTag(null, \$1S)", rootField.tagName) - } - .build() - -private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec = - MethodSpec.methodBuilder(classField.serializeMethodName) - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .addParameter( - ParameterSpec.builder(xmlSerializerType, "serializer") - .addAnnotation(nonNullType) - .build() - ) - .apply { - val nameAllocator = NameAllocator().apply { - newName("serializer") - newName("i") - } - val parameterName = nameAllocator.newName(classField.serializeParameterName) - addParameter( - ParameterSpec.builder(classField.type, parameterName) - .addAnnotation(nonNullType) - .build() - ) - addException(IOException::class.java) - val (attributeFields, tagFields) = classField.fields - .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } - for (field in attributeFields) { - val variableName = "$parameterName.${field.name}" - if (!field.isRequired) { - beginControlFlow("if (\$1L != null)", variableName) - } - when (field) { - is PrimitiveFieldInfo -> { - if (field.isRequired && !field.type.isPrimitive) { - addControlFlow("if (\$1L == null)", variableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Field \"${field.name}\" is null" - ) - } - } - val stringVariableName = - nameAllocator.newName("${field.variableName}String") - addStatement( - "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName, - variableName - ) - addStatement( - "serializer.attribute(null, \$1S, \$2L)", field.attributeName, - stringVariableName - ) - } - is StringFieldInfo -> { - if (field.isRequired) { - addControlFlow("if (\$1L == null)", variableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Field \"${field.name}\" is null" - ) - } - } - addStatement( - "serializer.attribute(null, \$1S, \$2L)", field.attributeName, - variableName - ) - } - else -> error(field) - } - if (!field.isRequired) { - endControlFlow() - } - } - for (field in tagFields) { - val variableName = "$parameterName.${field.name}" - if (field.isRequired) { - addControlFlow("if (\$1L == null)", variableName) { - addStatement( - "throw new IllegalArgumentException(\$1S)", - "Field \"${field.name}\" is null" - ) - } - } - when (field) { - is ClassFieldInfo -> { - addStatement("serializer.startTag(null, \$1S)", field.tagName) - addStatement( - "\$1L(serializer, \$2L)", field.serializeMethodName, variableName - ) - addStatement("serializer.endTag(null, \$1S)", field.tagName) - } - is ListFieldInfo -> { - val sizeVariableName = nameAllocator.newName("${field.variableName}Size") - addStatement( - "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName - ) - addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) { - val elementNameAllocator = nameAllocator.clone() - val elementVariableName = elementNameAllocator.newName( - field.element.xmlName!!.toLowerCamelCase() - ) - addStatement( - "final \$1T \$2L =\$W\$3L.get(i)", field.element.type, - elementVariableName, variableName - ) - addControlFlow("if (\$1L == null)", elementVariableName) { - addStatement( - "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)", - "Field element \"${field.name}[", "]\" is null" - ) - } - addStatement("serializer.startTag(null, \$1S)", field.element.tagName) - addStatement( - "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName, - elementVariableName - ) - addStatement("serializer.endTag(null, \$1S)", field.element.tagName) - } - } - else -> error(field) - } - } - } - .build() - -private val ClassFieldInfo.serializeMethodName: String - get() = "serialize${type.simpleName().toUpperCamelCase()}" - -private val ClassFieldInfo.serializeParameterName: String - get() = type.simpleName().toLowerCamelCase() - -private val FieldInfo.variableName: String - get() = name.toLowerCamelCase() - -private val FieldInfo.attributeName: String - get() { - check(this is PrimitiveFieldInfo || this is StringFieldInfo) - return xmlNameOrName.toLowerCamelCase() - } - -private val FieldInfo.tagName: String - get() { - check(this is ClassFieldInfo || this is ListFieldInfo) - return xmlNameOrName.toLowerKebabCase() - } - -private val FieldInfo.xmlNameOrName: String - get() = xmlName ?: name - -private fun generateDeleteMethod(): MethodSpec = - MethodSpec.methodBuilder("delete") - .addJavadoc("Delete the XML file, if any.") - .addModifiers(Modifier.PUBLIC) - .addStatement("mFile.delete()") - .build() - -private inline fun MethodSpec.Builder.addControlFlow( - controlFlow: String, - vararg args: Any, - block: MethodSpec.Builder.() -> Unit -): MethodSpec.Builder { - beginControlFlow(controlFlow, *args) - block() - endControlFlow() - return this -} diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt deleted file mode 100644 index e271f8cb9361..000000000000 --- a/tools/xmlpersistence/src/main/kotlin/Main.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.io.File -import java.nio.file.Files - -fun main(args: Array<String>) { - val showUsage = args.isEmpty() || when (args.singleOrNull()) { - "-h", "--help" -> true - else -> false - } - if (showUsage) { - usage() - return - } - - val files = args.flatMap { - File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() } - } - val persistences = parse(files) - for (persistence in persistences) { - val file = generate(persistence) - Files.newBufferedWriter(persistence.path).use { - it.write(FILE_HEADER) - file.writeTo(it) - } - } -} - -private fun usage() { - println("Usage: xmlpersistence <FILES>") -} diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt deleted file mode 100644 index 3ea12a9aa389..000000000000 --- a/tools/xmlpersistence/src/main/kotlin/Parser.kt +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.github.javaparser.JavaParser -import com.github.javaparser.ParseProblemException -import com.github.javaparser.ParseResult -import com.github.javaparser.ParserConfiguration -import com.github.javaparser.ast.Node -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration -import com.github.javaparser.ast.body.FieldDeclaration -import com.github.javaparser.ast.body.TypeDeclaration -import com.github.javaparser.ast.expr.AnnotationExpr -import com.github.javaparser.ast.expr.Expression -import com.github.javaparser.ast.expr.NormalAnnotationExpr -import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr -import com.github.javaparser.ast.expr.StringLiteralExpr -import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration -import com.github.javaparser.resolution.types.ResolvedPrimitiveType -import com.github.javaparser.resolution.types.ResolvedReferenceType -import com.github.javaparser.symbolsolver.JavaSymbolSolver -import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration -import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver -import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver -import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver -import com.squareup.javapoet.ClassName -import com.squareup.javapoet.ParameterizedTypeName -import com.squareup.javapoet.TypeName -import java.nio.file.Path -import java.util.Optional - -class PersistenceInfo( - val name: String, - val root: ClassFieldInfo, - val path: Path -) - -sealed class FieldInfo { - abstract val name: String - abstract val xmlName: String? - abstract val type: TypeName - abstract val isRequired: Boolean -} - -class PrimitiveFieldInfo( - override val name: String, - override val xmlName: String?, - override val type: TypeName, - override val isRequired: Boolean -) : FieldInfo() - -class StringFieldInfo( - override val name: String, - override val xmlName: String?, - override val isRequired: Boolean -) : FieldInfo() { - override val type: TypeName = ClassName.get(String::class.java) -} - -class ClassFieldInfo( - override val name: String, - override val xmlName: String?, - override val type: ClassName, - override val isRequired: Boolean, - val fields: List<FieldInfo> -) : FieldInfo() - -class ListFieldInfo( - override val name: String, - override val xmlName: String?, - override val type: ParameterizedTypeName, - val element: ClassFieldInfo -) : FieldInfo() { - override val isRequired: Boolean = true -} - -fun parse(files: List<Path>): List<PersistenceInfo> { - val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) } - val javaParser = JavaParser(ParserConfiguration() - .setSymbolResolver(JavaSymbolSolver(typeSolver))) - val compilationUnits = files.map { javaParser.parse(it).getOrThrow() } - val memoryTypeSolver = MemoryTypeSolver().apply { - for (compilationUnit in compilationUnits) { - for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) { - val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue - addDeclaration(name, typeDeclaration.resolve()) - } - } - } - typeSolver.add(memoryTypeSolver) - return mutableListOf<PersistenceInfo>().apply { - for (compilationUnit in compilationUnits) { - val classDeclarations = compilationUnit - .getNodesByClass<ClassOrInterfaceDeclaration>() - .filter { !it.isInterface && (!it.isNestedType || it.isStatic) } - this += classDeclarations.mapNotNull { parsePersistenceInfo(it) } - } - } -} - -private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? { - val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull() - ?: return null - val rootClassName = classDeclaration.nameAsString - val name = annotation.getMemberValue("value")?.stringLiteralValue - ?: "${rootClassName}Persistence" - val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull() - ?.getMemberValue("value")?.stringLiteralValue - val root = parseClassFieldInfo( - rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration - ) - val path = classDeclaration.findCompilationUnit().get().storage.get().path - .resolveSibling("$name.java") - return PersistenceInfo(name, root, path) -} - -private fun parseClassFieldInfo( - name: String, - xmlName: String?, - isRequired: Boolean, - classDeclaration: ClassOrInterfaceDeclaration -): ClassFieldInfo { - val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) } - val type = classDeclaration.resolve().typeName - return ClassFieldInfo(name, xmlName, type, isRequired, fields) -} - -private fun parseFieldInfo(field: FieldDeclaration): FieldInfo { - require(field.isPublic && field.isFinal) - val variable = field.variables.single() - val name = variable.nameAsString - val annotations = field.annotations + variable.type.annotations - val annotation = annotations.getByName("XmlName") - val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue - val isRequired = annotations.getByName("NonNull") != null - return when (val type = variable.type.resolve()) { - is ResolvedPrimitiveType -> { - val primitiveType = type.typeName - PrimitiveFieldInfo(name, xmlName, primitiveType, true) - } - is ResolvedReferenceType -> { - when (type.qualifiedName) { - Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name, - Short::class.javaObjectType.name, Char::class.javaObjectType.name, - Integer::class.javaObjectType.name, Long::class.javaObjectType.name, - Float::class.javaObjectType.name, Double::class.javaObjectType.name -> - PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired) - String::class.java.name -> StringFieldInfo(name, xmlName, isRequired) - List::class.java.name -> { - requireNotNull(xmlName) - val elementType = type.typeParametersValues().single() - require(elementType is ResolvedReferenceType) - val listType = ParameterizedTypeName.get( - ClassName.get(List::class.java), elementType.typeName - ) - val element = parseClassFieldInfo( - "(element)", xmlName, true, elementType.classDeclaration - ) - ListFieldInfo(name, xmlName, listType, element) - } - else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration) - } - } - else -> error(type) - } -} - -private fun <T> ParseResult<T>.getOrThrow(): T = - if (isSuccessful) { - result.get() - } else { - throw ParseProblemException(problems) - } - -private inline fun <reified T : Node> Node.getNodesByClass(): List<T> = - getNodesByClass(T::class.java) - -private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply { - if (klass.isInstance(this@getNodesByClass)) { - this += klass.cast(this@getNodesByClass) - } - for (childNode in childNodes) { - this += childNode.getNodesByClass(klass) - } -} - -private fun <T> Optional<T>.getOrNull(): T? = orElse(null) - -private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? = - find { it.name.identifier == name } - -private fun AnnotationExpr.getMemberValue(name: String): Expression? = - when (this) { - is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value - is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null - else -> null - } - -private val Expression.stringLiteralValue: String - get() { - require(this is StringLiteralExpr) - return value - } - -private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration - get() { - val resolvedClassDeclaration = typeDeclaration - require(resolvedClassDeclaration is JavaParserClassDeclaration) - return resolvedClassDeclaration.wrappedNode - } - -private val ResolvedPrimitiveType.typeName: TypeName - get() = - when (this) { - ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN - ResolvedPrimitiveType.BYTE -> TypeName.BYTE - ResolvedPrimitiveType.SHORT -> TypeName.SHORT - ResolvedPrimitiveType.CHAR -> TypeName.CHAR - ResolvedPrimitiveType.INT -> TypeName.INT - ResolvedPrimitiveType.LONG -> TypeName.LONG - ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT - ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE - } - -// This doesn't support type parameters. -private val ResolvedReferenceType.typeName: TypeName - get() = typeDeclaration.typeName - -private val ResolvedReferenceTypeDeclaration.typeName: ClassName - get() { - val packageName = packageName - val classNames = className.split(".") - val topLevelClassName = classNames.first() - val nestedClassNames = classNames.drop(1) - return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray()) - } diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt deleted file mode 100644 index b4bdbba7170b..000000000000 --- a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.util.Locale - -private val camelHumpBoundary = Regex( - "-" - + "|_" - + "|(?<=[0-9])(?=[^0-9])" - + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])" - + "|(?<=[a-z])(?=[^a-z])" -) - -private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary) - -fun String.toUpperCamelCase(): String = - toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) } - -fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT) - -fun String.toUpperKebabCase(): String = - toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) } - -fun String.toLowerKebabCase(): String = - toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) } - -fun String.toUpperSnakeCase(): String = - toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) } - -fun String.toLowerSnakeCase(): String = - toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) } |