在Java應(yīng)用程序中訪問USB設(shè)備詳解

2010-08-28 10:48:44來源:西部e網(wǎng)作者:

Java 平臺一直都以其平臺無關(guān)性自豪。雖然這種無關(guān)性有許多好處,但是它也使得編寫與硬件交互的 Java 應(yīng)用程序的過程變得相當(dāng)復(fù)雜。在本文中,研究科學(xué)家蔣清野討論了兩個項目,它們通過提供使Java 應(yīng)用程序可以使用 USB 設(shè)備的 API 而使這個過程變得更容易。雖然這兩個項目仍然處于萌芽狀態(tài),但是它們都顯示了良好的前景,并已經(jīng)成為一些實用應(yīng)用程序的基礎(chǔ)。

通用串行總線(Universal Serial Bus USB)規(guī)范的第一個版本發(fā)表于 1996年 1月。因為它的低成本、高數(shù)據(jù)傳輸率、使用容易和靈活性,USB 在計算機行業(yè)里獲得了廣泛接受。今天,許多周邊設(shè)備和裝置都是通過 USB 接口連接到計算機上的。目前,大多數(shù)一般用途的操作系統(tǒng)都提供了對 USB 設(shè)備的支持,并且用 C 或者 C++ 可以相對容易地開發(fā)訪問這些外設(shè)的應(yīng)用程序。不過,Java 編程語言在設(shè)計上對硬件訪問提供的支持很少,所以編寫與 USB 設(shè)備交互的應(yīng)用程序是相當(dāng)困難的。

IBM 的 Dan Streetman 最早開始了在 Java 語言中提供對 USB 設(shè)備的訪問的努力。2001年,他的項目通過 Java 規(guī)范請求(Java Specification Request,JSR)過程被接受為 Java 語言的候選擴(kuò)展標(biāo)準(zhǔn)。這個項目現(xiàn)在稱為 JSR-80 并且指定了官方包 javax.usb。同時,在 2000年 6月,Mojo Jojo 和 David Brownell 在 SourceForge 開始了 jUSB 項目。這兩個項目都開發(fā)出了 Linux 開發(fā)人員可以使用的包,盡管它們都還很不完善。這兩個項目也都開始試圖向其他操作系統(tǒng)上的 Java 應(yīng)用程序提供對 USB 設(shè)備的訪問,盡管它們都還沒有開發(fā)出可以使用的包(參閱 參考資料 中有關(guān)本文中討論的這兩個項目及其他項目的資料)。

在本文中,將對 jUSB 和 JSR-80 項目作一個簡要介紹,不過,我們首先要看一下 USB 協(xié)議的具體細(xì)節(jié),這樣您就可以理解這兩個項目是如何與 USB 設(shè)備交互的。我們還將提供代碼片段以展示如何用這兩個項目的 API 訪問 USB 設(shè)備。

USB 介紹

1994年,一個由四個行業(yè)伙伴(Compaq、Intel、Microsoft 和 NEC)組成的聯(lián)盟開始制定 USB 協(xié)議。該協(xié)議最初的目的是將 PC 與電話相連并提供容易擴(kuò)展和重新配置的 I/O 接口。1996年 1月,發(fā)表了 USB 規(guī)范的第一個版本,1998年 9月發(fā)表了后續(xù)版本(版本 1.1)。這個規(guī)范允許 127臺設(shè)備同時連接到一起,總的通信帶寬限制為 12 Mbps。后來,又有三個成員(Hewlett-Packard、Lucent 和 Philips)加入了這個聯(lián)盟。2000年 4月,發(fā)表了 USB 規(guī)范的 2.0版本,它支持高達(dá) 480 Mbps 的傳輸率。今天,USB 在高速(視頻、圖像、儲存)和全速(音頻、寬帶、麥克風(fēng))數(shù)據(jù)傳輸應(yīng)用中起了關(guān)鍵作用。它還使各種低速設(shè)備(鍵盤、鼠標(biāo)、游戲外設(shè)、虛擬現(xiàn)實外設(shè))連接到 PC 上。

