Thursday, July 20, 2006

El modo más simple de lanzar un servidor web J2EE en tu equipo

Haz clic aquí, espera unos 20 segundos, y luego vete al puerto 9090 de tu equipo. Allí podrás encontrar el servidor web Jetty corriendo. Esto se consigue gracias a la magia de los Applet: el primer enlace es un Applet que contiene empotrado al servidor web.

Mark McLaren, tras el bullicio que se levantó cuando Sun decidió incluir a Derby en su implementación del JDK decidió hace un par de experimentos sobre empotrar bases de datos y servidores en aplicaciones Java. En su weblog nos explica cómo ha hecho sus experimentos y tiene dos demos, una la que incluye a jetty en un Applet, y otra en la que incluye un servidor web simple. Además de ser curioso, es ilustrativo.

Curiosamente, hace poco publicábamos una noticia sobre las posibilidades de Jetty para ser empotrado dentro de una aplicación Java. ¿Cuántos de vosotros habéis empotrado alguna base de datos/servidor/... en una aplicación Java? ¿qué os llevó tomar esa decisión?

Sunday, July 16, 2006

¿Qué guardar en el CVS?

nteresante (y clásica) discusión en TheServerSide sobre qué guardar en el CVS... ¿sólo código fuente? ¿también las dependencias (librerías)? ¿guardamos los archivos de proyecto generados por el IDE (.project y .classpath en eclipse)?

Básicamente hay dos enfoques:
1. primar la compatibilidad: que un programador que entra nuevo al proyecto pueda hacer un checkout y empezar a trabajar en el minuto 0, siempre que su entorno sea como el de los demás.
2. primar el "agnosticismo de plataforma": que cualquier desarrollador pueda trabajar en el proyecto con el ide que más le guste, gestionando él mismo su entorno.

Para gustos colores... yo personalmente prefiero elegir un IDE (el que más gente conozca con soltura, normalemente eclipse), crear un entorno "estandar" y que cualquiera pueda empezar a trabajar con un checkout. Dedicar un día para "configurar el entorno" me parece una pérdida de tiempo.

Java Service Wrapper 3.2.1

Java Service Wrapper es una herramienta que permite instalar tus programas java como servicios nativos de Windows o de Unix, permitiéndote controlarlos con las herramientas del SO.
Esta nueva versión corrige bugs encontrados en anteriores versiones e incluye nuevas características como el soporte para poner pausa a los servicios en Windows.

http://wrapper.tanukisoftware.org/doc/english/introduction.html

Tuesday, July 04, 2006

Artículo sobre cómo imprimir desde Java

Java Pro Programming: Printing

Learn how to use the print service API

Summary
In this article, an excerpt from Pro Java Programming (Apress, June 2005), Brett Spell explains step-by-step how to locate print services, create a print job, create an implementation of the Doc interface that describes the data you want to print, and initiate printing. (4,500 words; July 25, 2005)
By Brett Spell


Printer-friendly version Printer-friendly version | Send this article to a friend Mail this to a friend


Advertisement

Java matured very quickly in most respects after it was first introduced, but for a long time, printing was one of Java's weakest points. In fact, Java 1.0 didn't offer any support for printing at all. Java 1.1 included a class called PrintJob in the java.awt package, but the printing capabilities supported by that class were somewhat crude and unreliable. When Java 1.2 (or "Java 2") was introduced, it included a completely separate mechanism (called the Java 2D printing API) for printing designed around PrinterJob and other classes and interfaces defined in the new java.awt.print package. This rendered the PrintJob-based printing mechanism (also known as AWT printing) largely obsolete, although PrintJob has never been deprecated and, at least of this writing, is still technically a supported class.

Additional changes were made in J2SE 1.3 when PrintJob's capabilities expanded to allow the setting of job and page attributes using the appropriately named JobAttributes and PageAttributes classes within the java.awt package. With the release of J2SE 1.3, the printing capabilities were reasonably robust, but some problems still existed aside from the confusion associated with having two completely separate printing facilities. For one thing, both facilities used an implementation of the java.awt.Graphics class for rendering the content to be printed, which meant anything that needed to be printed had to be rendered as a graphical image. In addition, the newer and generally more robust PrinterJob facility provided only limited support for setting attributes associated with the job. Finally, neither facility provided a way to programmatically select the target printer.

The biggest change in Java's printing capabilities to date came with the release of J2SE 1.4, when the Java print service API was introduced. This third implementation of printing support in Java addressed the limitations that were just described using an implementation of the PrintService and DocPrintJob interfaces defined in the javax.print package. Because this new API represents a superset of the functionality defined by the two older printing facilities, it's the one you should normally use and will be the focus of this article.

At a high level, the steps involved in using the Java print service API are straightforward:

  1. Locate print services (printers), optionally limiting the list of those returned to the ones that support the capabilities your application needs. Print services are represented as instances of PrintService implementations.
  2. Create a print job by calling the createPrintJob() method defined in the PrintService interface. The print job is represented by an instance of DocPrintJob.
  3. Create an implementation of the Doc interface that describes the data you want to print. You also have the option of creating an instance of PrintRequestAttributeSet that describes the printing options you want.
  4. Initiate printing by calling the print() method defined in the DocPrintJob interface, specifying the Doc you created in the previous step and the PrintRequestAttributeSet or a null value.

You'll now examine each of these steps and see how to achieve them.

Note
Within this article, I'll use the terms printer and print service interchangeably because, in most cases, a print service is nothing more than a representation of a physical printer. The more generic print service reflects that the output can theoretically be sent to something other than a printer. For example, a print service might not print the output at all but instead write it to a disk file. In other words, all printers are represented by a print service, but not every print service necessarily corresponds to a physical printer. In practice, though, it's likely you'll almost always send your content to a printer, which is why I'll sometimes use the simpler printer term instead of the more technically accurate print service.

Locating print services
You locate a printer using one of three static methods defined in the PrintServiceLookup class. The simplest of the three methods is lookupDefaultPrintService(), and, as its name implies, it returns a reference to the service that represents your default printer:

PrintService service = PrintServiceLookup.lookupDefaultPrintService();

Although this method is simple and convenient, using it to select which printer to send output to means you're implicitly assuming that the user's default printer will always be able to support the capabilities your application needs in order to be able to print its output correctly. In practice, you'll typically want to select only those printers that are able to handle the type of data you want to print and that support the features your application needs, such as color or two-sided printing. To retrieve the list of all defined printers or to retrieve a list that's limited to printers supporting certain capabilities, you'll want to use one of two other static methods defined in PrintServiceLookup: either lookupPrintServices() or lookupMultiDocPrintServices().

