11package parser
22
33import com.github.javaparser.JavaParser
4+ import com.github.javaparser.ParserConfiguration
5+ import com.github.javaparser.ast.CompilationUnit
46import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
57import com.github.javaparser.ast.body.ConstructorDeclaration
68import com.github.javaparser.ast.body.EnumDeclaration
79import com.github.javaparser.ast.body.FieldDeclaration
810import com.github.javaparser.ast.body.MethodDeclaration
9- import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration
11+ import com.github.javaparser.ast.type.Type
12+ import com.github.javaparser.resolution.SymbolResolver
13+ import com.github.javaparser.resolution.UnsolvedSymbolException
14+ import com.github.javaparser.symbolsolver.JavaSymbolSolver
15+ import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade
1016import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver
11- import com.github.javaparser.utils.SourceRoot
12- import util.extractJavaDocInfo
17+ import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver
18+ import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver
19+ import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver
20+ import com.github.javaparser.symbolsolver.resolution.typesolvers.TypeSolverBuilder
21+ import util.extractMainComment
22+ import util.extractParamComment
23+ import util.extractReturnComment
24+ import util.mapJavaTypeToLua
25+ import java.io.File
1326import java.io.InputStream
27+ import java.nio.file.Files
1428import java.util.jar.JarEntry
1529import java.util.jar.JarFile
30+ import kotlin.jvm.optionals.getOrNull
31+
32+ class JavaSourceParser (private val jarFiles : List <File >) : ClassParser {
33+ private val solver = CombinedTypeSolver ()
34+ private val parserConfiguration = ParserConfiguration ()
35+ private val parser: JavaParser = JavaParser (parserConfiguration)
36+ private val javaParserFacade: JavaParserFacade = JavaParserFacade .get(solver)
37+ val parsedClasses = mutableMapOf<String , ParsedClass >() // Map to store parsed classes by their fully qualified name
38+
39+ init {
40+ // Add the reflection type solver to the combined type solver
41+ solver.add(ReflectionTypeSolver ())
42+
43+ // Process each source JAR
44+ // I don't like this at all however it seems to be the only way to let it parse from source JARS
45+ // I suppose we could use a custom type solver
46+ // Or move away from source jars
47+ jarFiles.forEach { jar ->
48+ val tempDir = Files .createTempDirectory(" source_jar" )
49+ JarFile (jar).use { jarFile ->
50+ jarFile.entries().asSequence()
51+ .filter { it.name.endsWith(" .java" ) }
52+ .forEach { entry ->
53+ val outputFile = tempDir.resolve(entry.name)
54+ Files .createDirectories(outputFile.parent)
55+ jarFile.getInputStream(entry).use { input ->
56+ Files .copy(input, outputFile)
57+ }
58+ }
59+ }
60+ solver.add(JavaParserTypeSolver (tempDir.toFile()))
61+ }
62+
63+ // Set the symbol resolver in the parser configuration
64+ val symbolResolver: SymbolResolver = JavaSymbolSolver (solver)
65+ parserConfiguration.setSymbolResolver(symbolResolver)
66+ }
1667
17- class JavaSourceParser : ClassParser {
18- private val parser = JavaParser ()
68+ override fun parse (): List <ParsedClass > {
1969
20- override fun parse (jar : JarFile ): List <ParsedClass > {
21- val parsedClasses = mutableListOf<ParsedClass >()
70+ val jars = jarFiles.map { JarFile (it) }
2271
23- jar.entries().asSequence().filter { it.name.endsWith(" .java" ) }.forEach { entry ->
24- parseEntry(jar, entry)?.let { parsedClasses.add(it) }
72+ jars.forEach { jar ->
73+ jar.entries().asSequence().filter { it.name.endsWith(" .java" ) }.forEach { entry ->
74+ parseJarEntry(jar, entry)
75+ }
2576 }
2677
27- return parsedClasses
78+ return parsedClasses.values.toList()
2879 }
2980
30- private fun parseEntry (jarFile : JarFile , entry : JarEntry ): ParsedClass ? {
81+ private fun parseJarEntry (jarFile : JarFile , entry : JarEntry ): ParsedClass ? {
3182 val inputStream: InputStream = jarFile.getInputStream(entry)
3283 val cu = parser.parse(inputStream).result.orElse(null ) ? : return null
3384 inputStream.close()
85+ return parseEntry(cu)
86+ }
3487
88+ private fun parseEntry (cu : CompilationUnit ): ParsedClass ? {
3589 val pkgName = cu.packageDeclaration.map { it.nameAsString }.orElse(" " )
36-
3790 var parsedClass: ParsedClass ? = null
3891
3992 // Handle enum
@@ -46,13 +99,14 @@ class JavaSourceParser : ClassParser {
4699 name = it.nameAsString,
47100 type = enumDecl.nameAsString,
48101 visibility = Visibility .PUBLIC ,
49- comment = it.comment.map { c -> extractJavaDocInfo(c.content).mainComment }.orElse( null )
102+ comment = extractMainComment( it.javadoc.getOrNull() )
50103 )
51104 },
52105 methods = emptyList(),
53- classComment = enumDecl.comment.map { extractJavaDocInfo(it.content).mainComment }.orElse( null ),
106+ classComment = extractMainComment( enumDecl.javadoc.getOrNull() ),
54107 isEnum = true
55108 )
109+ parsedClasses[enumDecl.fullyQualifiedName.toString()] = parsedClass!!
56110 }
57111
58112 // Handle class or interface
@@ -67,12 +121,13 @@ class JavaSourceParser : ClassParser {
67121 packageName = pkgName,
68122 fields = fields,
69123 methods = methods, // Combine methods
70- classComment = cls.comment.map { extractJavaDocInfo(it.content).mainComment }.orElse( null ),
71- extendedTypes = cls.extendedTypes.map { it.asString( ) },
72- implementedTypes = cls.implementedTypes.map { it.asString( ) },
124+ classComment = extractMainComment( cls.javadoc.getOrNull() ),
125+ extendedTypes = cls.extendedTypes.map { getFullyQualifiedName(it ) },
126+ implementedTypes = cls.implementedTypes.map { getFullyQualifiedName(it ) },
73127 constructors = cls.constructors.map { parseConstructor(it, cls.nameAsString) },
74128 isEnum = false
75129 )
130+ parsedClasses[cls.fullyQualifiedName.toString()] = parsedClass!!
76131 }
77132
78133 return parsedClass
@@ -93,7 +148,7 @@ class JavaSourceParser : ClassParser {
93148 )
94149 },
95150 visibility = Visibility .fromString(constructor .accessSpecifier.toString().uppercase()),
96- comment = constructor .comment.map { extractJavaDocInfo(it.content).mainComment }.orElse( null ),
151+ comment = extractMainComment( constructor .javadoc.getOrNull() ),
97152 isDeprecated = constructor .annotations.any { it.nameAsString == " Deprecated" },
98153 isAsync = false , // Constructors are not async
99154 isConstructor = true
@@ -103,19 +158,21 @@ class JavaSourceParser : ClassParser {
103158 private fun parseMethod (method : MethodDeclaration , className : String ): ParsedMethod {
104159 return ParsedMethod (
105160 name = method.nameAsString,
106- returnType = method.type.toString( ),
161+ returnType = getFullyQualifiedName( method.type),
107162 parameters = method.parameters.map { param ->
108163 // Check for nullability in the parameter type. Not sure why but it seems to include annotations in the type string
109164 val isNullableComment = param.type.toString()?.contains(" @Nullable" ) ? : false
110165
111166 ParsedParameter (
112167 name = param.nameAsString,
113- type = param.type.toString().replace( " @Nullable " , " " ),
168+ type = getFullyQualifiedName( param.type),
114169 required = ! isNullableComment,
170+ comment = extractParamComment(method.javadoc.getOrNull(), param.nameAsString)
115171 )
116172 },
117173 visibility = Visibility .fromString(method.accessSpecifier.toString().uppercase()),
118- comment = method.comment.map { extractJavaDocInfo(it.content).mainComment }.orElse(null ),
174+ comment = method.javadoc.getOrNull()?.description?.toText()?.replace(" \n " , " " ) ? : " " ,
175+ returnComment = extractReturnComment(method.javadoc.getOrNull()),
119176 isDeprecated = method.annotations.any { it.nameAsString == " Deprecated" },
120177 isAsync = method.nameAsString.contains(
121178 " Async" ,
@@ -128,10 +185,36 @@ class JavaSourceParser : ClassParser {
128185 return field.variables.map { variable ->
129186 ParsedField (
130187 name = variable.nameAsString,
131- type = field.elementType.asString( ),
188+ type = getFullyQualifiedName( field.elementType),
132189 visibility = Visibility .fromString(field.accessSpecifier.toString().uppercase()),
133- comment = field.comment.map { extractJavaDocInfo(it.content).mainComment }.orElse( null )
190+ comment = extractMainComment( field.javadoc.getOrNull() )
134191 )
135192 }
136193 }
194+
195+ // Slightly based off of : https://github.com/freggy/blua/blob/2408c0e0fdbc29c2fd38ed9083ab437d31eb83e2/javadump/src/main/kotlin/dev/freggy/blua/javadump/Main.kt#L136-L167
196+ fun getFullyQualifiedName (
197+ type : Type ,
198+ ): String {
199+ return try {
200+ val resolvedType = javaParserFacade.convertToUsage(type)
201+ if (resolvedType.isReferenceType) {
202+ mapJavaTypeToLua(resolvedType.asReferenceType().qualifiedName)
203+ } else {
204+ mapJavaTypeToLua(type.asString())
205+ }
206+ } catch (e: Exception ) {
207+ when (e) {
208+ is UnsupportedOperationException -> {
209+ mapJavaTypeToLua(type.asString())
210+ }
211+
212+ is UnsolvedSymbolException -> {
213+ " any" // Fallback to "any" for unresolved types
214+ }
215+
216+ else -> throw e // Rethrow unexpected exceptions
217+ }
218+ }
219+ }
137220}
0 commit comments