USB 協(xié)議有嚴(yán)格的層次結(jié)構(gòu)。在所有 USB 系統(tǒng)中,只有一個主設(shè)備,到主計算機的的 USB 接口稱為主控器(host controller)。主控器有兩個標(biāo)準(zhǔn)??開放主控器接口(Compaq 的 Open Host Controller Interface,OHCI)和通用主控器接口(Intel 的 Universal Host Controller Interface,UHCI)。這兩個標(biāo)準(zhǔn)提供了同樣的能力,并可用于所有的 USB 設(shè)備,UHCI 的硬件實現(xiàn)更簡單一些,但是需要更復(fù)雜的設(shè)備驅(qū)動程序(因而 CPU 的負(fù)荷更大一些)。

USB 物理互連是分層的星形拓樸,最多有七層。一個 hub 是每個星形的中心,USB 主機被認(rèn)為是 root hub。每一段連線都是 hub 與 USB 設(shè)備的點對點連接,后者可以是為系統(tǒng)提供更多附加點的另一個 hub,也可以是一個提供功能的某種設(shè)備。主機使用主/從協(xié)議與 USB 設(shè)備通信。這種方式解決了包沖突的問題,但是同時也阻止了附加的設(shè)備彼此建立直接通信。

所有傳輸?shù)臄?shù)據(jù)都是由主控器發(fā)起的。數(shù)據(jù)從主機流向設(shè)備稱為下行(downstream)或者輸出(out)傳輸,數(shù)據(jù)從設(shè)備流向主機稱為上 行(upstream)或者輸入(in)傳輸。數(shù)據(jù)傳輸發(fā)生在主機和 USB 設(shè)備上特定的端點(endpoint) 之間,主機與端點之間的數(shù)據(jù)鏈接稱為管道(pipe)。 一個給定的 USB 設(shè)備可以有許多個端點,主機與設(shè)備之間數(shù)據(jù)管道的數(shù)量與該設(shè)備上端點的數(shù)量相同。一個管道可以是單向或者是雙向的,一個管道中的數(shù)據(jù)流與所有其他管道中的數(shù)據(jù)流無關(guān)。

USB 網(wǎng)絡(luò)中的通信可以使用下面四種數(shù)據(jù)傳輸類型中的任意一種:

控制傳輸: 這些是一些短的數(shù)據(jù)包,用于設(shè)備控制和配置,特別是在設(shè)備附加到主機上時。

批量傳輸: 這些是數(shù)量相對大的數(shù)據(jù)包。像掃描儀或者 SCSI 適配器這樣的設(shè)備使用這種傳輸類型。

中斷傳輸: 這些是定期輪詢的數(shù)據(jù)包。主控器會以特定的間隔自動發(fā)出一個中斷。

等時傳輸: 這些是實時的數(shù)據(jù)流,它們對帶寬的要求高于可靠性要求。音頻和視頻設(shè)備一般使用這種傳輸類型。

像串行端口一樣,計算機上每一個 USB 端口都由 USB 控制器指定了一個惟一的標(biāo)識數(shù)字(端口 ID)。當(dāng) USB 設(shè)備附加到 USB 端口上時,就將這個 惟一端口 ID 分配給這臺設(shè)備,并且 USB 控制器會讀取設(shè)備描述符。設(shè)備描述符包括適用于該設(shè)備的全局信息、以及設(shè)備的配置信息。配置定義了一臺 USB 設(shè)備的功能和 I/O 行為。一臺 USB 設(shè)備可以有一個或者多個配置,這由它們相應(yīng)的配置描述符所描述。每一個配置都有一個或者多個接口,它可以視為一個物理通信渠道 ;每一個接口有零個或者多個端點,它可以是數(shù)據(jù)提供者或者數(shù)據(jù)消費者,或者同時具有這兩種身份。接口由接口描述符描述,端點由端點描述符描述。并且一臺 USB 設(shè)備可能還有字符串描述符以提供像廠商名、設(shè)備名或者序列號這樣的附加信息。

正如您所看到的,像 USB 這樣的協(xié)議為使用 Java 這種強調(diào)平臺和硬件無關(guān)性的語言的開發(fā)人員提出了挑戰(zhàn),F(xiàn)在讓我們看兩個試圖解決這個問題的項目。

jUSB API

jUSB 項目是由 Mojo Jojo 和 David Brownell 于 2000年 6月創(chuàng)立的。其目標(biāo)是提供一組免費的、在 Linux 平臺上訪問 USB 設(shè)備的 Java API。這個 API 是按照 Lesser GPL (LGPL)條款發(fā)表的,這意味著您可以在專有和免費軟件項目中使用它。這個 API 提供了對多個物理 USB 設(shè)備的多線程訪問,并支持本機和遠(yuǎn)程設(shè)備。具有多個接口的設(shè)備可以同時被多個應(yīng)用程序(或者設(shè)備驅(qū)動程序)所訪問,其中每一個應(yīng)用程序(或者設(shè)備驅(qū)動程序)都占據(jù)一個不同的接口。該 API 支持控制傳輸、批量傳輸和中斷傳輸,不支持等時傳輸,因為等時傳輸用于媒體數(shù)據(jù)(如音頻和視頻),JMF API 已經(jīng)在其他標(biāo)準(zhǔn)設(shè)備驅(qū)動程序上對此提供了很好的支持(參閱 參考資料)。當(dāng)前,該 API 可以在具有 Linux 2.4 核心或者以前的 2.2.18 核心的 GNU/Linux 版本上工作。因此可支持大多數(shù)最新的版本,例如,該 API 可以在沒有任何補丁或者升級的 Red Hat 7.2 和 9.0 上工作。

jUSB API 包括以下包:

1.usb.core: 這個包是 jUSB API 的核心部分。它使得 Java 應(yīng)用程序可以從 USB 主機訪問 USB 設(shè)備。

2.usb.linux: 這個包包含 usb.core.Host 對象的 Linux 實現(xiàn)、bootstrapping 支持和其他可以提升 Linux USB 支持的類。這個實現(xiàn)通過虛擬 USB 文件系統(tǒng)(usbdevfs)訪問 USB 設(shè)備。

3.usb.windows: 這個包包含 usb.core.Host 對象的 Windows 實現(xiàn)、bootstrapping 支持和其他可以提升 Windows USB 支持的類。這個實現(xiàn)仍然處于非常初級的階段。

4.usb.remote: 這個包是 usb.core API 的遠(yuǎn)程版本。它包括一個 RMI proxy 和一個 daemon 應(yīng)用程序,它讓 Java 應(yīng)用程序可以訪問遠(yuǎn)程計算機上的 USB 設(shè)備。

5.usb.util: 這個包提供了一些有用的實用程序,可以將 firmware下載到 USB 設(shè)備上、將 USB 系統(tǒng)的內(nèi)容轉(zhuǎn)儲到 XML 中、以及將只有 bulk I/O 的 USB 設(shè)備工具轉(zhuǎn)換成一個套接字(socket)。

6.usb.devices: 這個可選包收集了用 jUSB API 訪問不同 USB 設(shè)備的 Java 代碼,包括柯達(dá)數(shù)碼相機和 Rio 500 MP3 播放器。這些 API 經(jīng)過特別編寫以簡化訪問特定 USB 設(shè)備的過程,并且不能用于訪問其他設(shè)備。這些 API 是在 usb.core API 之上構(gòu)建的,它們可以工作在所有支持 jUSB 的操作系統(tǒng)上。

7.usb.view: 這個可選包提供了基于 Swing 的 USB 樹簡單瀏覽器。它是一個展示 jUSB API 應(yīng)用的很好的示例程序。

盡管 usb.core.Host 對象的實現(xiàn)對于不同的操作系統(tǒng)是不同的,但是 Java 程序員只需要理解 usb.core 包就可以用 jUSB API 開始應(yīng)用程序的開發(fā)。表 1 列出了 usb.core 的接口和類,Java 程序員應(yīng)該熟悉它們:

表 1. jUSB 中的接口和類

接口/類 說明
Bus 將一組 USB 設(shè)備連接到 Host 上
Host 表示具有一個或者多個 Bus 的 USB 控制器
Configuration 提供對設(shè)備所支持的 USB 配置的訪問,以及對與該配置關(guān)聯(lián)的接口的訪問
Descriptor 具有 USB 類型的描述符的實體的基類
Device 提供對 USB 設(shè)備的訪問
DeviceDescriptor 提供對 USB 設(shè)備描述符的訪問
EndPoint 提供對 USB 端點描述符的訪問、在給定設(shè)備配置中構(gòu)造設(shè)備數(shù)據(jù)輸入或者輸出
HostFactory 包含 bootstrapping 方法
Hub 提供對 USB hub 描述符以及一些 hub 操作的訪問
Interface 描述一組端點,并與一個特定設(shè)備配置相關(guān)聯(lián)
PortIdentifier 為 USB 設(shè)備提供穩(wěn)定的字符串標(biāo)識符,以便在操作和故障診斷時使