The lookupPrintServices() method accepts two parameters: an instance of DocFlavor and an instance of some implementation of the AttributeSet interface. As you'll see shortly, you can use both of these to limit the list of printers returned by the method, but lookupPrintServices() allows you to specify a null value for either or both of the two parameters. By specifying a null value for both parameters, you're effectively requesting that the method return a PrintService instance for every printer that's available. At this point, you haven't really examined the methods defined in PrintService, but one of them is the getName() method, which returns a String representing the name of the printer. You can display a list of all printers available on your system by compiling and running code like this:

PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
for (int i = 0; i < services.length; i++) {
System.out.println(services[i].getName());
}

For example, if you have access to printers named Alpha, Beta, and Gamma that are attached to a server named PrintServer, running the previous code produces this output:

\\PrintServer\Alpha
\\PrintServer\Beta
\\PrintServer\Gamma

Now let's examine the parameters you can pass to the lookupPrintServices() method and see how they allow you to limit the printers returned to those with only certain capabilities.

DocFlavor
The first parameter you can specify on a call to lookupPrintServices() is an instance of the DocFlavor class, which describes the type of data to be printed and how that data is stored. In most cases, it won't be necessary for you to create a new instance of DocFlavor because Java includes many predefined instances, allowing you to simply pass a reference to one of those instances to lookupPrintServices(). However, let's look at the DocFlavor constructor and methods to understand how an instance is used by a print service.

The two arguments required when creating an instance of DocFlavor are both String instances, with one representing a MIME (Multipurpose Internet Mail Extensions) type and the other being the name of a representation class. The MIME type is used by a DocFlavor to describe the type of data to be printed. For example, if you're printing a gif file, you'll need to use a DocFlavor that has a MIME type of image/gif. Similarly, you might use a MIME type of text/plain if you're printing text information or text/html for an HTML document.

Representation class
While the MIME type describes the type of data to be printed, the representation class describes how that data is to be made available to the print service. DocFlavor includes seven static inner classes, with each one corresponding to a representation class and each one corresponding to a different way of encapsulating the data that's to be printed.

