The CommPortIdentifier class is the controller for the ports on a system. It has methods that list the available ports, figure out which program owns them, take control of a port, and open a port so you can perform I/O with it. The actual I/O, stream-based or otherwise, is performed through an instance of CommPort that represents the port in question. The purpose of CommPortIdentifier is to mediate between different programs, objects, or threads that want to use the same port.
Before you can use a port, you need an identifier for the port. Because the possible port identifiers are closely tied to the physical ports on the system, you cannot simply construct an arbitrary CommPortIdentifier object. Instead, you use one of several static methods in CommPortIdentifier that find the right port. These include:
public static Enumeration getPortIdentifiers( ) public static CommPortIdentifier getPortIdentifier(String portName) throws NoSuchPortException public static CommPortIdentifier getPortIdentifier(CommPort port) throws NoSuchPortException
The most general of these is CommPortIdentifier.getPortIdentifiers( ), which returns a java.util.Enumeration containing one CommPortIdentifier for each of the ports on the system. Example 22-1 uses this method to list all the ports on the system.
import javax.comm.*; import java.util.*; public class PortLister { public static void main(String[] args) { Enumeration e = CommPortIdentifier.getPortIdentifiers( ); while (e.hasMoreElements( )) { System.out.println((CommPortIdentifier) e.nextElement( )); } } } |
To compile this and the other programs in this chapter, you need the JAR file containing the API. In Suns implementation, this file is named comm.jar. For example,
$ javac -classpath .:jar/comm.jar PortLister.java
To run the program, youll need the native libraries and a javax.comm.properties file. The native libraries are found in the lib directory. To link to these on Linux or Solaris, you need to add the lib directory to the LD_LIBRARY_PATH environment variable like so:
$ export LD_LIBRARY_PATH= lib
If $LD_LIBRARY_PATH has already been defined, you need to redefine it like this instead:
$ export LD_LIBRARY_PATH= lib :$ LD_LIBRARY_PATH
This assumes the lib directory is found in the current working directory. Otherwise, specify the complete path to where it is found.
Finally, youll need to place the javax.comm.properties file in your classpath. Confusingly, Sun distributes this file in the docs directory. If your serial and parallel ports are not in the usual locations, or you have extra ports, customize this file to tell Java where they are.
Heres the output I got when I ran PortLister on my fairly stock Wintel PC:
D:JAVA22>java PortLister javax.comm.CommPortIdentifier@be3c9581 javax.comm.CommPortIdentifier@be209581 javax.comm.CommPortIdentifier@be489581 javax.comm.CommPortIdentifier@be4c9581
This shows you that my system has four ports, though it doesn tell you what those ports are. Of course, the output varies depending on how many serial and parallel ports the system possesses. I also ran PortLister on the same hardware but this time running Linux and heres the output:
$ java -classpath .:jar/comm.jar PortLister javax.comm.CommPortIdentifier@19f953d javax.comm.CommPortIdentifier@1fee6fc
Surprisingly, only two ports were found now. The problem is that the default javax.comm.properties file specifies the serial ports at /dev/ttyS0 and /dev/ttyS1 and the parallel ports at /dev/parport0 and /dev/parport1. However, the version of Debian Im running maps the parallel ports to /dev/par0 and /dev/par1. After I edited the javax.comm.properties file to recognize this, Java found all four ports:
$ java -classpath .:jar/comm.jar PortLister javax.comm.CommPortIdentifier@19f953d javax.comm.CommPortIdentifier@1fee6fc javax.comm.CommPortIdentifier@1eed786 javax.comm.CommPortIdentifier@187aeca
Clearly, a better toString( ) method is needed. (CommPortIdentifier merely inherits java.lang.Objects toString( ) method.) Youll see how to work around this in the next section.
You can also get a CommPortIdentifier by using the static method getPortIdentifier( ) to request a port identifier, either by name or by the actual port object. The latter assumes that you already have a reference to the relevant port, which usually isn the case. The former allows you to choose from Windows names like "COM1" and "LPT2" or Unix names like "Serial A" and "Serial B." The exact format of a name is highly platform- and implementation-dependent. If you ask for a port that doesn exist, a NoSuchPortException is thrown. Example 22-2 looks for serial and parallel ports by starting with COM1 and LPT1 and counting up until one is missing. Be warned that this code is highly platform-dependent and probably won work on Unix or the Mac.
import javax.comm.*; public class NamedPortLister { public static void main(String[] args) { // List serial (COM) ports. try { int portNumber = 1; while (true) { CommPortIdentifier.getPortIdentifier("COM" + portNumber); System.out.println("COM" + portNumber); portNumber++; } } catch (NoSuchPortException ex) { // Break out of loop. } // List parallel (LPT) ports. try { int portNumber = 1; while (true) { CommPortIdentifier.getPortIdentifier("LPT" + portNumber); System.out.println("LPT" + portNumber); portNumber++; } } catch (NoSuchPortException ex) { // Break out of loop. } } } |
Once again, heres the output from a stock Wintel box:
D:JAVA22>java NamedPortLister COM1 COM2 LPT1 LPT2
Now you can see that I have two serial and two parallel ports. However, this same program wouldn find any ports on a Unix box because Unix uses different port names.
Once you have a CommPortIdentifier, you can discover information about the port by calling several accessor methods. These include:
public String getName( ) public int getPortType( ) public String getCurrentOwner( ) public boolean isCurrentlyOwned( )
The getName( ) method returns the platform-dependent name of the port, such as "COM1" (Windows) or "Serial A" (Solaris). The getPortType( ) method returns one of the two mnemonic constants CommPortIdentifier.PORT_SERIAL or CommPortIdentifier.PORT_PARALLEL:
public static final int PORT_SERIAL = 1; public static final int PORT_PARALLEL = 2;
The isCurrentlyOwned( ) method returns true if some other Java process, thread, or application currently has control of the port. It returns false otherwise. If a port is owned by another Java program, the getCurrentOwner( ) returns the name supplied by the program that owns it; otherwise, it returns null. This isn too useful because it doesn handle the much more likely case that a non-Java program like Dial-Up Networking or PPP is using the port. Example 22-3 is a revision of the PortLister in Example 22-1 that uses these four accessor methods to provide information about each port rather than relying on the inherited toString( ) method.
import javax.comm.*; import java.util.*; public class PrettyPortLister { public static void main(String[] args) { Enumeration e = CommPortIdentifier.getPortIdentifiers( ); while (e.hasMoreElements( )) { CommPortIdentifier com = (CommPortIdentifier) e.nextElement( ); System.out.print(com.getName( )); switch(com.getPortType( )) { case CommPortIdentifier.PORT_SERIAL: System.out.print(", a serial port, "); break; case CommPortIdentifier.PORT_PARALLEL: System.out.print(", a parallel port, "); break; default: System.out.print(" , a port of unknown type, "); break; } if (com.isCurrentlyOwned( )) { System.out.println("is currently owned by " + com.getCurrentOwner( ) + "."); } else { System.out.println("is not currently owned."); } } } } |
Heres the output when run on a stock Wintel box:
D:JAVA22>java PrettyPrintLister COM1, a serial port, is not currently owned. COM2, a serial port, is not currently owned. LPT1, a parallel port, is not currently owned. LPT2, a parallel port, is not currently owned.
This output originally confused me because I expected one of the COM ports to be occupied by the Dial-Up Networking PPP connection on the internal modem (COM2). However, the isCurrentlyOwned( ) method only notices other Java programs in the same VM occupying ports. To detect whether a non-Java program is controlling a port, you must try to open the port and watch for PortInUseExceptions, as discussed in the next section.
Before you can read from or write to a port, you have to open it. Opening a port gives your application exclusive access to the port until you give it up or the program ends. (Two different programs should not send data to the same modem at the same time, after all.) Opening a port is not guaranteed to succeed. If another program (Java or otherwise) is using the port, a PortInUseException will be thrown when you try to open the port. Surprisingly, this is not a subclass of IOException.
public class PortInUseException extends Exception
CommPortIdentifier has two open( ) methods; they each return a CommPort object you can use to read data from and write data to the port. The first variant takes two arguments, a name and a timeout value:
public CommPort open(String name, int timeout) throws PortInUseException
The name argument is a name for the program that wants to use the port and is returned by getCurrentOwner( ) while the port is in use. The timeout argument is the maximum number of milliseconds this method blocks while waiting for the port to become available. If the operation does not complete within that time, a PortInUseException is thrown. Example 22-4 is a variation of the PortLister program that attempts to open each unowned port.
import javax.comm.*; import java.util.*; public class PortOpener { public static void main(String[] args) { Enumeration thePorts = CommPortIdentifier.getPortIdentifiers( ); while (thePorts.hasMoreElements( )) { CommPortIdentifier com = (CommPortIdentifier) thePorts.nextElement( ); System.out.print(com.getName( )); switch(com.getPortType( )) { case CommPortIdentifier.PORT_SERIAL: System.out.print(", a serial port, "); break; case CommPortIdentifier.PORT_PARALLEL: System.out.print(", a parallel port, "); break; default: System.out.print(" , a port of unknown type, "); break; } try { CommPort thePort = com.open("PortOpener", 10); System.out.println("is not currently owned."); thePort.close( ); } catch (PortInUseException ex) { String owner = com.getCurrentOwner( ); if (owner == null) owner = "unknown"; System.out.println("is currently owned by " + owner + "."); } } } } |
Heres the output:
D:JAVA22>java PortOpener COM1, a serial port, is not currently owned. COM2, a serial port, is currently owned by Port currently not owned. LPT1, a parallel port, is not currently owned. LPT2, a parallel port, is currently owned by Port currently not owned.
In this example, you see that COM2 is occupied, though by a non-Java program that did not register its name. You also see that LPT2 is occupied, which was something of a surprise to meI didn think I was using any parallel ports.
On Linux, the results were a little different:
elharo@cafe:~/comm$ java -classpath .:jar/comm.jar PortOpener /dev/ttyS0, a serial port, Exception in thread "main" java.lang.RuntimeException: Error opening "/dev/ttyS0" Permission denied at com.sun.comm.LinuxDriver.getCommPort(LinuxDriver.java:66) at javax.comm.CommPortIdentifier.open(CommPortIdentifier.java:368) at PortOpener.main(PortOpener.java:27)
Linux and Unix often require root access to open ports. Since I didn have it, they threw a rather nasty RuntimeException. This really should be an IOException or a SecurityException. After I sud to root, I was able to run the program as expected and open the ports:
# /opt/java/j2sdk1.4.2_04/bin/java -classpath .:jar/comm.jar PortOpener /dev/ttyS0, a serial port, is not currently owned. /dev/ttyS1, a serial port, is not currently owned. /dev/par0, a parallel port, is not currently owned. /dev/par1, a parallel port, Exception in thread "main" java.lang.RuntimeException: Error opening"/dev/par1" No such device or address at com.sun.comm.LinuxDriver.getCommPort(LinuxDriver.java:66) at javax.comm.CommPortIdentifier.open(CommPortIdentifier.java:368) at PortOpener.main(PortOpener.java:27)
That final exception came because this hardware only had one parallel port, even though there were entries for more in the /dev directory.
The second open( ) method takes a file descriptor as an argument:
public CommPort open(FileDescriptor fd) throws UnsupportedCommOperationException
This may be useful on operating systems like Unix, where all devices, serial ports included, are treated as files. On all other platforms, this method throws an UnsupportedCommOperationException:
public class UnsupportedCommOperationException extends Exception
There is no corresponding close( ) method in the CommPortIdentifier class. The necessary close( ) method is included in the CommPort class itself. You should close all ports youve opened when you e through with them.