spoonjava_如何以及为什么使⽤Spoon分析,⽣成和转换Java
代码
spoon java
是分析,⽣成和转换Java代码的⼯具。
在本⽂中,我们将看到通过使⽤以编程⽅式处理代码的技术可以实现什么。 我认为这些技术不是很为⼈所知或使⽤,这很遗憾,因为它们可能⾮常有⽤。 谁知道,即使您不想使⽤Spoon甚⾄不处理Java代码,⽽是使⽤C#,Python,Kotlin或其他语⾔,某些想法对于您当前的项⽬也可能有⽤。 让我们学习如何以更智能的⽅式编程。
Spoon具有与重叠的⼀些功能,⽽是我贡献的框架。 对于某些任务,Spoon可能是更好的选择,⽽对于另⼀些任务,JavaParser具有明显的优势。 稍后,我们将深⼊探讨这些⼯具之间的差异。
本⽂由⼀个伴随库与所有代码配对:
使⽤代码处理技术可以实现什么?
勺⼦和⼀般的代码处理⼯具可⽤于:
代码分析
计算源代码指标,例如出有多少类具有⼀定数量以上的⽅法
代码⽣成
以编程⽅式⽣成重复代码。
代码转换
⾃动重构,例如在构造函数中指定的字段中转换⼏种⽅法中使⽤的参数
这三个⼤家族与我们与代码交互的⽅式⼤致不同:
在代码分析中,代码是我们⽤来产⽣⾮代码输出的输⼊
在代码⽣成中,我们使⽤⼀些通常不是代码的输⼊,或者使⽤与我们输出的语⾔相同的代码。 输出是代码
在代码转换中,相同的代码库是输⼊和输出
设置汤匙
要设置汤匙,您需要提供:
要分析的代码
所有依赖关系(当然还有依赖关系的依赖关系)
利⽤此信息,Spoon可以构建代码模型。 在该模型上,您可以执⾏相当⾼级的分析。 这与JavaParser的⼯作⽅式不同。 如果需要,在JavaParser中,您可以仅构建代码的轻量级模型,⽽⽆需考虑依赖关系。 当您没有可⽤的依赖项或需要执⾏简单快速的操作时,这将很有⽤。 您还可以通过启⽤符号解析来进⾏更⾼级的分析,但这是可选的,并且在仅某些依赖项可⽤时也可以使⽤。
我喜欢Spoon的⼀件事是⽀持从Maven进⾏配置。 我认为这是⼀个⾮常有⽤的功能。 不过,我只想得到Gradle的⽀持。
在我们的⽰例中,我们不使⽤Maven配置,我们仅指定⼀个包含代码的⽬录。 在我们的案例中,我们正在检查JavaParser的核⼼模块,该模块的依赖项为零,因此我们⽆需指定任何JAR即可构建代码模型。
fun main(args: Array<String>) {
val launcher = Launcher()
launcher.addInputResource("codebases/jp/javaparser-core/src/main/java")
val model = launcher.buildModel()
...
}
现在我们有了⼀个模型,让我们看看如何使⽤它。
顺便说⼀句,⽰例是⽤Kotlin编写的,因为我认为它是⼀种简洁明了的语⾔,⾮常适合教程。 你同意吗?
使⽤Spoon执⾏代码分析
让我们开始使⽤20多种⽅法打印类列表:
fun examineClassesWithManyMethods(ctModel: CtModel, threshold: Int = 20) {
val classes = ctModel.filterChildren<CtClass<*>> {
it is CtClass<*> && it.methods.size > threshold
}.list<CtClass<*>>()
printTitle("Classes with more than $threshold methods")
printList(classes.asSequence()
.sortedByDescending { it.methods.size }
.map { "${it.qualifiedName} (${it.methods.size})"})
println()
}
fun main(args: Array<String>) {
val launcher = Launcher()
launcher.addInputResource("codebases/jp/javaparser-core/src/main/java")
val model = launcher.buildModel()
examineClassesWithManyMethods(model)
}
在此⽰例中,我们在主函数中设置模型,然后在inspectClassesWithManyMethods中 ,按⽅法数量过滤类,然后使⽤⼏个实⽤程序函数来打印这些类的列表(printTitle , printList )。
运⾏以下代码,我们获得以下输出:
=====================================
| Classes with more than 20 methods |
=====================================
* com.github.pr.Expression (141)
* com.github.javaparser.printer.PrettyPrintVisitor (105)
* com.github.javaparser.ast.visitor.EqualsVisitor (100)
* com.github.javaparser.ast.visitor.NoCommentEqualsVisitor (98)
* com.github.javaparser.ast.visitor.CloneVisitor (95)
* com.github.javaparser.ast.visitor.GenericVisitorWithDefaults (94)
* com.github.javaparser.ast.visitor.ModifierVisitor (94)
* com.github.javaparser.ast.visitor.VoidVisitorWithDefaults (94)
* com.github.javaparser.ast.visitor.HashCodeVisitor (93)
* com.github.javaparser.ast.visitor.NoCommentHashCodeVisitor (93)
* com.github.javaparser.ast.visitor.ObjectIdentityEqualsVisitor (93)
* com.github.javaparser.ast.visitor.ObjectIdentityHashCodeVisitor (93)
* com.github.javaparser.ast.stmt.Statement (92)
* com.github.javaparser.ast.visitor.GenericListVisitorAdapter (92)
* com.github.javaparser.ast.visitor.GenericVisitorAdapter (92)
* com.github.javaparser.ast.visitor.VoidVisitorAdapter (92)
* com.github.javaparser.ast.Node (62)
* com.github.javaparser.ast.NodeList (62)
* com.github.pe.Type (55)
* com.github.javaparser.ast.body.BodyDeclaration (50)
* com.github.dules.ModuleDirective (44)
* com.github.javaparser.ast.CompilationUnit (44)
* com.github.javaparser.JavaParser (39)
* com.pes.ResolvedReferenceType (37)
* com.github.javaparser.utils.SourceRoot (34)
* com.github.javaparser.ast.body.CallableDeclaration (29)
* com.github.javaparser.ast.body.MethodDeclaration (28)
* com.github.javaparser.printer.PrettyPrinterConfiguration (27)
* com.amodel.PropertyMetaModel (26)
* com.github.pe.WildcardType (25)
* com.github.pr.ObjectCreationExpr (24)
* com.github.pe.PrimitiveType (24)
* com.github.javaparser.printer.lexicalpreservation.NodeText (24)
* com.github.javaparser.utils.VisitorList (24)
* com.github.javaparser.printer.lexicalpreservation.Difference (23)
* com.github.javaparser.astments.Comment (22)
* com.github.pr.FieldAccessExpr (22)
* com.github.pe.ClassOrInterfaceType (22)
* com.github.javaparser.utils.Utils (22)
* com.github.javaparser.JavaToken (22)
* com.github.javaparser.ast.body.ClassOrInterfaceDeclaration (21)
* com.github.javaparser.ast.body.FieldDeclaration (21)
* com.github.pr.MethodCallExpr (21)
* com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt (21)
* com.github.javaparser.ast.stmt.IfStmt (21)
* com.github.javaparser.ParserConfiguration (21)
现在让我们尝试其他的东西。 让我们尝试查所有测试类,并确保其名称以“ Test”结尾。 测试类将是⾄少具有⽤org.unit.Test注释的⽅法的类。
fun CtClass<*>.isTestClass() = hods.any { it.annotations.any { it.annotationType.qualifiedName == "org.junit.Test" } }
fun verifyTestClassesHaveProperName(ctModel: CtModel) {
val testClasses = ctModel.filterChildren<CtClass<*>> { it is CtClass<*> && it.isTestClass() }.list<CtClass<*>>()
val testClassesNamedCorrectly = testClasses.filter { dsWith("Test") }
val testClassesNotNamedCorrectly = testClasses.filter { it !in testClassesNamedCorrectly }
printTitle("Test classes named correctly")
println("N Classes named correctly: ${testClassesNamedCorrectly.size}")
println("N Classes not named correctly: ${testClassesNotNamedCorrectly.size}")
printList(testClassesNotNamedCorrectly.asSequence().sortedBy { it.qualifiedName }.map { it.qualifiedName })
}
fun main(args: Array<String>) {
val launcher = Launcher()
launcher.addInputResource("codebases/jp/javaparser-core/src/main/java")
launcher.addInputResource("codebases/jp/javaparser-core-testing/src/test/java")
launcher.addInputResource("libs/junit-vintage-engine-4.12.3.jar")
val model = launcher.buildModel()
verifyTestClassesHaveProperName(model)
}
构建模型与以前⼏乎相同,我们只是添加了更多的源⽬录和JAR,作为测试模块对JUnit的依赖。javaparser野外
在verifyTestClassesHaveProperName中,我们:
过滤所有属于测试类的类 (它们⾄少具有⼀个⽤org.junit.Test注释的⽅法)
查所有名称以Test结尾的测试类,以及所有不包含
我们打印要修复的类的列表以及有关它们的⼀些统计信息
让我们运⾏这段代码,我们得到以下结果:
================================
| Test classes named correctly |
================================
N Classes named correctly: 124
N Classes not named correctly: 2
* com.github.javaparser.wiki_samples.CreatingACompilationUnitFromScratch
* com.github.javaparser.venode.RemoveDeleteNodeFromAst
当然,这些都是⾮常简单的⽰例,但希望它们⾜以显⽰Spoon和代码分析的潜⼒。 处理代表您的代码的模型,提取有趣的信息并验证是否遵守某些语义规则是相当容易的。
有关更⾼级的⽤法,您还可以查看有关本⽂。
使⽤Spoon执⾏代码⽣成
让我们看⼀个考虑⼀个常见任务的代码⽣成⽰例:JSON的代码序列化和反序列化。 我们将从采⽤开始,然后将⽣成表⽰JSON模式描述的实体的类。
这是⼀个相当⾼级的⽰例,我花了⼀些时间熟悉Spoon才能编写它。 我还不得不向他们的团队提出⼀些问题,以解决⼀些问题。 的确,这段代码绝⾮易事,但是我认为我们应该认为这是⼀个⾮常复杂的功能,因此对我来说听起来很公平。
好的,现在让我们进⼊代码。
这是⼀个JSON模式:
{
"$id": "example/arrays.schema.json",
"$schema": "/draft-07/schema#",
"description": "A representation of a person, company, organization, or place",
"type": "object",
"properties": {
"fruits": {
"type": "array",
"items": {
"type": "string"
}
},
"vegetables": {
"type": "array",
"items": { "$ref": "#/definitions/veggie" }
}
},
"definitions": {
"veggie": {
"type": "object",
"required": [ "veggieName", "veggieLike" ],
"properties": {
"veggieName": {
"type": "string",
"description": "The name of the vegetable."
},
"veggieLike": {
"type": "boolean",
"description": "Do I like this vegetable?"
}
}
}
}
}
在顶层,我们可以看到整个架构所代表的实体。 我们知道它将被表⽰为⼀个对象并具有两个属性:
⽔果 :字符串数组
蔬菜 :⼀系列蔬菜 ,其中蔬菜是下⾯描述的另⼀个对象,在“定义”部分中
在定义部分,我们可以看到素⾷是具有两个属性的对象:
veggieName :字符串
veggieLike :布尔值
我们应该得到什么
我们想要得到的是两个java类:⼀个代表整个模式,⼀个代表单个蔬菜。 这两个类应允许读取和写⼊单个字段,将实例序列化为JSON以及从JSON反序列化实例。
我们的代码应⽣成两个类:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。