Table 1 shows the names of the static inner classes defined within DocFlavor and their corresponding representation classes. Note that aside from SERVICE_FORMATTED (which I'll discuss in detail later), each one is described as being associated with either "binary" or "character" data. In reality, the distinction is somewhat artificial because character data is really just a specialized form of binary data, in this case, referring to binary data that contains only human-readable characters and perhaps some formatting characters such as tabs, carriage returns, and so on. However, the distinction is important because it reflects that character-oriented representation classes aren't appropriate for storing binary data that's to be printed.

For example, you wouldn't store a representation of a gif image in a character array or a String, and you wouldn't make it accessible through a Reader implementation. On the other hand, since "character" data is just a specialized type of binary data, it's entirely appropriate to store text information in a byte array or make it accessible through an InputStream or via a URL.

Table 1. DocFlavor's predefined representation classes

Inner class name Representation classData type
BYTE_ARRAY [B (byte[])Binary
CHAR_ARRAY[C (char[]) Character
INPUT_STREAM java.io.InputStream Binary
READER java.io.Reader Character
SERVICE_FORMATTED java.awt.print.Pageable or java.awt.print.Printable Other
STRING java.lang.String Character
URL java.net.URL Binary

Each of these static inner classes defined within DocFlavor corresponds to a particular representation class, but remember that I said each DocFlavor instance encapsulates both a representation class and a MIME type that identifies the type of data to be printed. To access an instance of DocFlavor that corresponds to both the representation class and the MIME type of the content you want to print, you'll need to reference an inner class within one of the inner classes listed in Table 1. For example, let's suppose you want to print a gif file that's available on the Web through a URL. In this case, the obvious choice for the representation class is java.net.URL, which is associated with the static class named URL that's defined within DocFlavor. If you browse the documentation for that inner class, you'll find that it, in turn, defines a number of static inner classes, each one corresponding to a particular MIME type representing data types commonly supported by printers. Table 2 shows the inner classes defined within DocFlavor.URL and their corresponding MIME types.

Table 2. The DocFlavor.URL inner classes

Static inner classesMIME type
AUTOSENSE application/octet-stream
GIF image/gif
JPEG image/jpeg
PCL PCL application/vnd-hp.PCL
PDF application/pdf
PNG image/png
POSTSCRIPT application/postscript
TEXT_HTML_HOST text/html
TEXT_HTML_US_ASCII text/html;charset=us-ascii
TEXT_HTML_UTF_16 text/html;charset=utf-16
TEXT_HTML_UTF_16BE text/html;charset=utf-16be
TEXT_HTML_UTF_16LE text/html;charset=utf-16le
TEXT_HTML_UTF_8 TEXT_HTML_UTF_8 text/html;charset=utf-8
TEXT_PLAIN_HOST text/plain
TEXT_PLAIN_US_ASCII text/plain;charset=us-ascii
TEXT_PLAIN_UTF_16 text/plain;charset=utf-16
TEXT_PLAIN_UTF_16BE text/plain;charset=utf-16be
TEXT_PLAIN_UTF_16LE text/plain;charset=utf-16le
TEXT_PLAIN_UTF_8 text/plain;charset=utf-8

Since you'll print a gif image that's available through a URL, you can access an appropriate DocFlavor instance using this code:

DocFlavor flavor = DocFlavor.URL.GIF;

This code creates a reference to the static instance of DocFlavor that has a representation class of java.net.URL and a MIME type of image/gif.

The classes listed in Table 2 are defined within the DocFlavor.URL class, but what about the other six inner classes defined within DocFlavor? Again, I'll defer a discussion of SERVICE_FORMATTED until later, but, as for the classes associated with binary data types, all three (BYTE_ARRAY, INPUT_STREAM, and URL) include inner classes with the names shown in Table 2. So, for example, if you had loaded the gif data into a byte array, you might instead choose to use code like this:

DocFlavor flavor = DocFlavor.BYTE_ARRAY.GIF;

Just as the three DocFlavor inner classes associated with binary data types include their own inner classes, the three associated with character data types include a different set of inner classes, as shown in Table 3.

Table 3. CHAR_ARRAY, READER, and STRING

Static inner classMIME type
TEXT_HTML text/html;charset=utf-16
TEXT_PLAIN text/plain;charset=utf-16

So, for example, if you wanted to print plain text data that's stored in an instance of String, you could use code like the following:

DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN;

Similarly, if the text data represented an HTML document and you wanted to have the data printed as it would appear within a Web browser, you could use the following:

DocFlavor flavor = DocFlavor.STRING.TEXT_HTML;

Choosing the right printer
Remember that the discussion of DocFlavor began with a desire to make sure the printer you use actually supports the type of data that's to be printed and the delivery mechanism (representation class) you intend to use. This might seem like an unnecessary step, but, in reality, you may be surprised at which document types a given printer supports. For example, the text-oriented types just described might seem as though they'd be the simplest ones to support, so, if your application is printing plain or HTML text, you might be tempted to simply select the first available print service and send the output to that printer. As it turns out, though, many printers don't support the text-based representation classes, and, if you attempt to send output to a printer that doesn't support the DocFlavor you select, an exception will be thrown like the following:

Exception in thread "main" sun.print.PrintJobFlavorException: invalid flavor
at sun.print.Win32PrintJob.print(Win32PrintJob.java:290)
at PrintTest.main(PrintTest.java:11)

Now that you've seen how to obtain a reference to a DocFlavor and I've discussed the importance of selecting a printer that supports the selected flavor, I'll show how you can use it to make sure you use a printer that supports the flavor you need. As I discussed earlier, the lookupPrintServices() allows you to specify a DocFlavor as its first argument, and, if you specify a non-null value, the method will return only the PrintService instances that correspond to printers that support the specified DocFlavor. For example, the following code will retrieve an array that identifies all printers on your system that can print gif images that are referenced via a URL:

DocFlavor flavor = DocFlavor.URL.GIF;
PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null);

Alternatively, if your application has already retrieved a reference to a PrintService and you want to determine whether it supports a particular flavor, you can call the isDocFlavorSupported() method. In the following code segment, a reference to the default printer is obtained, and an error message will be displayed if it's not able to print a gif image retrieved via a URL:

PrintService service = PrintServiceLookup.lookupDefaultPrintService();
DocFlavor flavor = DocFlavor.URL.GIF;
if (!service.isDocFlavorSupported(flavor)) {
System.err.println("The printer does not support the appropriate DocFlavor");
}

AttributeSet
As you've now seen, a DocFlavor describes the data to be printed and can be used to ensure that a PrintService supports the corresponding type of data. However, your application may also need to select a printer based upon the features that the printer supports. For example, if you're printing a graph that uses different colors to convey information, you might want to see if a given service supports color printing and, if not, either prevent the printer from being used or render a representation of the graph that doesn't rely on colors.

Characteristics such as the ability to print in color, to print on both sides of a page, or to use different orientations (portrait or landscape) are referred to as a printer's attributes, and the javax.print.attribute package contains many classes and interfaces you can use to describe those attributes. One of those interfaces is AttributeSet, which was mentioned earlier as the second parameter that can be specified on a call to lookupPrintServices(). As you might expect, an implementation of AttributeSet represents a collection of attributes, and specifying a non-null value on the call to lookupPrintServices() will result in only print services being returned that support those attributes. In other words, if you specify both a DocFlavor and an AttributeSet on a call to lookupPrintServices(), the method will return only those printers that support both the specified flavor and the appropriate attributes.

Attribute
Given that an AttributeSet is a collection of attributes, the obvious question is, how do you go about specifying the attribute values that should make up that collection? The javax.print.attribute package also includes an interface named Attribute, and, as you'll see shortly, you create the collection of attributes by adding instances of Attribute to an AttributeSet by calling the add() method. Reviewing the documentation for the Attribute interface reveals that a large number of implementations are defined within the javax.print.attribute.standard package, and it's those classes you'll use. Before you see how that's done, it's helpful to review the other interfaces in the javax.print.attribute package along with their implementations.

Attribute roles
So far, I've described attributes as capabilities of a print service, and while that's largely true, it's really something of an oversimplification, at least in terms of how Java supports attributes. For each different attribute, Java associates it with one or more "roles," and the attribute is valid only in the context of the role(s) with which it's assigned. In other words, various places within the Java print service attributes are used, and not every attribute is valid within every context.

To better understand this, consider the OrientationRequested and ColorSupported implementations of Attribute that are defined within the javax.print.attribute.standard package. The OrientationRequested attribute is one you can specify when creating a document to be printed and allows you to specify the orientation (such as portrait or landscape) that should be used when printing the document. In contrast, ColorSupported is an attribute that can be returned when you call the getAttributes() method of the PrintService interface. In other words, OrientationRequested is an attribute you use to pass information to the print service, and ColorSupported is one that the print service uses to provide you with information about the printer's abilities. You can't specify ColorSupported as an attribute when creating a document to be printed because the printer's ability to print in color isn't something your application is able to control.

Interfaces and implementations
When you first look at the interfaces and classes defined in the javax.print.attribute package, it may appear to present a confusing list of choices when it comes to the interfaces and classes defined there. Aside from the Attribute and AttributeSet interfaces and the HashAttributeSet class that implements AttributeSet, the javax.print.attribute package has four sets of subinterfaces and classes, as shown in Table 4 and Figure 1.

Table 4. Interfaces and classes defined within the javax.print.attribute package

Attribute subinterface AttributeSet subinterfaceAttributeSet subclass
DocAttributeDocAttributeSetHashDocAttributeSet
PrintJobAttribute PrintJobAttributeSet HashPrintJobAttributeSet
PrintRequestAttribute PrintRequestAttributeSet HashPrintRequestAttributeSet
PrintServiceAttribute PrintServiceAttribute PrintServiceAttributeSetHashPrintServiceAttributeSet


Figure 1. The class hierarchy of a portion of the javax.print.attribute package. Click on thumbnail to view full-sized image.

So why do you need all these various interfaces and implementations, particularly since the more generalized Attribute, AttributeSet, and HashAttributeSet are provided? The answer is that these specializations are defined to ensure that only the appropriate attributes are used within the role(s) where they're valid. For example, I mentioned that one place where you can use attributes is when creating a document that's to be printed and that some attributes such as ColorSupported aren't valid within that context. When creating such a document, you'll use the DocAttributeSet interface (or more specifically, its HashDocAttributeSet implementation), and the implementation will allow you to add only attributes that implement the DocAttribute interface. The four different types of roles are as follows:

  • Doc: Specified when creating a document that's to be printed to describe how the document should be printed
  • PrintJob: Attributes returned from the print job to describe the state of the job
  • PrintRequest: Attributes passed to the print job when a request is made to initiate printing
  • PrintService: Returned by a PrintService to describe the capabilities of the service

To see how this works, let's create an instance of a DocAttributeSet and then attempt to set both the OrientationRequested and ColorSupported attributes for that AttributeSet. The HashDocAttributeSet defines a no-argument constructor, so you can create an instance easily as follows:

DocAttributeSet attrs = new HashDocAttributeSet();

Now that you've created the AttributeSet, you can call its add() method and pass to it instances of Attribute implementations. If you examine the documentation for the OrientationRequested class, you'll see it includes references to a number of static OrientationRequest instances, with each one corresponding to a document orientation, such as portrait or landscape. To specify the orientation you want, all you need to do is pass a reference to the appropriate static instance to the add() method as follows:

DocAttributeSet attrs = new HashDocAttributeSet();
attrs.add(OrientationRequested.PORTRAIT);

The ColorSupported class is slightly different but equally simple to use, and it defines two static instances: one that indicates that color printing is supported and another that indicates it's not supported. You can attempt to add a ColorSupported attribute to the DocAttributeSet with code like this:

DocAttributeSet attrs = new HashDocAttributeSet();

attrs.add(OrientationRequested.PORTRAIT);
attrs.add(ColorSupported.SUPPORTED);

As mentioned earlier, it's not appropriate to specify whether to support color printing because this isn't something an application is allowed to control. In other words, the ColorSupported attribute isn't valid within the context of a set of document attributes, and, as a result, attempting to run the previous code will cause a ClassCastException to be thrown when it attempts to add the ColorSupported attribute.

To understand how this works, remember that each AttributeSet subinterface (in this case, DocAttributeSet) has a corresponding Attribute subinterface (DocAttribute) and an implementation class (HashDocAttributeSet). When an attempt is made to add an attribute, the implementation class tries to cast the Attribute parameter to the corresponding subinterface type, which, in turn, ensures that only attributes appropriate for that context can be added successfully.

In this case, the add() method of HashDocAttributeSet is first called with an instance of OrientationRequested, and it successfully casts that object to a DocAttribute, because, as Figure 2 shows, OrientationRequested implements that interface. In contrast, however, passing an instance of ColorSupported fails because ColorSupported doesn't implement DocAttribute.


Figure 2. The class hierarchy of a portion of the javax.print.attribute package. Click on thumbnail to view full-sized image.

As this example illustrates, the four different groups of interfaces and classes shown in Table 4 ensure that only the appropriate attributes are used within the appropriate context. Notice that a great deal of overlap occurs between roles and the various attributes, so many of the attributes are associated with more than one role. For example, many of the attributes implement both PrintJobAttribute and PrintRequestAttribute because many of the attributes that are maintained and provided to you by a print job correspond to attributes you can specify when you request that printing be initiated. You can, for instance, both specify the job name by adding it to a PrintRequestAttributeSet and retrieve the name of the job during printing by retrieving it from a PrintJobAttributeSet. As a result, the JobName attribute class implements both PrintRequestAttribute and PrintJobAttribute.

AttributeSet and HashAttributeSet
You've now seen why the four groups of subclasses exist, but what about the base AttributeSet interface and the HashAttributeSet superclass? AttributeSet/HashAttributeSet is used in situations where you can't assume that only attributes associated with a single role will need to be stored in a collection. Remember that earlier I mentioned that the lookupPrintServices() method allows you to specify an AttributeSet parameter that will limit which print services are returned. On the surface it might appear that it'd be better to require that an instance of PrintServiceAttributeSet be specified, but many of the attributes you might want to specify don't implement PrintServiceAttribute.

Let's assume you want the lookupPrintServices() method to retrieve only services that support both color printing and landscape printing. Those attributes correspond to the ColorSupported and OrientationRequested attributes, respectively, but notice that those two attribute classes don't share a common role: ColorSupported is a PrintServiceAttribute, and OrientationRequested is associated with all three of the other roles (Doc, PrintRequest, and PrintJob), as shown in Figure 2. What this means is that there's no single role-specific AttributeSet interface/class that can contain both a ColorSupported attribute and a Sides attribute.

The way to create an AttributeSet that contains both an OrientationRequested and a ColorSupported instance is to simply use an instance of the generic HashAttributeSet. Unlike its subclasses, it doesn't limit you to adding attributes associated with a particular role, so you can successfully execute the following code:

AttributeSet attrs = new HashAttributeSet();
attrs.add(ColorSupported.SUPPORTED);
attrs.add(OrientationRequested.LANDSCAPE);
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, attrs);

Printer selection via user interface
Up to this point, I've assumed that the printer to be used would be selected programmatically by the application. In practice, however, it's more common to simply display a dialog and allow the user to select which printer to use when printing the output. Fortunately, Java makes it easy to do just that by using the static printDialog() method in the ServiceUI class defined within the javax.print package.

Aside from the location of the dialog to be displayed, the only parameter values that must be specified on the call to printDialog() are these:

  • An array of PrintService instances from which the user can choose.
  • The default PrintService.
  • An instance of PrintRequestAttributeSet. This is used to populate the dialog that's displayed, and it returns any changes that were made by the user before the dialog was dismissed.

To illustrate how this works, you can use the following simple code segment to display a print dialog:

PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
PrintService svc = PrintServiceLookup.lookupDefaultPrintService();
PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
PrintService selection = ServiceUI.printDialog(
null, 100, 100, services, svc, null, attrs);

When run, the code produces a dialog like the one shown in Figure 3.


Figure 3. The printer dialog

As this code illustrates, the value returned from the printDialog() method is an instance of PrintService that identifies which printer the user selected or null if the user canceled the printer dialog. In addition, the PrintRequestAttributeSet is updated to reflect any changes made by the user through the dialog, such as the number of copies to be printed.

By using the printDialog() method, you can allow users to select which printer their output will be sent to, providing the kind of functionality that users have come to expect from professional applications.

Creating a print job
This is the simplest step involved in printing, because once you've obtained a reference to a PrintService, all you need to do is call its createPrintJob() method like so:

PrintService service;
.
.
.
DocPrintJob job = service.createPrintJob();

As indicated in the code, the value returned from createPrintJob() is an instance of DocPrintJob, an object that allows you to control and monitor the status of the printing operation. To initiate printing, you'll call the DocPrintJob object's print() method, but, before you do so, you'll need to define the document to be printed and optionally a PrintRequestAttributeSet. You've already seen how to construct and populate an AttributeSet, so I won't review that step; instead, you'll see how you go about defining the document to be printed.

Defining the document to print
The next step in printing is to define the document that's to be printed, which is done by creating an instance of an implementation of the Doc interface defined in the javax.print package. Each instance of Doc has two mandatory attributes and an optional one:

  • An Object that represents the data to be printed
  • An instance of DocFlavor that describes the type of data to print
  • An optional DocAttributeSet containing attributes to use when printing the document

Reviewing the documentation for the Doc interface reveals that the javax.print package includes an implementation of the interface named SimpleDoc, which has a constructor that takes three arguments that match the three attributes described previously. To see how to construct an instance of SimpleDoc, let's assume you want to print two copies of a gif image that's stored at http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif.

All that's needed to construct a SimpleDoc instance that describes the document to be printed is to create a URL that points to the image, obtain a reference to the appropriate DocFlavor, and pass those two objects to the SimpleDoc constructor as follows:

URL url = new URL(
"http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif");
DocFlavor flavor = DocFlavor.URL.GIF;
SimpleDoc doc = new SimpleDoc(url, flavor, null);

Initiating printing
The final step involved in printing is to call the DocPrintJob's print() method, passing it the Doc object that describes the data to be printed and optionally an instance of PrintRequestAttributeSet. For the sake of simplicity, I'll assume the default printer supports the flavor and attributes you need, in which case you could use the following code to print two copies of the gif file referenced in the previous example:

PrintService service = PrintServiceLookup.lookupDefaultPrintService();
DocPrintJob job = service.createPrintJob();
URL url = new URL(
"http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif ");
DocFlavor flavor = DocFlavor.URL.GIF;
Doc doc = new SimpleDoc(url, flavor, null);
PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
attrs.add(new Copies(2));
job.print(doc, attrs);

Note that, in some cases, printing is performed asynchronously, in which case, the call to print() may return before printing has actually completed

Tuesday, June 27, 2006

Unix: Ten Things Every Java Developer Should Know

One of the great things about Java is how multi-platform it really is. While cross platform glitches do occur, they are not really all that common. But since the law of unintended consequences is all pervasive, we now have the common sight of teams of developers building Java programs meant to run on Unix boxes on Windows.

Developing code meant for Unix on Windows does work reasonably well. The trouble is, many those coders slaving away in front of XP have a very limited understanding of their target platform. If that describes you, this following list is meant for you. Without further ado, here are the ten things you really need to know about Unix, in reverse David Letterman order:

10) You need to be special to use some ports

