PalladioSimulator/Palladio-ReverseEngineering-SoMoX-SEFF

GitHub: PalladioSimulator/Palladio-ReverseEngineering-SoMoX-SEFF

该项目利用 Eclipse JDT AST 解析器将 Java 源代码逆向转换为 Palladio Component Model 的 SEFF 工件,帮助开发者从遗留代码中自动恢复架构设计并进行性能分析。

Stars: 6 | Forks: 0

# Palladio-ReverseEngineering-SoMoX ## 简介 该项目通过将输入的源代码转换为 Palladio Component Model 工件(即 Service Effect Specification, SEFF)来辅助逆向工程过程。本项目基于由 KIT 软件质量与设计 (SQD) 研究所开发的建模概念——Palladio Component Model (PCM)。它结合 Service Effect Specification (SEFF) 和 Eclipse JDT AST parser,用于创建源代码的抽象模型。它可用于描述所提供组件与所需组件之间的关系,并对它们的输入和输出变量进行建模。 如果您已经熟悉 PCM、SEFF、AST 和 fluent interface 的背景知识,请直接跳转至[目标](#Objectives)。 如果您对该项目已有了解,请直接跳转至[用法](#usage)。 ## 动机与背景 在开发软件时,通常会采用正向工程方法。在定义系统规范之后,会进行软件设计。基于此软件设计,可以以结构化的方式编写源代码。然而,一些遗留项目没有任何可用的规范或设计文档。在这种情况下,逆向工程是一种非常有趣的方法,因为它试图根据现有的源代码构建更高层级的抽象模型。代码分析可能是一项非常耗时且令人疲惫的任务。即使是简单的条件语句,也可能成为决定实现性能高效或缓慢的关键。如果开发人员对代码不熟悉,就很难找到具体的位置。因此,从代码自动生成易于阅读的图表,对开发人员来说将是一个完美的解决方案。 它可以用于逆向工程,以恢复原始设计并找出组件之间的所有连接。它简化了遗留系统中的功能开发与改进过程,并能够实现整洁的代码库,便于快速找到重构选项或进行资源需求分析。 ### Palladio Component Model (PCM) Palladio 是一种软件架构模拟方法,可在模型层面分析软件项目的性能瓶颈、可扩展性问题以及可靠性威胁,并允许进行后续优化。该方法需要不同的工件作为输入来执行模拟。这些工件可以是组件的规范、组件的组合,或者是组件内部函数行为的规范。这种行为规范被称为 Service Effect Specification。 ### Service Effect Specification (SEFF) Service Effect Specification (SEFF) 是一种类似于活动图的方法,用于对组件和函数行为进行抽象建模。它提供了多种方式来指定图表中动作元素的类型。在进行模拟时,可以为这些动作补充资源分配规范,例如 CPU 或 HDD 的使用情况。 ### AST Eclipse 提供了通过 Java Development Tools (JDT) API 访问和操作 Java 源代码的方法。可以通过 Java Model 或 Abstract Syntax Tree (AST) 来访问该 API。在我们的实现中,我们选择采用 AST 的方式,因为它与[下文](#modiscojamopp-version)所述的 MoDisco / JaMoPP 模型相匹配。 AST 是 Java 源代码的详细树状表示,可以通过[访问者模式](#visitor-pattern)进行遍历。它由一个根节点 (ICompilationUnit) 和许多相互构建的不同子节点组成。 AST 的结构类似于 ![AST: 结构](doc/ASTStructure.png "AST: 典型的 AST 结构") 其中每个 MethodDeclaration 描述了根类的一个函数,而每个 Statement 描述了不同的代码片段。AST 可以通过添加诸如 TypeDeclarations 之类的额外信息来丰富,以对关联类进行建模,但我们选择不这样做(参见[局限性与未来工作](#limitations-&-future-work))。 ### 访问者模式 访问者模式是一种常用的行为模式,它提供了将算法与其操作的对象结构分离开来的可能性。它与 AST 的遍历完美契合,因为它使得读取和处理部分对象结构成为可能,而无需直接对其进行修改。 AST 提供了一个接口,可以接受访问者,并在递归循环中调用其访问函数。为了响应不同类型的节点,必须对访问函数进行重载,以确保每种类型及其子节点都能得到正确的处理。由于并不总是希望一直处理到每个子节点的最深处,因此可以设置一个返回值:true 表示继续遍历子节点,而 false 表示停止遍历子节点并返回到父节点。 该模式使我们能够构建非常轻量级的代码结构,并有助于满足所有的目标。 ## 目标: 该项目专注于不同的目标以帮助用户: - 将 Java 源代码转换为包含 SEFF 元素的 Palladio Component Model repository - 理解源代码结构,以便能够对原始代码进行修改和进一步开发 - 通过 PCM 预处理源代码以进行运行时分析 由于该项目之前已有过其他实现,因此目标始终保持不变,但此版本更新了代码库以兼容更新的 Java 版本。为了与其他版本的构建过程保持兼容,我们还创建了一个名为 Ast2SeffJob 的 ` IBlackboardInteractingJob > `。它使用了与其他版本类似的访问者模式,但通过 JDT Ast 模式直接访问代码。更多信息可以在 [JDT 版本部分](#jdt-version)中找到。 ### MoDisco/JaMoPP 版本 本项目是 MoDisco 和 JaMoPP 版本的进一步发展。它是在基于 MoDisco 版本的 JaMopp 版本之上实现的。为了充分理解我们的实现,我们首先描述 MoDisco 和 JaMoPP 之间的区别。 MoDisco 版本是该逆向工程项目的第一个版本,由 Klaus Kogmann 实现。它的结构非常清晰,具有易于理解的映射规则,并且能够直接将代码块映射到 SEFF 动作。解析过程接收 Java 代码,通过 MoDisco 解析器将其解析为 MoDisco 模型,在 GAST2SEFF 类中通过访问者模式进行处理,最后以 XML 格式输出不同的 SEFF 对象。由于对解析器的支持停止,且它无法解析新结构的 Java 代码,MoDisco 版本的开发随之停止,随后新的版本(JaMoPP)得以实现。 JaMoPP 版本有两个不同的里程碑。首先,它由 Michael Langhammer 使用默认的 JaMoPP 解析器实现,但随后对解析器的支持再次停止,并且它无法解析新结构的 Java 代码。研究所没有从头重新创建一切,而是决定将 JaMoPP 解析器替换为 JDT 解析器,并在两个模型之间创建了转换。本文重点关注 JDT 解析器版本。 由于 JaMopp 模型与 MoDisco 模型有所不同,因此需要进行一些预处理才能创建类似于之前的匹配。它无法直接区分 Internal 和 External Actions,因此创建了一个名为“SourceCodeDecorator”的大型库来辅助此过程并进行一些运行时分析。解析过程接收 Java 代码,并使用 JDT 解析器进行解析。然后将 JDT 模型转换为 JaMoPP 模型并进行预处理,接着将其解析到调整后的 GAST2SEFF 类中,在此类中访问者模式处理 JaMoPP 模型,以 XML 格式输出不同的 SEFF。因此,该过程与 MoDisco 版本非常相似,但预处理使整个代码变得非常难以阅读,并且通过将代码从 JDT AST 模型转换为 JaMoPP 模型,产生了一个不必要的额外步骤。 ### JDT 版本 Eclipse Java Development Tools (JDT) 提供了遍历 Java 源代码的功能,并丰富了诸如类型解析和包附属关系等额外信息。本项目尝试了一种新的方法,即通过直接引用 JDT 库来构建访问者模式,从而省去了先前项目实现中的 JaMoPP 依赖。这缓解了僵硬的 JaMoPP 实现,并提供了废弃整个“SourceCodeDecorator”的可能性。由于 JDT 模型可以完全控制哪些 Actions 是 Internal / External Actions,它再次使代码变得整洁且没有任何限制。 解析过程接收 Java 代码,并使用 JDT 解析器对其进行解析,随后直接通过 AST2SEFF 访问者模式对其进行遍历,以输出不同的 SEFF 作为 XML。它再次变得更类似于 MoDisco 版本。 ### 方法对比 由于该项目具有包含不同版本的悠久历史,我们创建了一个表格来对它们进行相互比较。如果您熟悉其中一个版本,这可以很好地概述其他版本的结构。请注意,此表仅表示访问者获取的输入,并不反映 JaMoPP 中所做的预处理(例如,预先收集 For/EnhancedFor/While 到 Loop 中)。SetVariableAction 和 WithInputVariable 是最近添加到 JDT 版本中的,因此没有对应的 JaMoPP / MoDisco 版本。 | **SEFF 元素** | **MoDisco 版本** | **JaMoPP 版本** | **JDT 版本** | |--------------------------|-------------------------------------------------------------|-----------------------------|-----------------------------------------------------------| | LoopAction | ForStatement, EnhancedForStatement, WhileStatement | LoopStatement | ForStatement, EnhancedForStatement, WhileStatement | | BranchAction | SwitchStatement, TryStatement, IfStatement, IfElseStatement | Switch, TryBlock, Condition | SwitchStatement, TryStatement, IfStatement, ElseStatement | | AcquireAction | SynchronizedStatement | SynchronizedBlock | SynchronizedStatement | | InternalCallAction | ExpressionStatement | CallStatement | ExpressionStatement | | ExternalCallAction | ExpressionStatement | CallStatement | ExpressionStatement | | InternalAction | ExpressionStatement | ThisStatement | ExpressionStatement | | SetVariableAction | - | - | ReturnStatement | | WithInputVariable | - | - | ExpressionStatement | ### FluentAPI 由于 PCM 模型的创建可能会变得复杂,因此在本项目中添加了 [FluentAPI](https://github.com/PalladioSimulator/Palladio-Addons-FluentApiModelGenerator) 库。它使用户能够专注于创建功能性代码,从而免去了创建 PCM 对象并相互引用的负担。FluentAPI 还确保所有模型都能以相似的方式创建,从而提高了代码的可读性。为不同的模型元素寻找正确的工厂以及设置所需属性的方法名称对用户并不友好,特别是因为模型对象提供的方法建议远多于创建 repository 模型所需的方法。它还使得像阅读自然语言句子一样阅读代码成为可能,并提供了一个统一的接口,在该接口中可以对 PCM 元模型进行更改,而无需更改代码中的每一处引用。幸运的是,在先前的工作中已经创建好了 FluentAPI 并可以直接用于本实现。 ## 开始使用 既然我们项目的基础已经很清楚了,我们就可以直接开始了。要启动并运行本地副本,请按照以下简单的示例步骤操作。 ### 前置条件 1. 安装 Java (jdk-11) 2. 安装 Maven (最新版) 3. 安装 [Eclipse 2022-12](https://www.eclipse.org/downloads/packages/release/2022-12/r/eclipse-modeling-tools) 4. 打开 Eclipse 并导航至 Help > Install New Software... 5. 安装 [Palladio Build Updatesite](https://updatesite.palladio-simulator.com/palladio-build-updatesite/nightly/)、[Palladio Core Commons](https://updatesite.palladio-simulator.com/palladio-core-commons/nightly/)、[Palladio Reverse Engineering Java](https://updatesite.palladio-simulator.com/palladio-reverseengineering-java/nightly/) 以及 [Palladio FluentAPI Model Generator](https://updatesite.palladio-simulator.com/palladio-addons-fluentapimodelgenerator/nightly/) 1. 始终接受警告并依然安装软件 6. 最终重启后,所有依赖项均已安装完毕,Eclipse 已准备好导入项目文件 ### 安装 以下是如何安装和设置此项目的示例。请确保满足所有[前置条件](#Prerequisites),否则安装可能会失败。 1. 将代码库克隆到您选择的目录中 ``` https: git clone https://github.com/PalladioSimulator/Palladio-ReverseEngineering-SoMoX.git ssh: git clone git@github.com:PalladioSimulator/Palladio-ReverseEngineering-SoMoX.git ``` 2. 打开 Eclipse 并为该项目创建一个新的 workspace。 3. 选择 File > Open Projects From File System... 4. 点击 "Directory" 并在文件系统中选择克隆的仓库目录。 5. 会列出多个项目。选中所有的项目。 6. 点击 "Finish"。 现在您已经准备好审查、测试和开发该实现了。

