淄博网站建设铭盛信息,美容整形网站模板,平面设计班培训入门,wordpress显示图片spoon javaSpoon是分析#xff0c;生成和转换Java代码的工具。 在本文中#xff0c;我们将看到通过使用以编程方式处理代码的技术可以实现什么。 我认为这些技术不是很为人所知或使用#xff0c;这很遗憾#xff0c;因为它们可能非常有用。 谁知道#xff0c;即使您不想使… spoon java Spoon是分析生成和转换Java代码的工具。 在本文中我们将看到通过使用以编程方式处理代码的技术可以实现什么。 我认为这些技术不是很为人所知或使用这很遗憾因为它们可能非常有用。 谁知道即使您不想使用Spoon甚至不处理Java代码而是使用CPythonKotlin或其他语言某些想法对于您当前的项目也可能有用。 让我们学习如何以更智能的方式编程。 Spoon具有与JavaParser重叠的一些功能而JavaParser是我贡献的框架。 对于某些任务Spoon可能是更好的选择而对于另一些任务JavaParser具有明显的优势。 稍后我们将深入探讨这些工具之间的差异。 本文由一个伴随库与所有代码配对 ftomassetti / spoon-examples 使用代码处理技术可以实现什么 勺子和一般的代码处理工具可用于 代码分析 计算源代码指标例如找出有多少类具有一定数量以上的方法 代码生成 以编程方式生成重复代码。 代码转换 自动重构例如在构造函数中指定的字段中转换几种方法中使用的参数 这三个大家族与我们与代码交互的方式大致不同 在代码分析中代码是我们用来产生非代码输出的输入 在代码生成中我们使用一些通常不是代码的输入或者使用与我们输出的语言相同的代码。 输出是代码 在代码转换中相同的代码库是输入和输出 设置汤匙 要设置汤匙您需要提供 要分析的代码 所有依赖关系当然还有依赖关系的依赖关系 利用此信息Spoon可以构建代码模型。 在该模型上您可以执行相当高级的分析。 这与JavaParser的工作方式不同。 如果需要在JavaParser中您可以仅构建代码的轻量级模型而无需考虑依赖关系。 当您没有可用的依赖项或需要执行简单快速的操作时这将很有用。 您还可以通过启用符号解析来进行更高级的分析但这是可选的并且在仅某些依赖项可用时也可以使用。 我喜欢Spoon的一件事是支持从Maven进行配置。 我认为这是一个非常有用的功能。 不过我只想得到Gradle的支持。 在我们的示例中我们不使用Maven配置我们仅指定一个包含代码的目录。 在我们的案例中我们正在检查JavaParser的核心模块该模块的依赖项为零因此我们无需指定任何JAR即可构建代码模型。 fun main(args: ArrayString) {val launcher Launcher()launcher.addInputResource(codebases/jp/javaparser-core/src/main/java)launcher.environment.noClasspath trueval model launcher.buildModel()...
} 现在我们有了一个模型让我们看看如何使用它。 顺便说一句示例是用Kotlin编写的因为我认为它是一种简洁明了的语言非常适合教程。 你同意吗 使用Spoon执行代码分析 让我们开始使用20多种方法打印类列表 fun examineClassesWithManyMethods(ctModel: CtModel, threshold: Int 20) {val classes ctModel.filterChildrenCtClass* {it is CtClass* it.methods.size threshold}.listCtClass*()printTitle(Classes with more than $threshold methods)printList(classes.asSequence().sortedByDescending { it.methods.size }.map { ${it.qualifiedName} (${it.methods.size})})println()
}fun main(args: ArrayString) {val launcher Launcher()launcher.addInputResource(codebases/jp/javaparser-core/src/main/java)launcher.environment.noClasspath trueval model launcher.buildModel()examineClassesWithManyMethods(model)
} 在此示例中我们在主函数中设置模型然后在inspectClassesWithManyMethods中 按方法数量过滤类然后使用几个实用程序函数来打印这些类的列表printTitle printList 。 运行以下代码我们获得以下输出
| Classes with more than 20 methods |
* com.github.javaparser.ast.expr.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.javaparser.ast.type.Type (55)* com.github.javaparser.ast.body.BodyDeclaration (50)* com.github.javaparser.ast.modules.ModuleDirective (44)* com.github.javaparser.ast.CompilationUnit (44)* com.github.javaparser.JavaParser (39)* com.github.javaparser.resolution.types.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.github.javaparser.metamodel.PropertyMetaModel (26)* com.github.javaparser.ast.type.WildcardType (25)* com.github.javaparser.ast.expr.ObjectCreationExpr (24)* com.github.javaparser.ast.type.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.ast.comments.Comment (22)* com.github.javaparser.ast.expr.FieldAccessExpr (22)* com.github.javaparser.ast.type.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.javaparser.ast.expr.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() this.methods.any { it.annotations.any { it.annotationType.qualifiedName org.junit.Test } }fun verifyTestClassesHaveProperName(ctModel: CtModel) {val testClasses ctModel.filterChildrenCtClass* { it is CtClass* it.isTestClass() }.listCtClass*()val testClassesNamedCorrectly testClasses.filter { it.simpleName.endsWith(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: ArrayString) {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)launcher.environment.noClasspath trueval model launcher.buildModel()verifyTestClassesHaveProperName(model)
} 构建模型与以前几乎相同我们只是添加了更多的源目录和JAR作为测试模块对JUnit的依赖。 在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.wiki_samples.removenode.RemoveDeleteNodeFromAst 当然这些都是非常简单的示例但希望它们足以显示Spoon和代码分析的潜力。 处理代表您的代码的模型提取有趣的信息并验证是否遵守某些语义规则是相当容易的。 有关更高级的用法您还可以查看有关使用Spoon进行体系结构实施的本文。 使用Spoon执行代码生成 让我们看一个考虑一个常见任务的代码生成示例JSON的代码序列化和反序列化。 我们将从采用JSON模式开始然后将生成表示JSON模式描述的实体的类。 这是一个相当高级的示例我花了一些时间熟悉Spoon才能编写它。 我还不得不向他们的团队提出一些问题以解决一些问题。 的确这段代码绝非易事但是我认为我们应该认为这是一个非常复杂的功能因此对我来说听起来很公平。 好的现在让我们进入代码。 这是一个JSON模式 {$id: https://example.com/arrays.schema.json,$schema: http://json-schema.org/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反序列化实例。 我们的代码应生成两个类 package com.thefruit.company;public class FruitThing implements com.strumenta.json.JsonSerializable {private java.util.Listjava.lang.String fruits;public java.util.Listjava.lang.String getFruits() {return fruits;}public void setFruits(java.util.Listjava.lang.String fruits) {this.fruits fruits;}private java.util.Listcom.thefruit.company.Veggie vegetables;public java.util.Listcom.thefruit.company.Veggie getVegetables() {return vegetables;}public void setVegetables(java.util.Listcom.thefruit.company.Veggie vegetables) {this.vegetables vegetables;}public com.google.gson.JsonObject serialize() {com.google.gson.JsonObject res new com.google.gson.JsonObject();res.add(fruits, com.strumenta.json.SerializationUtils.serialize(fruits));res.add(vegetables, com.strumenta.json.SerializationUtils.serialize(vegetables));return res;}public static com.thefruit.company.FruitThing unserialize(com.google.gson.JsonObject json) {com.thefruit.company.FruitThing res new com.thefruit.company.FruitThing();res.setFruits((java.util.List) com.strumenta.json.SerializationUtils.unserialize(json.get(fruits), com.google.gson.reflect.TypeToken.getParameterized(java.util.List.class, java.lang.String.class)));res.setVegetables((java.util.List) com.strumenta.json.SerializationUtils.unserialize(json.get(vegetables), com.google.gson.reflect.TypeToken.getParameterized(java.util.List.class, com.thefruit.company.Veggie.class)));return res;}
} 和 package com.thefruit.company;public class Veggie implements com.strumenta.json.JsonSerializable {private java.lang.String veggieName;public java.lang.String getVeggieName() {return veggieName;}public void setVeggieName(java.lang.String veggieName) {this.veggieName veggieName;}private boolean veggieLike;public boolean getVeggieLike() {return veggieLike;}public void setVeggieLike(boolean veggieLike) {this.veggieLike veggieLike;}public com.google.gson.JsonObject serialize() {com.google.gson.JsonObject res new com.google.gson.JsonObject();res.add(veggieName, com.strumenta.json.SerializationUtils.serialize(veggieName));res.add(veggieLike, com.strumenta.json.SerializationUtils.serialize(veggieLike));return res;}public static com.thefruit.company.Veggie unserialize(com.google.gson.JsonObject json) {com.thefruit.company.Veggie res new com.thefruit.company.Veggie();res.setVeggieName((java.lang.String) com.strumenta.json.SerializationUtils.unserialize(json.get(veggieName), com.google.gson.reflect.TypeToken.get(java.lang.String.class)));res.setVeggieLike((boolean) com.strumenta.json.SerializationUtils.unserialize(json.get(veggieLike), com.google.gson.reflect.TypeToken.get(boolean.class)));return res;}
} 这是我们如何使用这两个类的示例 package com.thefruit.company;import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;import java.util.Arrays;public class Example {public static void main(String[] args) {FruitThing ft new FruitThing();ft.setFruits(Arrays.asList(Banana, Pear, Apple));Veggie cucumber new Veggie();cucumber.setVeggieLike(false);cucumber.setVeggieName(Cucumber);Veggie carrot new Veggie();carrot.setVeggieLike(true);carrot.setVeggieName(Carrot);ft.setVegetables(Arrays.asList(cucumber, carrot));Gson gson new GsonBuilder().setPrettyPrinting().create();System.out.println(gson.toJson(ft.serialize()));JsonElement serialized ft.serialize();FruitThing unserializedFt FruitThing.unserialize(serialized.getAsJsonObject());System.out.println(Fruits: unserializedFt.getFruits());}
} 在示例中我们构建了FruitThing和几个Veggies的实例。 然后我们对它们进行序列化并反序列化它们以便我们可以证明序列化和反序列化都可以工作。 生成过程一般组织 生成过程将生成一组GeneratedJavaFile实例每个实例都有自己的文件名和代码。 以后我们可以将它们写入文件或在内存中进行编译。 在程序的主要功能中我们将读取JSON模式并将其传递给函数generateJsonSchema 。 我们将其与两个参数一起传递首先在其中生成我们的类的包的名称然后是代表整个架构的类的名称。 一旦我们获得了生成的类我们就将它们打印在屏幕上以快速浏览。 data class GeneratedJavaFile(val filename: String, val code: String)fun main(args: ArrayString) {Dummy::class.java.getResourceAsStream(/a_json_schema.json).use {val generatedClasses generateJsonSchema(it, com.thefruit.company, FruitThing)generatedClasses.forEach {println(*.repeat(it.filename.length))println(it.filename)println(*.repeat(it.filename.length))println(it.code)}}
} 好的所以魔术发生在generateJsonSchema中对吗 fun generateJsonSchema(jsonSchema: InputStream, packageName: String, rootClassName: String) : ListGeneratedJavaFile {val rawSchema JSONObject(JSONTokener(jsonSchema))val schema SchemaLoader.load(rawSchema) as ObjectSchemaval cus generateClasses(schema, packageName, rootClassName)val pp DefaultJavaPrettyPrinter(StandardEnvironment())return cus.map { cu -pp.calculate(cu, cu.declaredTypes)val filename cu.declaredTypes[0].qualifiedName.replace(., File.separatorChar) .javaGeneratedJavaFile(filename, pp.result)}
} 在generateJsonSchema中我们解析提供该模式的InputStream并调用generateClasses 这将返回一堆CompilationUnits。 基本上每个CompilationUnit都是单个Java文件的抽象语法树。 一旦获得了这些编译单元就将它们打印为Java代码。 我们还计算适当的文件名并实例化GeneratedJavaFile实例。 因此看来我们现在来看一下generateClasses 。 fun generateClasses(schema: ObjectSchema, packageName: String, rootClassName: String) : ListCompilationUnit {// First we create the classesval pack CtPackageImpl()pack.setSimpleNameCtPackage(packageName)val classProvider ClassProvider(pack)schema.generateClassRecursively(classProvider, rootClassName)// Then we put them in compilation units and we generate themreturn classProvider.classesForObjectSchemas.map {val cu CompilationUnitImpl()cu.isAutoImport truecu.declaredPackage packcu.declaredTypes listOf(it.value)cu}.toList()
} 在generateClasses中我们首先创建包 CtPackageImpl类。 我们将使用它来生成所有类。 我们将其保留在ClassProvider类中。 它将用于生成和跟踪我们将生成的类。 然后我们调用添加到架构的扩展方法称为generateClassRecursively 。 最后我们将从classProvider中获取类并将其放入CompilationUnits中。 private fun Schema.generateClassRecursively(classProvider: ClassProvider, name: String? null) {when (this) {is ObjectSchema - {classProvider.register(this, this.generateClass(classProvider, name))this.propertySchemas.forEach { it.value.generateClassRecursively(classProvider) }}is ArraySchema - this.allItemSchema.generateClassRecursively(classProvider)is StringSchema, is BooleanSchema - nullis ReferenceSchema - this.referredSchema.generateClassRecursively(classProvider)else - TODO(not implemented: ${this.javaClass})}
} generateClassRecursively会发生什么 基本上我们寻找定义对象的模式并为每个对象生成一个类。 我们还对模式进行爬网以查找属性以查看它们是否间接定义或使用了我们可能要为其生成类的其他对象模式。 在ObjectSchema的扩展方法generateClass中生成一个类。 当它产生一个类时我们将其传递给classProvider以便对其进行记录。 private fun ObjectSchema.generateClass(classProvider: ClassProvider, name: String? null): CtClassAny {return CtClassImplAny().let { ctClass -val packag classProvider.packpackag.types.add(ctClass)ctClass.setParent(packag)ctClass.setVisibilityCtModifiable(ModifierKind.PUBLIC)ctClass.setSimpleNameCtClassAny(name ?: this.schemaLocation.split(/).last().capitalize())ctClass.setSuperInterfacesCtTypeAny(setOf(createTypeReference(JsonSerializable::class.java)))this.propertySchemas.forEach {ctClass.addProperty(it.key, it.value, classProvider)}addSerializeMethod(ctClass, this, classProvider)addUnserializeMethod(ctClass, this, classProvider)ctClass}
} 到目前为止我们已经设置了对架构进行爬网并决定生成什么的逻辑但是我们没有看到很多Spoon特定的API。 这在generateClass中发生了变化。 在这里我们首先实例化CtClassImpl然后 设置适当的包从classProvider获得 将课程设为公开 指定类的名称如果类代表整个模式我们可以将其作为参数接收否则我们可以从模式本身派生它 查看单个属性并在addProperty中处理它们 调用addSerializeMethod添加一个序列化方法我们将使用该方法从此类的实例生成JSON 那么我们如何添加属性 fun CtClass*.addProperty(name: String, schema: Schema, classProvider: ClassProvider) {val field CtFieldImplAny().let {it.setSimpleNameCtFieldAny(name)it.setTypeCtFieldAny(schema.toType(classProvider))it.setVisibilityCtFieldAny(ModifierKind.PRIVATE)}this.addFieldAny, Nothing(field)addGetter(this, field)addSetter(this, field)
} 我们只需添加一个字段 CtField 。 我们设置正确的名称类型和可见性并将其添加到类中。 目前我们还没有生成getter或setter。 生成过程序列化 在本节中我们将看到如何生成类的serialize方法。 对于我们的两个类它们看起来像这样 public class FruitThing implements com.strumenta.json.JsonSerializable {...public com.google.gson.JsonObject serialize() {com.google.gson.JsonObject res new com.google.gson.JsonObject();res.add(fruits, com.strumenta.json.SerializationUtils.serialize(fruits));res.add(vegetables, com.strumenta.json.SerializationUtils.serialize(vegetables));return res;}...
}public class Veggie implements com.strumenta.json.JsonSerializable {...public com.google.gson.JsonObject serialize() {com.google.gson.JsonObject res new com.google.gson.JsonObject();res.add(veggieName, com.strumenta.json.SerializationUtils.serialize(veggieName));res.add(veggieLike, com.strumenta.json.SerializationUtils.serialize(veggieLike));return res;}...
} 这是生成这种方法的切入点 fun addSerializeMethod(ctClass: CtClassImplAny, objectSchema: ObjectSchema, classProvider: ClassProvider) {val method CtMethodImplAny().let {it.setVisibilityCtModifiable(ModifierKind.PUBLIC)it.setTypeCtTypedElementAny(jsonObjectType)it.setSimpleNameCtMethodAny(serialize)val statements LinkedListCtStatement()statements.add(createLocalVar(res, jsonObjectType, objectInstance(jsonObjectType)))objectSchema.propertySchemas.forEach { statements.addAll(addSerializeStmts(it, classProvider)) }statements.add(returnStmt(localVarRef(res)))it.setBodyBlock(statements)it}ctClass.addMethodAny, CtTypeAny(method)
} 我们实例化CtMethodImpl然后 我们设置方法的可见性 我们将返回类型设置为JSONObject 我们将名称设置为序列化 我们创建JSONObject类型的res变量 对于每个属性我们将生成序列化语句以将该属性的值添加到res中 最后我们添加一个return语句并将此块设置为方法的主体 在这里我们使用了一堆实用程序方法来简化代码因为Spoon API非常冗长。 例如我们使用createLocalVar和objectInstance 如下所示 fun createLocalVar(name: String, type: CtTypeReferenceAny, value: CtExpressionAny? null) : CtLocalVariableAny {return CtLocalVariableImplAny().let {it.setSimpleNameCtNamedElement(name)it.setTypeCtTypedElementAny(type)if (value ! null) {it.setAssignmentCtRHSReceiverAny(value)}it}
}fun objectInstance(type: CtTypeReferenceAny) : CtConstructorCallAny {return CtConstructorCallImplAny().let {it.setTypeCtTypedElementAny(type)it}
} 现在我们来看看如何为特定属性生成序列化方法的语句。 un addSerializeStmts(entry: Map.EntryString, Schema,classProvider: ClassProvider): CollectionCtStatement {return listOf(instanceMethodCall(add, listOf(stringLiteral(entry.key),staticMethodCall(serialize,listOf(fieldRef(entry.key)),createTypeReference(SerializationUtils::class.java))), target localVarRef(res)))
} 基本上我们委托给SerializationUtils.serialize 。 该方法将包含在运行时库中以与我们生成的代码一起使用。 看起来是这样的 public class SerializationUtils {public static JsonElement serialize(Object value) {if (value instanceof JsonSerializable) {return ((JsonSerializable) value).serialize();}if (value instanceof Iterable?) {com.google.gson.JsonArray jsonArray new com.google.gson.JsonArray();for (Object element : (Iterable?)value) {jsonArray.add(com.strumenta.json.SerializationUtils.serialize(element));}return jsonArray;}if (value instanceof Boolean) {return new JsonPrimitive((Boolean)value);}if (value instanceof String) {return new JsonPrimitive((String)value);}throw new UnsupportedOperationException(Value: value ( value.getClass().getCanonicalName() ));}public static Object unserialize(JsonElement json, TypeToken? expectedType) {...to be discussed later...}
} 我们序列化某个属性的方式取决于其类型。 简单值字符串和布尔值很容易而数组则比较棘手。 对于任何可通过JsonSerializable进行调用的对象我们都调用相应的serialize方法。 我们为什么要这样做 这样我们就可以使用为类 FruitThing和Veggie 生成的序列化方法。 生成过程反序列化 让我们看看我们应该能够生成的反序列化方法 public class FruitThing implements com.strumenta.json.JsonSerializable {...public static com.thefruit.company.FruitThing unserialize(com.google.gson.JsonObject json) {com.thefruit.company.FruitThing res new com.thefruit.company.FruitThing();res.setFruits((java.util.List) com.strumenta.json.SerializationUtils.unserialize(json.get(fruits), com.google.gson.reflect.TypeToken.getParameterized(java.util.List.class, java.lang.String.class)));res.setVegetables((java.util.List) com.strumenta.json.SerializationUtils.unserialize(json.get(vegetables), com.google.gson.reflect.TypeToken.getParameterized(java.util.List.class, com.thefruit.company.Veggie.class)));return res;}...
}public class Veggie implements com.strumenta.json.JsonSerializable {...public static com.thefruit.company.Veggie unserialize(com.google.gson.JsonObject json) {com.thefruit.company.Veggie res new com.thefruit.company.Veggie();res.setVeggieName((java.lang.String) com.strumenta.json.SerializationUtils.unserialize(json.get(veggieName), com.google.gson.reflect.TypeToken.get(java.lang.String.class)));res.setVeggieLike((boolean) com.strumenta.json.SerializationUtils.unserialize(json.get(veggieLike), com.google.gson.reflect.TypeToken.get(boolean.class)));return res;}...
} 负责生成此类方法的代码是哪一部分 毫不奇怪它称为addUnserializeMethod fun addUnserializeMethod(ctClass: CtClassImplAny, objectSchema: ObjectSchema, classProvider: ClassProvider) {val method CtMethodImplAny().let {it.setTypeCtTypedElementAny(createTypeReference(ctClass))it.setModifiersCtModifiable(setOf(ModifierKind.STATIC, ModifierKind.PUBLIC))it.setSimpleNameCtMethodAny(unserialize)it.setParametersCtExecutableAny(listOf(CtParameterImplAny().let {it.setSimpleNameCtNamedElement(json)it.setTypeCtTypedElementAny(jsonObjectType)it}))val thisClass createTypeReference(ctClass.qualifiedName)val statements LinkedListCtStatement()statements.add(createLocalVar(res, thisClass, objectInstance(thisClass)))objectSchema.propertySchemas.forEach { statements.addAll(addUnserializeStmts(it, classProvider)) }statements.add(returnStmt(localVarRef(res)))it.setBodyBlock(statements)it}ctClass.addMethodAny, CtTypeAny(method)
} 结构与我们之前所见非常相似。 当然这里涉及的是对addUnserializeStmts的调用。 fun addUnserializeStmts(entry: Map.EntryString, Schema,classProvider: ClassProvider): CollectionCtStatement {// call to get the field, e.g. json.get(veggieName)val getField instanceMethodCall(get,listOf(stringLiteral(entry.key)),target localVarRef(json))// call to create the TypeToken, e.g., TypeToken.get(String.class)// or TypeToken.getParameterized(List.class, String.class)val ctFieldType entry.value.toType(classProvider)val createTypeToken if (ctFieldType is CtTypeReferenceAny ctFieldType.actualTypeArguments.isNotEmpty()) {staticMethodCall(getParameterized,(listOf(classField(ctFieldType)) ctFieldType.actualTypeArguments.map { classField(it) }).toList() as ListCtExpressionAny,createTypeReference(TypeToken::class.java))} else {staticMethodCall(get,listOf(classField(ctFieldType)),createTypeReference(TypeToken::class.java))}val callToUnserialize staticMethodCall(unserialize,listOf(getField, createTypeToken),createTypeReference(com.strumenta.json.SerializationUtils))val castedCallToUnserialize cast(callToUnserialize, entry.value.toType(classProvider))return listOf(instanceMethodCall(set entry.key.capitalize(), listOf(castedCallToUnserialize), target localVarRef(res)))
} 现在事情变得复杂了。 我们基本上必须为每个属性调用设置器。 给设置器我们将使用适当的转换将反序列化的结果传递给匹配属性的类型。 要调用反序列化我们需要一个TypeToken用于引导反序列化过程。 我们想以不同的方式对相同的值进行反序列化具体取决于我们是否要获取整数或字符串类型标记告诉我们要获取的内容。 生成过程注释 要构建此示例我们必须编写许多实用程序方法。 我们在本文中未显示的整个示例的某些部分但是您可以在协同存储库中找到所有这些代码。 还要注意我们可以将代码保存到文件中并使用编译器API进行编程编译。 如果需要我们甚至可以在内存中编译它。 在实际情况下我建议这样做而不是像我在本教程中所做的那样手动将代码粘贴复制到文件中。 使用Spoon执行代码转换 当使用大型代码库或防止重复性任务出现人为错误时代码转换可能非常有用。 例如假设您决定更改必须实施特定模式的方式。 假设您在代码库中使用了几十个单例模式并且您想确保每次懒惰地创建实例即仅在第一次需要时。 您可以自动执行此转换。 或者假设您正在更新正在使用的库并且您所依赖的特定方法已重命名或者其参数顺序已更改。 同样您可以通过使用代码转换来解决此问题。 对于我们的示例我们将采取一些简单的措施。 我们将重构一个类。 在此类中我们有几种方法可以接收特定的参数。 鉴于基本上每个操作都需要此参数我们决定将其移至构造函数并将其保存为字段实例。 然后我们要转换获取该参数的所有方法以使它们不再需要它而是访问相应的字段。 让我们看一下转换的样子 // original code
class MyClass {MyClass() {} void foo(MyParam param, String otherParam) {param.doSomething();}int bar(MyParam param) {return param.count();}}// transformed code
class MyClass {MyParam param;MyClass(MyParam param) {this.param param;} void foo(String otherParam) {this.param.doSomething();} int bar() { return this.param.count(); }} 在这个例子中我们只转换定义方法的类 在实际情况下我们可能还希望转换这些方法的调用。 我们如何实现此代码转换 让我们开始看一下代码转换示例的主要方法以便我们可以看到常规结构 fun main(args: ArrayString) {val originalCode class MyClass {MyClass() {}void foo(MyParam param, String otherParam) {param.doSomething();}int bar(MyParam param) {return param.count();}}val parsedClass Launcher.parseClass(originalCode)ParamToFieldRefactoring(param, createTypeReference(com.strumenta.MyParam)).refactor(parsedClass)println(parsedClass.toCode())
} 如您所见我们 解析代码 应用我们的类ParamToFieldRefactoring中定义的重构 我们打印结果代码 有趣的地方当然是ParamToFieldRefactoring class ParamToFieldRefactoring(val paramName: String, val paramType: CtTypeReferenceAny) {fun refactor(clazz: CtClass*) {// Add field to the classclazz.addFieldAny, Nothing(CtFieldImplAny().let {it.setSimpleNameCtNamedElement(paramName)it.setTypeCtTypedElementAny(paramType)it})// Receive the value for the field in each constructorclazz.constructors.forEach {it.addParameterNothing(CtParameterImplAny().let {it.setSimpleNameCtNamedElement(paramName)it.setTypeCtTypedElementAny(paramType)it})it.body.statements.add(CtAssignmentImplAny, Any().let {it.setAssignedCtAssignmentAny, Any(qualifiedFieldAccess(paramName, clazz.qualifiedName))it.setAssignmentCtRHSReceiverAny(localVarRef(paramName))it})}clazz.methods.filter { findParamToChange(it) ! null }.forEach {val param findParamToChange(it)!!CtIterator(it).forEach {if (it is CtParameterReference* it.simpleName paramName) {val cfr CtFieldReferenceImplAny()cfr.setSimpleNameCtReference(paramName)cfr.setDeclaringTypeCtFieldReferenceAny(createTypeReference(clazz.qualifiedName))it.replace(cfr)}}param.delete()}}fun findParamToChange(method: CtMethod*) : CtParameter*? {return method.parameters.find { it.simpleName paramName }}
} 首先我们将新字段添加到类中 clazz.addFieldAny, Nothing(CtFieldImplAny().let {it.setSimpleNameCtNamedElement(paramName)it.setTypeCtTypedElementAny(paramType)it}) 然后向所有构造函数添加一个参数以便我们可以接收该值并将其分配给该字段 // Receive the value for the field in each constructorclazz.constructors.forEach {it.addParameterNothing(CtParameterImplAny().let {it.setSimpleNameCtNamedElement(paramName)it.setTypeCtTypedElementAny(paramType)it})it.body.statements.add(CtAssignmentImplAny, Any().let {it.setAssignedCtAssignmentAny, Any(qualifiedFieldAccess(paramName, clazz.qualifiedName))it.setAssignmentCtRHSReceiverAny(localVarRef(paramName))it})} 请注意在实际应用程序中我们可能还需要考虑该类过去仅具有默认构造函数的情况并添加一个采用将单个值分配给字段的全新构造函数。 为了简单起见我们在示例中忽略了这一点。 最后我们要修改所有方法。 如果他们使用的参数名称被考虑我们将删除该参数。 我们还将查找对该参数的所有引用并将其替换为对新字段的引用 clazz.methods.filter { findParamToChange(it) ! null }.forEach {val param findParamToChange(it)!!CtIterator(it).forEach {if (it is CtParameterReference* it.simpleName paramName) {val cfr CtFieldReferenceImplAny()cfr.setSimpleNameCtReference(paramName)cfr.setDeclaringTypeCtFieldReferenceAny(createTypeReference(clazz.qualifiedName))it.replace(cfr)}}param.delete()} 就是这样 现在我们应该只打印代码我们就完成了。 我们如何打印代码 通过一个名为toCode的扩展方法 fun CtClass*.toCode() : String {val pp DefaultJavaPrettyPrinter(StandardEnvironment())val cu CompilationUnitImpl()pp.calculate(cu, listOf(this))return pp.result
}有关代码转换的更多信息 如果您想了解有关使用Spoon进行代码转换的更多信息请看以下内容 CocoSpoon 用于检测Java代码以计算代码覆盖率的工具 Trebuchet 一个概念证明展示了如何使用Spoon将Java代码转换为C 。 这篇文章是如何诞生的 Spoon是处理Java代码的工具。 在某种程度上它可以看作是JavaParser的竞争对手。 我一直想研究了很长一段时间但我有许多事情一大堆 我想看看和勺子从未到列表的顶部。 然后JavaParser的一些用户指出了关于Spoon项目的讨论讨论了JavaParser和Spoon之间的区别。 在我看来存在一些误解Spoon的贡献者卖出的JavaParser有点短……在成千上万的开发人员和知名公司都在使用JavaParser并对此感到满意之后。 另外JavaParser可能是最著名的Java解析器。 因此我开始与Spoon的贡献者进行讨论这引发了撰写本文的想法。 当这篇文章是在Spoon的贡献者的帮助下撰写的时我是这篇文章的作者而且我还是JavaParser的贡献者所以这是我的“偏见警报” 比较Spoon和JavaParser Spoon是JavaParser的学术替代品。 尽管JavaParser本身实现符号解析这是最困难的部分但Spoon却充当Eclipse Java编译器的包装然后在其之上构建一些高级API。 那么这种选择会有什么后果呢 Eclipse Java编译器已经成熟尽管并非没有错误但它相当可靠 Eclipse Java编译器是一个大型野兽它具有依赖性和复杂的配置 Eclipse Java编译器是……编译器它不是用于符号解析的库因此它不如我们在JavaParser上拥有的自行开发的解决方案灵活。 就个人而言我对成为JavaParser的贡献者有很大的偏见。 我已经习惯了JavaParserSpoon的某些行为对我来说似乎是不自然的。 例如对片段表达式的类型强制转换似乎不起作用。 类访问例如“ String.class”不是由特定的表达式表示而是表示为字段访问。 但是某些功能确实很有用我们也应该在JavaParser中获得它们。 总而言之它们是不同的工具具有不同的功能集而且我认为还存在不同的理念如下所述。 关于文档对于JavaParser来说似乎更好一些我们有一本书可以免费获得并且可以下载数千次还拥有一套教程。 不同的哲学 现在Spoon是在学术环境和法国创立的。 以我的经验法国工程师非常有才华但他们倾向于以“疯狂的方式”重新发明事物。 以该项目采用的许可证为例那是Apache许可证吗 GPL LGPL Eclipse许可证 不这是CeCILL-C免费软件许可协议 。 我从未听说过的许可证专门为遵守某些法国法规而创建。 现在这可能是有史以来最伟大的许可证但是对于一家想要采用该项目的公司他们需要研究一下弄清楚这意味着什么意味着什么如果它与他们正在使用的其他许可证兼容并且以此类推。 我认为如果他们只是选择一个现有许可证事情可能会简单得多。 因为存在现实 在这种现实中公司不想仅使用Spoon就必须学习此许可。 这与我们非常务实的 JavaParser中的方法截然不同。 我们与公司讨论并确定了他们需要哪些许可证然后我们努力为用户提供双重许可证Apache许可证或LGPL。 为什么 因为他们是他们熟悉的选择。 总的来说当我与Spoon的家伙交谈时我有不同的哲学感受。 他们清楚地意识到他们的产品要好得多并且坦率地说对于JavaParser如此受欢迎有些失望。 我们讨论了一些合作的可能性但在我看来这是从我们正确的角度出发。 在JavaParser中我们不认为我们是对的。 我们只是听取用户意见在彼此之间进行讨论然后再努力向前迈进使用户的生活更加轻松。 一个很大的优势就是我们会收到很多反馈因此当我们出错时用户可以帮助我们纠正方向。 关于依赖性到目前为止在JavaParser上我们一直在努力使核心模块保持无依赖性。 我们将来可能会放宽此约束但总的来说我们将依赖性管理视为重要方面。 相反在Spoon中您需要添加一个Maven存储库以使用甚至不在Maven Central或任何知名Maven存储库上的库。 为什么 为什么要让用户的生活变得更加艰难 结论 我认为代码处理功能非常强大它允许我们利用开发人员的技能来自动化部分工作从而减少工作量和错误。 如果您使用大型代码库那么它是在工具箱中的好工具。 至少我认为更多的开发人员应该意识到它提供的可能性。 在Java代码上执行代码处理时Spoon是有效的解决方案。 因此我邀请您熟悉并考虑使用它我想您会帮自己一个忙。 翻译自: https://www.javacodegeeks.com/2019/03/analyze-generate-transform-java-spoon.htmlspoon java