On Unix machines, programs run by ordinary mortals cannot use network ports less than 1024. Only the special root user can use these ports. If you do decide that you need to run your server as root, be very careful since a program running as root is all powerful on a Unix machine.

9) There is no magic file locking

Windows has this magic file locking mechanism that prevents people from removing a file while it is open. Thus, on a Windows box the call to delete() in the following code is pretty much sure to fail:

 InputStream is = new FileInputStream("foo.txt");
(new File("foo.txt")).delete();
int ch;
while( (ch = is.read()) > 0 )
System.out.println( "char: " + (char)ch );
is.close();

The delete will fail because someone (our program in fact) has the file open. After we get done printing out its contents, foo.txt will still be there. The kicker is, the delete() will work just fine on any Unix box. Under Unix, the call to delete() will delete the entry for foo.txt out from the file system, but since someone (our program) still has the file open, the bytes will live on, to be read and printed out. Only when we close the stream will the contents of foo.txt follow its name into oblivion.

In exactly the same way, on a Unix box, someone can delete a program's current directory right out from underneath it.

8) Sometimes there is no GUI

People who are used to Windows are sometimes surprised to find their programs running on a Unix box that has no GUI at all. None. Zilch. Nada. In Unix, the GUI is an add on component and it is completely optional. The machine will run fine without it and servers commonly do. Be very careful making calls to those handy java.awt methods -- some of them will fail on a machine with no GUI.

