`

了解全新的 Eclipse 包管理机制

阅读更多
了解如何通过支持 OSGi 命令 install、ss、start、stop、headers、active、update 和 uninstall 弥补 IBM® Rational® Functional Tester 和基于 Eclipse 的产品的控制台之间的不足。本解决方案提供了一种有效的方法,用于当 Eclipse-AutoStart 头部(header)的清单文件(manifest)被升级到 Eclipse-LazyStart 时提供自动测试用例支持。本文展示了一些测试场景,以验证这种包管理机制是可行的。
当我们的测试团队升级到 Eclipse V3.2 后,我们很快发现在我们的测试用例中 Eclipse 不再支持 AutoStart 头。Eclipse Foundation 已经使用 LazyStart 代替了 AutoStart,采用了 OSGi R4.1 规范中的延迟激活(lazy-activation)策略。这个更改带来的不利之一是,我们发现很难触发 LazyStart 中自动化的目标包。遗留的自动化测试用例需要公开一个资源,以便由触发包进行加载。由于更新所有遗留测试用例将会十分笨拙并且要花费大量时间,我们决定采用其他方法。

一种可行的方案是 Eclipse 控制台,一个操纵包的生命周期管理的强大工具。但是,Rational Functional Tester 不能识别 Eclipse 控制台。我们的解决方案是设计并实现该 GUI Console。

自动化测试的不足

Eclipse 是一个用于开发应用程序的流行的集成开发环境,得益于 Rich Client Platform (RCP),Eclipse 成为越来越多的应用程序的运行时平台,包括 IBM Notes® Client、Sametime® 以及 Expeditor(如果您刚接触 Eclipse,需要有关 Eclipse 功能的背景知识,请查看 参考资料)。但是,在自动化测试用例实践中,测试人员在尝试利用自动化工具(比如 IBM Rational Functional Tester)为基于 Eclipse RCP 的产品开发自动化测试用例时,遇到了严重的问题(至于 Rational Functional Tester 的对象识别的详细信息,请下载 “Grabbing GUI objects with IBM Rational Functional Tester”)。

IBM Rational Functional Tester 无法识别 Eclipse 或基于 Eclipse 的产品的控制台。当自动化测试人员试图通过 IBM Rational Functional Tester 获取 Eclipse 的控制台对象时,它无法识别该控制台。结果,自动化测试人员无法继续完成后续的任务。图 1 是 IBM Rational Functional Tester 无法识别 Eclipse 的控制台的一个屏幕截图。Rational Functional Tester 识别控制台的预期操作应该是图 1 中围绕控制台的一个红色的矩形。当 Rational Functional Tester 识别出 Eclipse 中的其他小部件(比如任务窗口、菜单栏以及组合列表)时,我们会看到类似的东西。


图 1. Rational Functional Tester 无法识别 Eclipse 控制台



因此,Rational Functional Tester 无法识别 Eclipse 控制台将引起严重的后果,因为自动化工具需要该控制台来执行以下两项任务。

包操作
出于一些显而易见的原因,我们需要检查并跟踪基于 Eclipse RCP 的产品中的目标包的状态。但是没有控制台的帮助,自动化测试人员必须开发并部署新的手动测试。测试人员必须将旧的自动化测试转换为手动测试用例。测试场景必须安装(install)、卸载(uninstall)、启动(start)、停止(stop)并确定(target)RCP 应用程序中的包。这可以借助控制台的支持来完成。通常,使用场景会在控制台中调用这些命令,提供一种检索回传结果的机制。
收集诊断信息
当 Eclipse 遇到错误时,它应该向控制台输出异常和错误。这些信息对于诊断问题的根源是非常关键的。开发人员非常希望测试人员能够在每个软件问题报告中提供这样的信息,但是 Rational Functional Tester 在自动化测试中无法收集这些信息。这意味着测试人员需要手动重新运行测试来收集这些信息。对于长时间运行的紧急测试来说,这基本上是不可能的。
为弥补这个不足,我们提出了一个称之为 GUI Console 的解决方案。

需求识别

包生命周期管理:AutoStart vs. LazyStart

在 MANIFEST.MF header 中,AutoStart 和 LazyStart 可能被当作是同一包-清单文件 header 的同义词,不同之处是不推荐使用前者,而推荐使用后者。在修复 Eclipse V3.1.2 中的 537 个 bug 的过程中(这为我们带来了 Eclipse Europa),AutoStart 被放弃了。现在,全部都是关于 OSGi V3.2 遵从性和 LazyStart(请查看 参考资料 进一步了解包如何在 MANIFEST.MF 文件中包含有关其自身的描述信息)。

在 Eclipse V3.2 中,LazyStart header 定义包是否在其类之前自动启动或者某个资源是否被其他的包访问。通过将值设置为 true,我们可以在第一次加载类或资源时延迟激活包 — 换句话说,自动激活。

如前所述,即使将所有遗留的测试用例迁移到 LazyStart 在逻辑上是可行的,但是这么做非常笨拙,将花费巨大的努力。更新所有测试用例以支持 AutoStart,这将向它们公开一个资源,并通过一个触发包加载它们。另外,启动包没有覆盖我们的自动化测试用例的所有要求。包状态管理 — 或者说 “包生命周期操作”— 涉及测试用例中的验证点。

包生命周期管理:包状态

包(包括我们的自动化测试用例中的包)在同一时间只能处于以下六种状态之一。

INSTALLED
当包成功安装后它可以进入 INSTALLED 状态。
RESOLVED
当包所需的 Java 类可用时,它可以进入 RESOLVED 状态。换句话说,这种状态表示框架已经成功按清单文件中的描述解析了包的依赖性。RESOLVED 状态来自于 INSTALLED 或 ACTIVE 状态,可进入 ACTIVE。
STARTING
当包正被启动时可以进入 STARTING 状态(当 BundleActivator.start() 方法被调用,但是还没有返回时)。
ACTIVE
当包已经启动且正在运行中,此时可以进入 ACTIVE 状态。
STOPPING
当包正被停止时可进入 STOPPING 状态(当 BundleActivator.stop() 方法被调用,但是还未返回时)。
UNINSTALLED
当包已被卸载后可以进入 UNINSTALLED 状态。它不能进入其他状态。
Bundle 接口定义了一个 getState() 方法,用于返回包的状态。

图 2 图示了包在其生命周期中的所有状态,以及它的转换路径。


图 2. OSGi 包状态



检索清单文件 header

MANIFEST.MF 的 header 是该 GUI Console 基础的另一部分。Eclipse 希望包开发者在名为 MANIFEST.MF 的清单文件中提供有关包的说明信息。这是定义清单 header 的 OSGi 文件,比如 Export-Package 和 Bundle-Classpath 说明。表 1 列出了 OSGi 包中最有用的 header。


表 1. 某个 OSGi 包的 header
OSGi 包 header 说明
Bundle-Activator 指定用于启动和停止包的类的名称。
Bundle-Classpath 指定包含类和资源的 JAR 文件或目录。句号(‘.’)、默认值指定了包的 JAR 的根目录。
Bundle-ContactAddress 包含供应商的联系地址。
Bundle-Copyright 包含此包的版权说明。
Bundle-DocURL 指定了一个指向有关此包的文档的 URL。
Bundle-Localization 指定了包的本地化文件的位置,其默认值为 OSGI-INF/l10n/bundle。
Bundle-ManifestVersion 指定该包遵从 OSGi 规范 V3 或者 OSGi 规范 V4 中的规则。
Bundle-Name 指定该包的可读名称(无空格)。
Bundle-SymbolicName 一个强制的 header,用于为此包指定一个惟一的名称。
Bundle-Vendor 包含一个包供应商的可读名称。
Bundle-Version 指定包的版本,默认为 0.0.0。
Export-Package 指定此包的导出包(exported package)。
Fragment-Host 定义此片段的本地包(host bundle)。
Import-Package 声明此包的导入包(imported package)。
Require-Bundle 指定从其他包所需的导出。
Import-Service 不建议使用
Export-Service 不建议使用


需求总结

根据以上的需求分析,该 GUI Console 解决方案需要提供以下命令,以支持我们的自动化测试用例的 OSGi 包管理。


表 2. GUI Console 支持的 OSGi 命令
命令 说明
install+bundle URL 将一个具有指定 URL 的包添加到当前平台。
uninstall+bundle ID 从当前平台删除一个指定的包。
ss 列出在当前平台注册的所有包的简单状态。
start+bundle ID;
start+bundle name 启动具有给定包 ID 或符号名称的包。
stop+bundle ID;
stop+bundle name 终止具有给定包 ID 或符号名称的包。
headers 列出具有给定 ID 或符号名称的包的清单 header。
active 列出当前平台中所有活动的包。
update 更新一个当前实例的包。






设计和实现

在本节中,我们将展示为什么在我们的 GUI Console 解决方案中 BundleContext 对象和包对象非常关键,并且我们将讨论 OSGi 中的包管理。

BundleContext

BundleContext 将 Eclipse 框架和框架中安装的包连接了起来。BundleContext 对象代表 OSGi 平台内的包的执行上下文,并且作为底层框架的代理运行。

当包启动后,该框架将会创建一个 BundleContext 对象,作为参数提供给该包的 Bundle-Activator 的 start(BundleContext) 方法。该包可以使用这个私有的 private BundleContext 对象执行以下操作:

将新包安装到 OSGi 环境。
询问该 OSGi 环境中其他已安装的 bundle。
获得持久性存储区域。
检索已注册服务的服务对象。
在框架服务中注册服务。
订阅或取消订阅由框架广播的事件。
每个包都有自己的 BundleContext 对象,并且它们不应该在包之间传递。为什么?因为 BundleContext 对象与包的安全性和资源分配有关。当 Bundle-Activator 的 stop(BundleContext) 方法被返回时,BundleContext 对象就会停止服务。

BundleContext 最有用的一点就是,它定义了一些方法,用于检索有关安装在 OSGi 服务平台中的包的信息:

getBundle()
返回与 BundleContext 对象相关的单个包对象。
getBundles()
返回当前安装在框架中的一组包。
getBundle(long)
返回由惟一标识符指定的包对象,如果没有发现匹配的包,则返回空。
因为对包的访问没有限制,所以任何包都可以枚举已安装包的集合。这允许我们处理和控制自动化测试用例的包生命周期管理操作以及包信息检索操作。GUI Console 将使用清单 1 中的代码来检索所有活动包的信息。它的功能和 Eclipse 控制台的 active 命令是一样的。


清单 1. Active 命令实现代码
                
public static void doActive() throws Exception {
    b = bContext.getBundles();
    for (int i = 0; i > b.length; i++) {
        if (b[i].getState() == b[i].ACTIVE) {
	result = result + b[i].getLocation()+" " + "["+b[i].getBundleId() +"]"
                 +System.getProperty ("line.separator");
		    } 
		}
		result=result+p+" active bundle(s).";
	}
 



至于曾经很流行的 ss 命令,只有当我们接受所有具有相应状态的包(包括 INSTALLED、RESOLVED 和 ACTIVE)时,GUI Console 才可以根据以上代码实现它。有关详细信息,请参阅 样例代码。

包对象

为了在具有基于 Eclipse 的产品的 OSGi 平台中管理包的生命周期,我们必须对目标包使用相关的包对象。BundleContext 接口提供了以下安装包的方法。

installBundle(String)
从指定的位置字符串(一个 URL)安装包。
installBundle(String,InputStream)
从指定的 InputStream 对象安装包。
当包成功安装后,将为其生成一个包对象,所有的生命周期管理操作必须使用此对象执行,比如启动、停止和卸载。

如我们在清单 2 中所看到的,我们可以使用提供的位置字符串安装包。如果该包在平台中成功安装,它将返回该包的符号名称。


清单 2. Active 命令实现
               
public static String doInstall(String location) throws Exception{
                ...
		Bundle iBundle = bContext.installBundle(location);
		if(iBundle!=null){
			iBundle.update();
			return iBundle.getSymbolicName();
		}
		return null;
	}
 


启动包

包接口定义 start() 方法,用于启动包并将包从 RESOLVED 状态转换到 ACTIVE 状态。前提条件是,该包必须已经被成功解析;否则会抛出一个 BundleException。

要执行一个包的 start() 方法,我们需要通过该包的清单文件中的 Bundle-Activator header 通知 OSGi 环境 Bundle-Activator 的类名。OSGi 环境将会实例化该类的一个新对象,并将其传递到一个 Bundle-Activator 实例。然后它将会调用 BundleActivator.start() 方法来启动该包。

作为一个 Bundle-Activator,该包中的类必须实现此 Bundle-Activator 接口,将其声明为公共的和公共的默认构造函数。但是,在每个包中提供一个 Bundle-Activator 是可选的行为。例如,导出少量包(package)的 library 包并不需要定义 Bundle-Activator。

当提供了包 ID 或符号名称时,我们的 GUI Console 应用程序调用以下代码来启动包。


清单 3. Start 命令实现
               
public static void doStart(int bID) throws Exception {
    if(bContext.getBundle(bID)!=null&&bContext.getBundle(bID).state==
    bContext.getBundle(bID).RESOLVED){
	bContext.getBundle(bID).start();
    }
}
public static void doStart(String s,String matchS) throws Exception {
    b = bContext.getBundles();
    ...
    for (int i = 0; i < b.length; i++) {
	if (b[i].getSymbolicName().indexOf(s) >-1 && (b[i].getState() == 
        b[i].RESOLVED) {
	    boolean isFragment=false;
	    Enumeration eKey = b[i].getHeaders().keys();
	    Dictionary dValue = b[i].getHeaders();
	    Enumeration eValue = dValue.elements();
	    while (eKey.hasMoreElements() && eValue.hasMoreElements()) {
		String sKey = eKey.nextElement().toString();
		if (sKey.equalsIgnoreCase("Fragment-Host")) {
		    isFragment=true;
		 }
	    }
	   if (isFragment==false){
		b[i].start();
	   }
     }				    
}
 


停止包

包接口定义 stop() 方法来停止包,并导致转换到 RESOLVED 状态。所有与停止包相关的线程应该立即停止。

卸载包

包接口提供 uninstall() 方法来从框架中卸载包。该框架将通知其他包该目标包正在被卸载,删除所有与该包相关的资源,并将该目标包的状态设置为 UNINSTALLED。

新安装的包不能使用已卸载包的 package。但是,如果已卸载的包曾经导出过其他包使用的 package,那么框架将会继续使用这些 package,直到框架被重启或者 org.osgi.service.packageadmin.PackageAdmin.refreshPackages() 方法被调用。

以下代码展示了 GUI Console 如何使用提供的包 ID 或符号名称从平台卸载包。


清单 4. Uninstall 命令实现
  
             
public static void doUninstall(String s, String matchS) throws Exception{
    b = bContext.getBundles();
    ...
    if (b[i].getSymbolicName().indexOf(s) >-1) {
	....
	b[i].uninstall();
    }
    ...
}
public static void doUninstall(int bID) throws Exception{	
    if (bContext.getBundle(bID)!=null){
	bContext.getBundle(bID).uninstall();
    }
}



检索清单 header

包接口提供了两种方法来返回清单 header 信息:

getHeaders()
返回一个包含该包的清单 header 和值(键-值对)的 dictionary 对象。
getHeaders(String)
返回一个包含该包的清单 header 和值(键-值对)的 dictionary 对象。
即使当包进入 UNINSTALLED 状态时,getHeaders 方法可以继续提供清单 header 信息。


清单 5. Headers 命令实现
    
           
Enumeration eKey = b[i].getHeaders().keys();
Dictionary dValue = b[i].getHeaders();
Enumeration eValue = dValue.elements();
while (eKey.hasMoreElements() && eValue.hasMoreElements()) {
    String sKey = eKey.nextElement().toString();
    String sValue = eValue.nextElement().toString();
    headers.append(sKey+" = ");
    headers.append(sValue+"\n");
}





执行

我们可以通过使用 IBM Expeditor(一个基于 Eclipse 的产品)执行 GUI Console 测试场景(请参阅 参考资料)。它要求我们在验证场景之前将 GUI Console 安装到 Expeditor 或任何其他基于 Eclipse 的产品中。然后,如图 3 所示,我们使用一个叫做 PascalTriangle 的示例包作为执行期间的操作包。通过在文件系统中导出它的 JAR 文件,我们将在 GUI Console 上执行包管理操作,包括安装、启动、停止、卸载以及其他操作。

GUI Console 的一个优势是,包的符号名和包 ID 将自动在命令解释和执行期间获得支持。当安装好格式化为导出 JAR 文件的包时,符号名和包 ID 将通过编程的方式检索。另外,为了为自动化测试执行提供尽可能多的灵活性,您可以使用简单的 regex 技术搜索符号名称,包括 start with、contains 和 perfect match。


图 3. 操作包:为进行测试而导出的 Pascal 包 JAR 文件



为了展示支持 OSGi 的 install 命令的功能,我们将把这个操作包导入到 Expeditor 或者任何其他基于 Eclipse 的产品,它们已经配备了 GUI Console 应用程序。在 GUI Console 应用程序中,按 Browse,导航到 PascalTriangle bundle JAR 文件在文件系统中所在的位置,然后按下 OK。在文本字段中检验了目标包的位置地址后,按下 Install。如果 GUI Console 和 OSGi 平台可以成功加载和解析您的 PascalTriangle 包,您将会看到指定了一个包 ID。


图 4. Install 命令场景



使用 ss 命令(按下 ss 按钮),检查包的状态是否被解析,如下所示。


图 5. ss 命令场景



要启动包,在安装好该包并且文本字段自动检索到它的符号名称后按下 Start。启动 PascalTriangle 包的执行结果可以按如下所示进行检验。


图 6. Sstart 命令场景



要查看一个包的 MANIFEST 文件中 header 的详细信息,请按 headers 按钮。随后,您可以查看您的目标包中所有可用的 header 和它们的值。图 7 展示了对 PascalTriangle 包执行 headers 命令的结果。


图 7. Headers 命令场景



有时候您需要列出平台上所有 ACTIVE 状态的包。要进行概览,按下 Active。详细信息请参阅图 8。


图 8. Active 命令场景



如下所示,在显示区域的底部列出了 GUI Console 包和 PascalTriangle 包,包括它们的包 ID。


图 9. Active 命令场景



图 10 也展示了使用 Update 命令把目标包的 JAR 文件替换为一个更新的文件。我们可以发现更新的 PascalTriangle 在更新之后被输出。


图 10. Update 命令场景



卸载包的操作如下所示。结果由 ss 命令进行了验证,按照符号名称搜索 PascalTriangle 包。正如我们可以看到的,在卸载后 OSGi 平台中就没有 PascalTriangle 包存在了。


图 11. Uninstall 命令场景







结束语

IBM Rational Functional Tester 无法识别 Eclipse 控制台。从 V3.2 开始,Eclipse 不再支持遗留测试用例中的 Eclipse-AutoStart header。为了弥补这个不足,我们展示了一个称为 GUI Console 的解决方案,它可以使用新的 Eclipse-LazyStart。


分享到:
评论

相关推荐

    eclipse 入门级资料打包

    2. Maven集成:了解如何在Eclipse中配置Maven项目,管理依赖,并使用Maven命令行工具。 3. Spring工具集:如果计划进行企业级开发,Spring Tool Suite (STS) 插件将大有裨益,它提供了便捷的Spring项目创建和配置...

    eclipse资源包

    通过分析这些文件名,我们可以了解到这个`src.zip`资源包涵盖了Eclipse的多个核心组件和Java语言的关键部分。开发者可以通过研究这些源码来学习Eclipse的工作机制,定制自己的开发环境,或者为Eclipse创建新的插件和...

    GNUARMEclipse包含一系列的Eclipse插件和工具

    首先,我们要了解 GNU ARM Eclipse 的核心组件——Eclipse 插件。Eclipse 是一个高度可扩展的 IDE,通过插件机制,可以扩展其功能以适应各种编程语言和开发需求。在 GNU ARM Eclipse 中,这些插件主要包括: 1. **C...

    eclipse 图书管理系统

    《Eclipse图书管理系统详解》 Eclipse图书管理系统是一款基于Java编程语言,利用Eclipse集成开发环境构建的应用程序,主要用于图书馆日常运营中的书籍管理、借阅、归还等操作。本系统充分利用了Java的面向对象特性...

    Java 超市管理系统 Eclipse

    【Java超市管理系统Eclipse】是一个基于Java编程语言和Eclipse集成开发环境的项目,用于实现一个全面的超市管理功能。这个系统涵盖了商品管理、库存控制、销售记录、客户管理等多个核心模块,旨在提高超市运营效率,...

    eclipse theme

    为了更好地利用这个主题,我们需要了解如何在Eclipse中管理和应用主题。以下是步骤: 1. 打开Eclipse,进入“窗口”菜单,然后选择“首选项”。 2. 在首选项对话框中,展开“General”选项,点击“Appearance”。 3...

    eclipse 第三方jar包配置.txt

    在深入讨论如何配置第三方JAR包之前,我们需要先了解Eclipse项目的几个关键概念: 1. **项目路径**:指Eclipse项目所在的物理位置。 2. **源代码路径**:项目中的源代码目录,例如`src`。 3. **编译输出路径**:...

    eclipse数据库图书馆管理系统.zip

    《Eclipse数据库图书馆管理系统》 本系统是一款基于Java编程语言,使用Eclipse开发环境,并结合MySQL数据库构建的图书管理...通过实际操作,学生不仅可以提升编程技能,还能深入了解数据库管理和软件工程的实践经验。

    Eclipse4-RCP 开发教程

    首先,Eclipse 4引入了全新的组件模型(E4 Model),它取代了E3时代的Plug-in System,简化了开发流程。E4 Model包括了应用程序模型(Application Model)、UI模型(UI Model)和生命周期管理(Life Cycle ...

    删除eclipse3.4中的P2更新管理器

    首先,P2更新管理器是Eclipse 3.4(Ganymede)版本中引入的一个关键特性,它提供了更加灵活的插件管理和更新机制。然而,对于某些用户来说,特别是那些不需要频繁更新或对系统精简有需求的用户,这个功能可能是多余...

    eclipse-zh_4.7.0 OXYGEN 中文汉化包

    Eclipse 是一个著名的开源集成开发环境(IDE),广泛用于Java编程,同时也支持其他多种语言如...通过了解Eclipse的基本特性和Oxygen版本的改进,以及如何正确安装和使用汉化包,开发者可以更高效地进行软件开发工作。

    eclipse api 帮助文档

    Eclipse API包括了众多包、类和接口,如`org.eclipse.core.runtime`、`org.eclipse.ui`、`org.eclipse.jdt.core`等,它们覆盖了项目管理、工作空间操作、编辑器、视图、透视图、运行时环境、调试等多个方面。...

    Eclipse插件开发学习笔记.pdf

    Eclipse插件开发学习笔记将带领我们深入了解Eclipse插件开发的方方面面。 首先,我们需要了解Eclipse插件的基础概念。在Eclipse中,插件主要由一系列的扩展点(Extension Points)组成,这些扩展点定义了插件可以...

    Eclipse从入门教程.docx

    本教程将引导初学者了解Eclipse的基础使用,以及如何进行SWT和JFace的编程,同时深入探讨Eclipse插件的开发。 1. **Eclipse插件开发** Eclipse的插件机制是其独特之处,允许开发者构建功能丰富且可定制化的应用...

    基于eclipse+SQL server 2008的蔬菜管理系统

    "基于eclipse+SQL server 2008的蔬菜管理系统" 这个标题揭示了项目的关键组成部分。Eclipse是一个广泛使用的开源集成开发环境(IDE),尤其适用于Java编程,而SQL Server 2008是微软提供的一款关系型数据库管理系统...

    JAVAweb客户管理系统源码eclipse版.rar

    JavaWeb客户管理系统是一款基于Eclipse开发的Web应用,主要利用了Spring、Struts2和Hibernate(SSH)三大框架,构建了MVC模式的项目结构。这个项目是为新手学习和二次开发设计的,使用JDK1.7版本,确保了在较旧的...

    Eclipse3.6 2(6-2)

    这个源代码包允许开发者深入了解P2的工作机制,便于自定义和扩展Eclipse的插件管理。 2. `org.eclipse.core.commands_3.6.0.I20110111-0800.jar`:这是Eclipse核心命令框架的实现,提供了一种模型来定义、注册、...

    NLpack-eclipse-SDK-3.0.x-win32

    NLpack-eclipse-SDK-3.0.x-win32是一个专为Windows平台设计的Eclipse多国语言包,主要用于扩展Eclipse开发环境的语言支持。...同时,掌握Eclipse的插件管理机制,也能让你更灵活地定制自己的开发环境,提升开发效率。

    Eclipse SVN插件 包含 site-1.8.22、site-1.10.9

    Eclipse SVN插件是开发人员在Eclipse集成开发环境中与Subversion版本控制系统交互的重要工具。Subversion(SVN)是一种广泛使用的开源版本控制系统,...了解并掌握其使用方法,有助于开发者更好地管理和维护项目代码。

    eclipse序列号生成器

    总的来说,了解Eclipse的基本操作和特性,遵循开源社区的规则,以及抵制和避免使用非法工具,是成为一个负责任和成功的IT专业人士的基础。在开发过程中,应始终重视合法授权和软件安全,这样才能确保个人和团队的...

Global site tag (gtag.js) - Google Analytics