(返回顶部)

## 用法 为了分析代码,首先必须将其转换为 [AST 表示](https://www.vogella.com/tutorials/EclipseJDT/article.html)。为此,请先创建一个解析器并解析所有想要的文件(或如下例所示的目录)。关于其他设置的作用,可以在 [Eclipse 帮助平台文档](https://help.eclipse.org/latest/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FCompilationUnit.html)中阅读。 这个简单案例的完整实现也可以在我们的[测试](tests/org.palladiosimulator.somox.ast2seff.test/src/org/palladiosimulator/somox/ast2seff/Ast2SeffTest.java)中找到。 首先,需要设置解析器: ``` String javaCoreVersion = JavaCore.latestSupportedJavaVersion(); final ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); parser.setResolveBindings(true); parser.setBindingsRecovery(true); parser.setStatementsRecovery(true); parser.setCompilerOptions(Map.of(JavaCore.COMPILER_SOURCE, javaCoreVersion, JavaCore.COMPILER_COMPLIANCE, javaCoreVersion, JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, javaCoreVersion)); ``` 由于我们想要解析 ".java" 文件,并且需要从 ".jar" 文件中获取环境信息,因此我们创建了一个名为 "get Entries" 的辅助函数: ``` private String[] getEntries(Path dir, String suffix) { try (Stream paths = Files.walk(dir)) { return paths .filter(path -> Files.isRegularFile(path) && path.getFileName().toString().toLowerCase().endsWith(suffix)) .map(Path::toAbsolutePath).map(Path::normalize).map(Path::toString).toArray(i -> new String[i]); } catch (final IOException e) { e.printStackTrace(); return new String[0]; } } ``` 现在解析器已设置完毕并且辅助函数也已定义,我们可以开始解析我们的[目录](tests/org.palladiosimulator.somox.ast2seff.test/src/org/palladiosimulator/somox/ast2seff/res/)并为每个文件创建一个 [Compilation Unit](https://help.eclipse.org/latest/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FCompilationUnit.html)。 ``` Path dir = Path.of("src/org/palladiosimulator/somox/ast2seff/res"); String[] classpathEntries = getEntries(dir, ".jar"); final String[] sources = getEntries(dir, ".java"); final String[] encodings = new String[sources.length]; Arrays.fill(encodings, StandardCharsets.UTF_8.toString()); final Map compilationUnits = new HashMap<>(); try { parser.setEnvironment(classpathEntries, new String[0], new String[0], true); parser.createASTs(sources, encodings, new String[0], new FileASTRequestor() { @Override public void acceptAST(final String sourceFilePath, final CompilationUnit ast) { compilationUnits.put(sourceFilePath, ast); } }, new NullProgressMonitor()); } catch (IllegalArgumentException | IllegalStateException e) { e.printStackTrace(); } return compilationUnits; ``` 由于需要诸如排除私有函数之类的额外过滤,我们选择不将整个 [CompilationUnit-Objects](https://help.eclipse.org/latest/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FCompilationUnit.html) 传递给 ast2SeffJob,而是传递一个从 classNames 到 methodDeclarations 的映射关系,我们将其定义为 [MethodBundlePairs](bundles/org.palladiosimulator.somox.ast2seff/src/org/palladiosimulator/somox/ast2seff/models/MethodBundlePair.java)。 ``` Map> bundleName2methodAssociationMap = new HashMap>(); for (var entry : compUnitMap.entrySet()) { List methodDeclarations = MethodDeclarationFinder.perform(entry.getValue()); for (MethodDeclaration methodDeclaration : methodDeclarations) { List modifierList = (List) methodDeclaration.modifiers(); // Generate a seff for public methods only IExtendedModifier firstModifier = modifierList.get(0); if (firstModifier.isModifier()) { Modifier modifier = (Modifier) firstModifier; if (modifier.isPublic()) { TypeDeclaration typeDeclaration = (TypeDeclaration) methodDeclaration.getParent(); String className = typeDeclaration.getName().toString(); if (bundleName2methodAssociationMap.containsKey(className)) { bundleName2methodAssociationMap.get(className).add(new MethodBundlePair(className, methodDeclaration)); } else { List methodAssociationList = new ArrayList(); methodAssociationList.add(new MethodBundlePair(className, methodDeclaration)); bundleName2methodAssociationMap.put(className, methodAssociationList); } } } } } ``` 现在映射已设置完毕,使用已开发的工作流引擎的 blackboard 实现来存储和获取生成的元素。当调用 ` ast2SeffJob.execute() ` 时,从 AST 到 SEFF 的转换随即开始。 ``` Ast2SeffJob ast2SeffJob = new Ast2SeffJob(); Blackboard blackboard = new Blackboard<>(); blackboard.addPartition("bundleName2methodAssociationMap", bundleName2methodAssociationMap); ast2SeffJob.setBlackboard(blackboard); NullProgressMonitor progressMonitor = new NullProgressMonitor(); ast2SeffJob.execute(progressMonitor); ```