While you are at it, you may want to rethink what you are writing out with System.out. Many severs not only lack a GUI, they are completely headless: no keyboard, no mouse, no screen. If you are deploying into this kind of environment, the log file is your friend, because your program's cries for help are unlikely to be heard anywhere else.

7) In Unix, there is no registry...

... but if there was, it would be a plain text file. Unix systems have no central place like the Windows registry for storing configuration information. Instead, Unix configuration is spread over a fair number of different files. Many of these files live in a directory called /etc : the list of users is in a file called /etc/passwd, while the name of the machine is typically found in /etc/host.

I guess if you are a Windows user that is the bad news. Here is the good news: most, if not all of the configuration files are plain text files. You can look at them with any old text editor. A further bit of good news is that all modern Unix like systems come with GUIs to edit the configuration files.

6) Forward slashes are your friend

This one is really more about Java on Windows than it is about Unix, but useful anyway. Just about everyone knows that Unix uses forward slashes to separate the bits of a path: /etc/passwd, while Windows uses backslashes: c:\Program Files\Windows. What a disturbing number of folks don't realize is that forward slashes work just fine on in Java on Windows too. On a Windows box, Java is smart enough to translate /a/b/c/foo.txt to something like C:\a\b\c\foo.txt.

Should you rely on this for production? No. If you are really coding cross platform Java, you need know about File.pathSeparator and the various File constructors. But if you are coding stuff that is only meant for Unix, and you just want to test it on your windows box, knowing that the forward slashes work in both places can save a lot of agony.

5) Our services are just programs

In Windows, system services are special programs, written to a particular service API. On Unix, services are provided by pretty ordinary programs that do service like things.

Typically, a Unix service consists of the useful program and a script which starts the program in the background when the system starts up and may shut it down again when the system is halting. Many Unix systems keep these scripts in the /etc/init.d directory.

4) Environment variables are hard to change

Windows programmers sometimes cook up little batch files that set the environment variables they want. Perhaps you need to roll back to an old version of Java and Ant. You might write a batch file that looks something like:

set JAVA_HOME=C:\java1.4.1_05
set ANT_HOME=C:\apache-ant-1.6.1
They can then run the batch file:
c:\> setenv.bat

and have the environment that you need. If you try to translate this directly into Unix-land, you get a script that looks not too different:

#!/bin/sh

JAVA_HOME=/usr/java1.4.1_05
export JAVA_HOME

ANT_HOME=/usr/apache-ant-1.6.1
export ANT_HOME

So you try out your new script...

$ setenv.sh

...and it does nothing. The trouble is that the Unix shell runs scripts by creating a copy of itself and running the script in the new shell. This new shell will read in the script, set all the environment variables and then exit, leaving the original shell and its environment unchanged. I hate when that happens. Changing directories works pretty much the same way: if you do a cd in an ordinary shell script, it will change the directory for that second shell, which may or may not be what you want.

If what you want is to change things in your current shell, you need to have the current shell read in the script directly, without starting a new shell to do the job. You can do this with by using the dot command:

. setenv.sh

Life will be good as your environment variables change.

I should mention that there are several different shells available in Unix. Which one you use is mostly a matter of taste, either yours or the person who set up your account. The commands above will work with many of the common Unix shells, but if you happen to be running the C shell (note the pun), then your little script will need to look something like:

setenv JAVA_HOME /usr/java1.4.1_05
setenv ANT_HOME /usr/apache-ant-1.6.1

You also need a different command to read the script in:

source setenv.csh

3) We don't like spaces in our file names

Okay, this one probably has more to do with Unix users and sys admins than the OS itself, but the fact is, we don't like spaces in our paths. Oh it is perfectly possibly to have spaces in Unix file names. In fact you can put darn near anything in a Unix file name: /tmp/:a,*b%c is actually the name of a directory on the system that I am using to write this.

But can and should are two different things. Unix users spend much of their time using the command line and Unix command line utilities are not crazy about paths with stars, question marks and especially spaces. The Unix guy can always find a way, but if you insist on putting strange characters like spaces in your file names, you are just finding a way to annoy your local Unix guy.

2) There is no carriage to return

If I had just one magic lighting bolt to hurl at one person, I think I would pick the guy who decided to use different line terminating characters on the two major OS's. I suspect we would have hyper intelligent computers by now were it not for all the time wasted trying to figure out why my file looks funny on your OS.

In a plain text file, Windows typically uses a carriage return followed by a newline character to mark the end of a line. Unix dispenses with the carriage return and makes due with the single newline character. Although the tools are getting better, Unix style files may show up as one long line in Windows -- not a single carriage return/newline pair in that file, must be a single line. Likewise, Windows style files will sometimes show up on Unix with garbage characters at the end of a line -- that extra carriage return is meaningless under the Unix convention.

This doesn't matter much for Java files -- it's all white space to the Java compiler. But some native Unix programs care passionately about those funny characters at the end of a line. In particular if you use a Windows based tool to edit a Unix script and your tool adds carriage returns to the script file, you have screwed it up. The script will no longer run. Worse, since many Unix editors do now handle the carriage returns, you could look at the broken script and see nothing wrong.

1) You need a Linux box

If you are doing significant development for Unix, you owe it to yourself and your customers to know something about Unix. If all else fails, get yourself an old PC and some of that free Linux. Or get yourself one of the many bootable Linux-on-a-CD distributions and try it out on any machine. Java is nearly platform independent, but not quite. It is up to you to make up the difference.

Wednesday, March 29, 2006

Memoria libre y ocupada en el sistema

  • Runtime.getRuntime().totalMemory()
  • Runtime.getRuntime().freeMemory()

Tuesday, March 28, 2006

XMLEncoder

Escribe XML una instancia de una clase, pero solo los atributos que tengan get() implementado, ya que usa reflexión.

XMLEncoder

Escribe XML una instancia de una clase, pero solo los atributos que tengan get() implementado, ya que usa reflexión.

SwingX Online Components Store

SwingLabs es un repositorio de componentes Swing, todos ellos distribuidos bajo una licencia libre, que esta impulsado desde Sun Microsystems. Recientemente acaban de hacer disponible la versión beta.

Aquí tenéis unas cuantas capturas de pantalla de los componentes. Seguro que encontráis algo que puede seros útil.

Sunday, March 26, 2006

Lanzar un navegador desde java

Bare Bones Browser
Launch for Java

Use Default Browser to Open a Web Page from a Swing Application

Java is often touted as the programing language of the Internet, so you would think Java might include a standard platform-independent mechanism to launch the user's default web browser. Unfortunately, this commonly needed feature is left to the application developer to build, and it's not easy. Two open source projects exist specifically to address this problem -- BrowserLauncher and BrowserLauncher2. The BrowserLauncher2 project is more current and is an industrial strength solution packed with advanced features and even supports old browsers and old OSes.

Bare Bones


The Bare Bones Browser Launch solution, on the other hand, is intended for those with much simpler requirements or those just looking for an educational "Hello, World" type tutorial on the subject. This solution is appropriate when a compact lightweight method will suffice which limits support to the most current browsers and OSes. Bare Bones is free and works on Mac OS X, GNU/Linux, Unix (Solaris), and Windows XP.

Let's jump straight to the code:

BareBonesBrowserLaunch.java

///////////////////////////////////////////////////////// // Bare Bones Browser Launch // // Version 1.5 // // December 10, 2005 // // Supports: Mac OS X, GNU/Linux, Unix, Windows XP // // Example Usage: // // String url = "http://www.centerkey.com/"; // // BareBonesBrowserLaunch.openURL(url); // // Public Domain Software -- Free to Use as You Like // ///////////////////////////////////////////////////////// import java.lang.reflect.Method; import java.util.Arrays; import javax.swing.JOptionPane; public class BareBonesBrowserLaunch { private static final String errMsg = "Error attempting to launch web browser"; public static void openURL(String url) { String osName = System.getProperty("os.name"); try { if (osName.startsWith("Mac OS")) { Class fileMgr = Class.forName("com.apple.eio.FileManager"); Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] {String.class}); openURL.invoke(null, new Object[] {url}); } else if (osName.startsWith("Windows")) Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); else { //assume Unix or Linux String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape" }; String browser = null; for (int count = 0; count < browser ="="" browser =" browsers[count];" browser ="="">
Launch the user's default browser from your Java Swing application with the following line of code:
BareBonesBrowserLaunch.openURL(urlStr);
This is a fire and forget method -- no further communication or confirmation is provided. However, a pop-up error message will be displayed to the user in most cases if a failure is encountered.

MyApp Test Program


You can try out the cross-platform Bare Bones Browser Launch with this small standalone test program:

MyApp.java

import java.awt.event.*; import javax.swing.*; public class MyApp { public static void main(String[] args) { JFrame frame = new JFrame(); JPanel panel = new JPanel(); final JTextField urlField = new JTextField("http://www.centerkey.com "); JButton webButton = new JButton("Web Trip"); webButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BareBonesBrowserLaunch.openURL(urlField.getText().trim()); } } ); frame.setTitle("Bare Bones Browser Launch"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); panel.add(new JLabel("URL:")); panel.add(urlField); panel.add(webButton); frame.getContentPane().add(panel); frame.pack(); frame.setVisible(true); } }

Tutorial


Put both the "BareBonesBrowserLaunch.java" and "MyApp.java" files into a folder, and issue the following command line instructions:
$ javac *.java
$ java MyApp
The first command compiles the two Java files into class files, and the second command runs the test program.

A window like the following will be displayed:
Click the "Web Trip" button to launch the default browser.

Of course, you'll need the Java JDK for this work, and you may also need to specify the full path to the Java commands. On Windows for example, the "javac" command above would become something like:
> "\Program Files\Java\jdk1.5.0_06\bin\javac" *.java
That's it.

JAR Library


Instead of putting the Bare Bones Browser Launch code directly in your project, you can alternatively use it as an external JAR library [file: BareBonesBrowserLaunch.jar, v1.5, 21KB] complete with source code and Javadoc. Tell your IDE, such as Eclipse or NetBeans, to include the JAR file into your project and then add the following "import" statement to the class responsible for opening a web page:
import com.centerkey.utils.BareBonesBrowserLaunch;
The code completion feature in most IDEs will automatically create the above "import" statement for you.

The steps for including an external JAR into your project will vary depending on your IDE. In Eclipse, open your project and navigate through these menus and options (screenshot):
Project → Properties → Java Build Path → Libraries → Add External JARs...
Now link to the Javadoc included within the JAR (screenshot):
Expand "BareBonesBrowserLaunch.jar" → Select "Javadoc location: (None)" → Edit... → Select "Javadoc in archive" → Browse... to BareBonesBrowserLaunch.jar → 2nd Browse... to doc
Finish the configuration process and you're ready to use the Bare Bones Browser Launch including the help activated with the F1 key.

Llamar a otros programas

Using the Desktop API in Java SE 6 (Mustang)
By John O'Conner, February 2006

With the default graphical user interface (GUI) look and feel, printing, and performance, the Java platform has come a long way in its effort to minimize the difference between the performance and integration of native applications and Java applications. Version 6 of the Java Platform, Standard Edition (Java SE), code-named Mustang, continues to narrow the gap with new system tray functionality, better print support for JTable, and now the Desktop API (java.awt.Desktop API). This article describes the new Desktop API, which allows Java applications to interact with the default applications associated with specific file types on the host platform. In order to describe the API more effectively, the article also features a simple application called DesktopDemo.

Desktop Overview

This new functionality is provided by the java.awt.Desktop class. The API is adopted from the JDesktop Integration Components (JDIC) project. The goal of that project is to make "Java technology-based applications first-class citizens" of the desktop, enabling seamless integration. Specifically, the new Desktop API allows your Java applications to do the following:

  • Launch the host system's default browser with a specific Uniform Resource Identifier (URI)
  • Launch the host system's default email client
  • Launch applications to open, edit, or print files associatedwith those applications

The Desktop API uses your host operating system's file associations to launch applications associated with specific file types. For example, if OpenDocument text (.odt) file extensions are associated with the OpenOffice Writer application, your Java application could launch OpenOffice Writer to open, edit, or even print files with that association. Depending on your host system, different applications may be associated with each different action.

Running the DesktopDemo Application

DesktopDemo is a simple Java application that uses Mustang's Desktop API. The application has one main window that exposes the entire API, allowing you to do three things:

  1. Launch the default browser with a specific URI.
  2. Launch the default email client with a mail recipient.
  3. Launch an associated application to open, edit, or print a file.

Figure 1 shows the user interface (UI).

Figure 1: DesktopDemo UI
Figure 1: DesktopDemo UI

You can run this application by downloading the application source and JAR files, changing your console's active directory to the application project's dist directory, and executing the following command using a Mustang JDK:

    java -jar DesktopDemo.jar

Determining Desktop API Support

Before launching the browser, email client, or any application, DesktopDemo must determine whether your platform supports the API. First, however, DesktopDemo disables all the graphical text fields and buttons. It enables these graphical components after determining that the platform supports them.

After instantiating the UI, the application's constructor quickly disables the few components of this application as shown in the following code:

    public DesktopDemo() {
// Initiate all GUI components.
initComponents();
// Disable buttons that launch browser and email client.
// Disable buttons that open, edit, and print files.
disableActions();
...
}

/**
* Disable all graphical components until we know
* whether their functionality is supported.
*/
private void disableActions() {
txtBrowserURI.setEnabled(false);
btnLaunchBrowser.setEnabled(false);

txtMailTo.setEnabled(false);
btnLaunchEmail.setEnabled(false);

rbEdit.setEnabled(false);
rbOpen.setEnabled(false);
rbPrint.setEnabled(false);

txtFile.setEnabled(false);
btnLaunchApplication.setEnabled(false);
}

...

public javax.swing.JTextField txtBrowserURI;
public javax.swing.JButton btnLaunchBrowser;
public javax.swing.JTextField txtMailTo;
public javax.swing.JButton btnLaunchEmail;
public javax.swing.JRadioButton rbEdit;
public javax.swing.JRadioButton rbOpen;
public javax.swing.JRadioButton rbPrint;
public javax.swing.JTextField txtFile;
public javax.swing.JButton btnLaunchApplication;

Use the Desktop.isDesktopSupported() method to determine whether the Desktop API is available. On the Solaris Operating System and the Linux platform, this API is dependent on Gnome libraries. If those libraries are unavailable, this method will return false. After determining that the API is supported, that is, the isDesktopSupported() returns true, the application can retrieve a Desktop instance using the static method getDesktop().

    Desktop desktop = null;
// Before more Desktop API is used, first check
// whether the API is supported by this particular
// virtual machine (VM) on this particular host.
if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
...

If your application doesn't check for API support using isDesktopSupported() before calling getDesktop(), it must be prepared to catch an UnsupportedOperationException, which is thrown when your application requests a Desktop instance on a platform that does not support these features. Additionally, if your application runs in an environment without a keyboard, mouse, or monitor (a "headless" environment), the getDesktop() method will throw a java.awt.HeadlessException.

Once retrieved, the Desktop instance allows your application to browse, mail, open, edit, or even print a file or URI, but only if the retrieved Desktop instance supports these activities. Each of these activities is called an action, and each is represented as a Desktop.Action enumeration instance:

  • BROWSE. Represents a browse action performed by the host's default browser
  • MAIL. Represents a mail action performed by the host's default email client
  • OPEN. Represents an open action performed by an application associated with opening a specific file type
  • EDIT. Represents an edit action performed by an application associated with editing a specific file type
  • PRINT. Represents a print action performed by an application associated with printing a specific file type

Before invoking any of these actions, an application should determine whether the Desktop instance supports them. This is different from determining whether a Desktop instance is available. The Desktop.isDesktopSupported() method tells you whether an instance can be created. Once a Desktop object is acquired, you can query the object to find out which specific actions are supported. If the Desktop object does not support specific actions, or if the Desktop API itself is unsupported, DesktopDemo simply keeps the affected graphical components disabled. As Figure 2 indicates, the components cannot be used to invoke Desktop features in the disabled state.

Figure 2: Graphical components are disabled when the desktop is unsupported.
Figure 2: Graphical components are disabled when the desktop is unsupported.

Using a new Desktop instance, the following code checks each Desktop.Action for support and enables the appropriate graphical components:

    public DesktopDemo() {
...
// Before more Desktop API is used, first check
// whether the API is supported by this particular
// VM on this particular host.
if (Desktop.isDesktopSupported()) {
desktop = Desktop.getDesktop();
// Now enable buttons for actions that are supported.
enableSupportedActions();
}
...
}

/**
* Enable actions that are supported on this host.
* The actions are the following: open browser,
* open email client, and open, edit, and print
* files using their associated application.
*/
private void enableSupportedActions() {
if (desktop.isSupported(Desktop.Action.BROWSE)) {
txtBrowserURI.setEnabled(true);
btnLaunchBrowser.setEnabled(true);
}

if (desktop.isSupported(Desktop.Action.MAIL)) {
txtMailTo.setEnabled(true);
btnLaunchEmail.setEnabled(true);
}

if (desktop.isSupported(Desktop.Action.OPEN)) {
rbOpen.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.EDIT)) {
rbEdit.setEnabled(true);
}
if (desktop.isSupported(Desktop.Action.PRINT)) {
rbPrint.setEnabled(true);
}

if (rbEdit.isEnabled() || rbOpen.isEnabled() || rbPrint.isEnabled()) {
txtFile.setEnabled(true);
btnLaunchApplication.setEnabled(true);
}
}

Once the application determines the supported actions, it enables the appropriate graphical components. If all components are enabled, the UI should look like Figure 3.

Figure 3: Components are enabled when the Desktop API is supported.
Figure 3: Components are enabled when the Desktop API is supported.

Opening the Browser

Calling the following instance method will open your host's default browser:

    public void browse(URI uri) throws IOException

Because DesktopDemo's UI components are enabled only if the associated Desktop.Action is supported, this simple demo application doesn't need to check support again before actually calling the browse() method. However, checking action support before each invocation may be more robust in real-world applications:

    if (desktop.isSupported(Desktop.Action.BROWSE)) {
// launch browser
...
}

DesktopDemo adds a java.awt.event.ActionListener to each button. When enabled, the Launch Browser button invokes the following method through its ActionListener:

    private void onLaunchBrowser(java.awt.event.ActionEvent evt) {
URI uri = null;
try {
uri = new URI(txtBrowserURI.getText());
desktop.browse(uri);
}
catch(IOException ioe) {
ioe.printStackTrace();
}
catch(URISyntaxException use) {
use.printStackTrace();

}
...
}

The browse() method can throw a variety of exceptions, including a NullPointerException if the URI is null, an UnsupportedOperationException if the BROWSE action is unsupported, an IOException if the default browser or application can't be found or launched, and a SecurityException if a security manager denies the invocation.

If all goes well, however, the listener retrieves the text from the associated text field in Figure 4, creates a URI, and invokes the browse() method. The code above launches the default browser on your system and instructs the browser to load the URI, as Figure 5 shows.

Figure 4: Launch the default browser with a specific URI.
Figure 4: Launch the default browser with a specific URI.

Figure 5: The default browser launches using the Desktop API.
Figure 5: The default browser launches using the Desktop API.

Sending Email

Applications can launch the host's default email client, if that action is supported, by calling this Desktop instance method:

    public void mail(URI uri) throws IOException

DesktopDemo has an ActionListener for the Launch Mail button. In this case, the listener invokes the following method:

    private void onLaunchMail(java.awt.event.ActionEvent evt) {
String mailTo = txtMailTo.getText();
URI uriMailTo = null;
try {
if (mailTo.length() > 0) {
uriMailTo = new URI("mailto", mailTo, null);
desktop.mail(uriMailTo);
} else {
desktop.mail();
}
}
catch(IOException ioe) {
ioe.printStackTrace();
}
catch(URISyntaxException use) {
use.printStackTrace();
}
...
}

The onLaunchMail() method retrieves the email recipient from the associated text field, creates the URI with a mailto scheme argument if a recipient exists, and then invokes the mail() method. The mail() method is overloaded, so you can call it with or without a URI that represents its mailto recipient (see Figure 6).

Figure 6: Launch the default email client with an email recipient.
Figure 6: Launch the default email client with an email recipient.

You can use more than just a single email recipient when creating this URI. The mailto scheme supports CC, BCC, SUBJECT, and BODY fields as well. For example, the following text could be used to create a mailto URI:

    mailto:duke@sun.com?SUBJECT=Happy New Year!&BODY=Happy New Year, Duke!

Figure 7 shows the result.

mailto parameters." border="0" height="415" width="538">
Figure 7: The Desktop API launches the default email client with multiple mailto parameters.

You can, of course, invoke mail() without an argument. In this case, your email client will launch a new email window without specifying a recipient, subject, or body message.

Opening, Editing, and Printing a File

Java applications can open, edit, and print files from their associated application using a Desktop object's open(), edit(), and print() methods, respectively (see Figure 8). Again, DesktopDemo allows these actions only if the Desktop instance supports them, so in this application scenario, it is not necessary to check for support again.

Figure 8: Launch the associated application for a specific file type.
Figure 8: Launch the associated application for a specific file type.

Each of DesktopDemo's radio buttons has its own ActionListener as well. In this case, each sets an instance variable so that it represents the most recently selected button's associated Desktop.Action:

    Desktop.Action action;

private void onPrintAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.PRINT;
}

private void onEditAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.EDIT;
}

private void onOpenAction(java.awt.event.ActionEvent evt) {
action = Desktop.Action.OPEN;
}

When you press the Launch Default Application button, it invokes its own listener, which calls the following method:

    private void onLaunchDefaultApplication(java.awt.event.ActionEvent evt) {
String fileName = txtFile.getText();
File file = new File(fileName);

try {
switch(action) {
case OPEN:
desktop.open(file);
break;
case EDIT:
desktop.edit(file);
break;
case PRINT:
desktop.print(file);
break;
}
}
catch (IOException ioe) {
ioe.printStackTrace();
}
...
}

This method determines which Desktop.Action is selected and invokes the appropriate Desktop instance method, either open(), edit(), or print(). Each method requires a File argument, which will be used to perform the requested action.

Interestingly, different applications may be registered for these different actions even on the same file type. For example, the Firefox browser may be launched for the OPEN action, Emacs for the EDIT action, and yet a different application for the PRINT action. Your host desktop's associations are used to determine what application should be invoked. The ability to manipulate desktop file associations is not possible with the existing Desktop API in Mustang, and those associations can be created or changed only with platform-dependent tools at this time.

Summary

Desktop integration is an important Mustang theme. One way that Mustang supports this theme is through the java.awt.Desktop API. This API allows Java applications to launch the host's default browser and email client. Additionally, Java applications can launch applications associated with specific file types to open, edit, and print files. Although Java applications cannot manipulate, create, or change file associations, the Desktop API does allow Java applications to launch the default associated applications. This article provides a sample application that demonstrates the API, which you can download from this site.

Note: Any API additions or other enhancements to the Java SE platform specification are subject to review and approval by the JSR 270 Expert Group.

For More Information

The following information may help you learn more about Mustang's integration features as well as related topics: