/* * 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() 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): List { 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>()) { val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue addDeclaration(name, typeDeclaration.resolve()) } } } typeSolver.add(memoryTypeSolver) return mutableListOf().apply { for (compilationUnit in compilationUnits) { val classDeclarations = compilationUnit .getNodesByClass() .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 ParseResult.getOrThrow(): T = if (isSuccessful) { result.get() } else { throw ParseProblemException(problems) } private inline fun Node.getNodesByClass(): List = getNodesByClass(T::class.java) private fun Node.getNodesByClass(klass: Class): List = mutableListOf().apply { if (klass.isInstance(this@getNodesByClass)) { this += klass.cast(this@getNodesByClass) } for (childNode in childNodes) { this += childNode.getNodesByClass(klass) } } private fun Optional.getOrNull(): T? = orElse(null) private fun List.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()) }