(返回顶部)

## 局限性与未来工作 - 我们当前的模型仅实现了 Guarded Branch Transitions,而省略了 Probabilistic Branch Transitions,因为我们无法对不同分支的概率做出合理的猜测。在未来的工作中,应该添加一种估算概率的方法。 - 我们已经开始探索 Assignment Statement 及其向FF 上下文中的 SetVariableAction 元素的转换。正如我们在探索过程中发现的那样,SetVariableAction 元素用于具有 return 语句的函数。我们停止了进一步的探索,但将初始代码留在了 ExpressionStatement 访问函数中供未来使用。 - 我们的解析器目前为每个解析的文件创建一个 ICompilationUnit,忽略了文件内部的类结构。这将可能解析的代码限制为每个文件只有一个类,因为所有方法都会被解析到匹配的 ICompilationUnit 中。在未来的工作中,可以进行更精确的抽象,即创建更多的 ICompilationUnit,或者包含 TypeDeclaration(在此例中为类结构)。 - 目前,没有添加有关 CPU 或 HDD 需求的资源使用信息。这在以前的 JaMoPP 版本中实现过,但由于我们是从头开始的,因此未包含在此版本中。随着对 JaMoPP 版本进行更多分析,将其引入到 JDT 版本中应该很容易。 - 我们的 internalCallActions 仅实现为深度为 1 的方法内联。这意味着如果一个 InternalCallAction 被调用多次,它们之间并不会相互引用,而是被创建多次。如果方法之间相互引用(且深度大于 1),它们将显示为 InternalActions。 - 其他语句内部的 Expression Statements 可以在进一步的开发中进行建模。为此,需要查明在其他条件内部是否存在方法调用,并将其建模为 InternalAction、InternalCallAction 或 ExternalCallAction。 ## 测试 本项目使用 JUnit Framework 进行质量保证。JUnit 在 Eclipse IDE 中具有良好的集成度,因此做出这个决定是很直接的。对于使用模型,提供了 JUnit 测试。测试以及带有测试类的整体用法的更大示例可以在项目的 [test folder](./tests/org.palladiosimulator.somox.ast2seff.test) 中找到。 我们遵循了一种简单的测试策略,以涵盖每个语句访问者函数的常规测试用例。 四个标准测试用例是: 1. 测试一个空语句 2. 测试内部包含表达式的语句 3. 测试内部包含相同类型语句的语句 4. 测试内部包含另一种语句的语句(循环或分支语句) 必要时,还会对不同类型语句的特定属性进行额外的测试用例测试。
标签:JS文件枚举, 域名枚举