用 jUSB API 訪問一臺 USB 設(shè)備的正常過程如下:

1.通過從 HostFactory 得到 USB Host 進(jìn)行 Bootstrap。

2.從 Host 訪問 USB Bus,然后從這個 Bus 訪問 USB root hub(即 USB Device)。

3.得到 hub 上可用的 USB 端口數(shù)量,遍歷所有端口以找到正確的 Device。

4.訪問附加到特定端口上的 USB Device?梢杂靡慌_ Device 的 PortIdentifier 直接從 Host 訪問它,也可以通過從 root hub 開始遍歷 USB Bus 找到它。

5.用 ControlMessage 與該 Device 直接交互,或者從該 Device 的當(dāng)前 Configuration 中要求一個 Interface,并與該 Interface 上可用的 Endpoint 進(jìn)行 I/O 。

清單 1 展示了如何用 jUSB API 獲得 USB 系統(tǒng)中的內(nèi)容。這個程序編寫為只是查看 root hub 上可用的 USB 設(shè)備,但是很容易將它改為遍歷整個 USB 樹。這里的邏輯對應(yīng)于上述步驟 1 到步驟 4。

清單 1. 用 jUSB API 獲得 USB 系統(tǒng)的內(nèi)容

import usb.core.*;

public class ListUSB

{

 public static void main(String[] args)

 {

try

{

 // Bootstrap by getting the USB Host from the HostFactory.

 Host host = HostFactory.getHost();

 // Obtain a list of the USB buses available on the Host.

 Bus[] bus = host.getBusses();

 int total_bus = bus.length;

 // Traverse through all the USB buses.

 for (int i=0; i
 {

// Access the root hub on the USB bus and obtain the

// number of USB ports available on the root hub.

Device root = bus[i].getRootHub();

int total_port = root.getNumPorts();

// Traverse through all the USB ports available on the

// root hub. It should be mentioned that the numbering

// starts from 1, not 0.

for (int j=1; j<=total_port; j++)

{

 // Obtain the Device connected to the port.

 Device device = root.getChild(j);

 if (device != null)

 {

// USB device available, do something here.

 }

}

 }

} catch (Exception e)

{

 System.out.println(e.getMessage());

}

 }

清單 2 展示了在應(yīng)用程序成功地找到了 Device 的條件下,如何與 Interface 和 EndPoint 進(jìn)行批量 I/O。 這個代碼段也可以修改為執(zhí)行控制或者中斷 I/O。它對應(yīng)于上述步驟 5。

清單 2. 用 jUSB API 執(zhí)行批量 I/O

if (device != null)

{

 // Obtain the current Configuration of the device and the number of

 // Interfaces available under the current Configuration.

 Configuration config = device.getConfiguration();

 int total_interface = config.getNumInterfaces();

 // Traverse through the Interfaces

 for (int k=0; k
 {

// Access the currently Interface and obtain the number of

// endpoints available on the Interface.

Interface itf = config.getInterface(k, 0);

int total_ep = itf.getNumEndpoints();

// Traverse through all the endpoints.

for (int l=0; l
{

 // Access the endpoint, and obtain its I/O type.

 Endpoint ep = itf.getEndpoint(l);

 String io_type = ep.getType();

 boolean input = ep.isInput();

 // If the endpoint is an input endpoint, obtain its

 // InputStream and read in data.

 if (input)

 {

InputStream in;

in = ep.getInputStream();

// Read in data here

in.close();

 }

 // If the Endpoint is and output Endpoint, obtain its

 // OutputStream and write out data.

 else

 {

OutputStream out;

out = ep.getOutputStream();

// Write out data here.

out.close();

 }

}

 }

}


jUSB 項目在 2000年 6月到 2001年 2月期間非;钴S。該 API 的最新的版本 0.4.4發(fā)表于 2001年 2月 14日。從那以后只提出了很少的改進(jìn),原因可能是 IBM 小組成功地成為了 Java 語言的候選擴(kuò)展標(biāo)準(zhǔn)。不過,基于 jUSB 已經(jīng)開發(fā)出一些第三方應(yīng)用程序,包括 JPhoto 項目(這是一個用 jUSB 連接到數(shù)碼照相機的應(yīng)用程序)和 jSyncManager 項目(這是一個用 jUSB 與使用 Palm 操作系統(tǒng)的 PDA 同步的應(yīng)用程序)。

JSR-80 API (javax.usb)

正如前面提到的,JSR-80 項目是由 IBM 的 Dan Streetman 于 1999年創(chuàng)立的。2001年,這個項目通過 Java 規(guī)范請求(JSR)過程被接受為 Java 語言的候選擴(kuò)展標(biāo)準(zhǔn)。這個項目現(xiàn)在稱為 JSR-80 并且被正式分派了 Java 包 javax.usb。這個項目使用 Common Public License 的許可證形式,并通過 Java Community Process 進(jìn)行開發(fā)。這個項目的目標(biāo)是為 Java 平臺開發(fā)一個 USB 接口,可以從任何 Java 應(yīng)用程序中完全訪問 USB 系統(tǒng)。JSR-80 API 支持 USB 規(guī)范所定義的全部四種傳輸類型。目前,該 API 的 Linux 實現(xiàn)可以在支持 2.4 核心的大多數(shù)最新 GNU/Linux 版本上工作,如 Red Hat 7.2 和 9.0。

JSR-80 項目包括三個包:javax-usb (javax.usb API)、javax-usb-ri (操作系統(tǒng)無關(guān)的基準(zhǔn)實現(xiàn)的公共部分)以及 javax-usb-ri-linux (Linux 平臺的基準(zhǔn)實現(xiàn),它將公共基準(zhǔn)實現(xiàn)鏈接到 Linux USB 堆棧)。所有這三個部分都是構(gòu)成 Linux 平臺上 java.usb API 完整功能所必需的。在該項目的電子郵件列表中可以看到有人正在致力于將這個 API 移植到其他操作系統(tǒng)上(主要是 Microsoft Windows),但是還沒有可以工作的版本發(fā)表。

盡管 JSR-80 API 的操作系統(tǒng)無關(guān)的實現(xiàn)在不同的操作系統(tǒng)上是不同的,但是 Java 程序員只需要理解 javax.usb 包就可以開始開發(fā)應(yīng)用程序了。表 2 列出了 javax.usb 中的接口和類, Java 程序員應(yīng)該熟悉它們:

表 2. JSR-80 API 中的接口和類

接口/類 說明
UsbConfiguration 表示 USB 設(shè)備的配置
UsbConfigurationDescriptor USB 配置描述符的接口
UsbDevice USB 設(shè)備的接口
UsbDeviceDescriptor USB 設(shè)備描述符的接口
UsbEndpoint USB 端點的接口
UsbEndpointDescriptor USB 端點描述符的接口
UsbHub USB hub 的接口
UsbInterface USB 接口的接口
UsbInterfaceDescriptor USB 接口描述符的接口
UsbPipe USB 管道的接口
UsbPort USB 端口的接口
UsbServices javax.usb實現(xiàn)的接口
UsbHostManager javax.usb 的入口點



用 JSR-80 API 訪問 USB 設(shè)備的正常過程如下:

1.通過從 UsbHostManager 得到相應(yīng)的 UsbServices 進(jìn)行 Bootstrap。

2.通過 UsbServices 訪問 root hub。在應(yīng)用程序中 root hub 就是一個 UsbHub。

3.獲得連接到 root hub 的 UsbDevices 清單。遍歷所有低級 hub 以找到正確的 UsbDevice。

4.用控制消息(UsbControlIrp)與 UsbDevice 直接交互,或者從 UsbDevice 的相應(yīng) UsbConfiguration 中要求一個 UsbInterface 并與該 UsbInterface 上可用的 UsbEndpoint 進(jìn)行 I/O。

5.如果一個 UsbEndpoint 用于進(jìn)行 I/O,那么打開與它關(guān)聯(lián)的 UsbPipe。通過這個 UsbPipe 可以同步或者異步提交上行數(shù)

據(jù)(從 USB 設(shè)備到主計算機)和下行數(shù)據(jù)(從主計算機到 USB 設(shè)備)。

6.當(dāng)應(yīng)用程序不再需要訪問該 UsbDevice 時,關(guān)閉這個 UsbPipe 并釋放相應(yīng)的 UsbInterface。

在清單 3 中,我們用 JSR-80 API 獲得 USB 系統(tǒng)的內(nèi)容。這個程序遞歸地遍歷 USB 系統(tǒng)上的所有 USB hub 并找出連接到主機計算機上的所有 USB 設(shè)備。這段代碼對應(yīng)于上述步驟 1 到步驟 3。

清單 3. 用 JSR-80 API 獲得 USB 系統(tǒng)的內(nèi)容

import javax.usb.*;

import java.util.List;

public class TraverseUSB

{

 public static void main(String argv[])

 {

try

{

 // Access the system USB services, and access to the root

 // hub. Then traverse through the root hub.

 UsbServices services = UsbHostManager.getUsbServices();

 UsbHub rootHub = services.getRootUsbHub();

 traverse(rootHub);

} catch (Exception e) {}

 }

 public static void traverse(UsbDevice device)

 {

if (device.isUsbHub())

{

 // This is a USB Hub, traverse through the hub.

 List attachedDevices = ((UsbHub) device).getAttachedUsbDevices();

 for (int i=0; i
 {

traverse((UsbDevice) attachedDevices.get(i));

 }

}

else

{

 // This is a USB function, not a hub.

 // Do something.

}

 }

}



清單 4 展示了在應(yīng)用程序成功地找到 Device 后,如何與 Interface 和 EndPoint 進(jìn)行 I/O。這段代碼還可以修改為進(jìn)行所有四種數(shù)據(jù)傳輸類型的 I/O。它對應(yīng)于上述步驟 4 到步驟 6。

清單 4. 用 JSR-80 API 進(jìn)行 I/O

public static void testIO(UsbDevice device)

{

 try

 {

// Access to the active configuration of the USB device, obtain

// all the interfaces available in that configuration.

UsbConfiguration config = device.getActiveUsbConfiguration();

List totalInterfaces = config.getUsbInterfaces();

// Traverse through all the interfaces, and access the endpoints

// available to that interface for I/O.

for (int i=0; i
{

 UsbInterface interf = (UsbInterface) totalInterfaces.get(i);

 interf.claim();

 List totalEndpoints = interf.getUsbEndpoints();

 for (int j=0; j
 {

// Access the particular endpoint, determine the direction

// of its data flow, and type of data transfer, and open the

// data pipe for I/O.

UsbEndpoint ep = (UsbEndpoint) totalEndpoints.get(i);

int direction = ep.getDirection();

int type = ep.getType();

UsbPipe pipe = ep.getUsbPipe();

pipe.open();

// Perform I/O through the USB pipe here.

pipe.close();

 }

 interf.release();

}

 } catch (Exception e) {}

}

JSR-80 項目從一開始就非常活躍。2003年 2月發(fā)表了 javax.usb API、RI 和 RI 的 0.10.0 版本?雌饋磉@一版本會提交給 JSR-80 委員會做最終批準(zhǔn)。預(yù)計正式成為 Java 語言的擴(kuò)展標(biāo)準(zhǔn)后,其他操作系統(tǒng)上的實現(xiàn)會很快出現(xiàn)。Linux 開發(fā)者團(tuán)體看來對 JSR-80 項目的興趣比 jUSB 項目更大,使用 Linux 平臺的 javax.usb API 的項目數(shù)量在不斷地增加。

結(jié)束語

 

jUSB API 和 JSR-80 API 都為應(yīng)用程序提供了從運行 Linux 操作系統(tǒng)的計算機中訪問 USB 設(shè)備的能力。JSR-80 API 提供了比 jUSB API 更多的功能,很有可能成為 Java 語言的擴(kuò)展標(biāo)準(zhǔn)。目前,只有 Linux 開發(fā)人員可以利用 jUSB 和 JSR-80 API 的功能。不過,有人正在積極地將這兩種 API 移植到其他操作系統(tǒng)上。Java 開發(fā)人員應(yīng)該在不久就可以在其他操作系統(tǒng)上訪問 USB 設(shè)備。從現(xiàn)在起就熟悉這些 API,當(dāng)這些項目可以在多個平臺上發(fā)揮作用時,您就可以在自己的應(yīng)用程序中加入 USB 功能了。

關(guān)鍵詞:Java

贊助商鏈接: