Java interfacing with Web Browser: Some Details 

Last time we have looked at the methods provided by the AppletContext interface: 

public abstract AudioClip getAudioClip(URL  url) 
loads an audio clip from the web 
public abstract Image getImage(URL  url) 
loads an image from the web 
public abstract void showDocument(URL  url); 
directs web browser to load indicated page from the web 
public abstract void showDocument(URL  url, String  target) 
directs web browser to load indicated page from the web into target window 
public abstract void showStatus(String  status) 
displays a message in the browser's status line 

Today, we want to revisit these methods in some more detail. The easiest method is, without doubt, the 'showStatus' method. The only thing to say about it is that your applet is not the only one using this status line: the web browser itself may display messages there, or other applets might use it. Thus, there is no guaranty that your message will really be observed by the user, because it might be overridden by another message any time. Thus, you should use this area only for informative messages, not for critical ones. Critical messages, or messages that are essential to the user, could be placed, for example, in a status line dedicated to your applet (which means, of course, that you have to program for this feature). 

The next easiest methods are the two 'showDocument' methods: they simply direct the browser to load the indicated pages, if necessary into a target area (which could be a new browser window, or a named frame if it already exists). More information about named targets can be found, for example, in any 'new' book on HTML. The thing that concerns us is the input parameter type URL. To create a URL,  you need to invoke one of the URL constructors. Each one throws an exception which need to be caught in the usual try-catch block. In particular, two of the constructors are perhaps most useful (taken from the Java API): 

public URL(String spec) throws MalformedURLException 

Creates a URL object from the String representation. 

public URL(URL context, String spec) throws MalformedURLException 

Creates a URL by parsing the String specification within a specified context: If the context argument is not null and the spec argument is a partial URL specification, then any of the strings missing components are inherited from the context argument. 

The specification given by the String argument is parsed to determine if it specifies a protocol. If the String contains an ASCII colon ':' character before the first occurrence of of an ASCII slash character '/', then the characters before the colon comprise the protocol. 

If the spec argument does not specify a protocol:
If the context argument isn't null, then the protocol is copied from the context argument.
If the context argument is null, then a MalformedURLException is thrown.
If the spec argument does specify a protocol: 
If the context argument is null, or specifies a different protocol than the specification argument, the context argument is ignored. 


The first constructor is useful to create an 'absolute' URL, i.e. an address which specifies each part, including the protocol, the macine name, and the directory and file names. The second constructor is useful to create 'relative' URL address, i.e. addresses that work relative to the current location of the web page containing the applet, or the machine where the applet originates. For example (try-catch blocks omitted): 

URL url = new URL("http://www.shu.edu/"); 
URL url = new URL("mailto:wachsmut@shu.edu");
URL url = new URL("ftp://anonymous@ftp.shu.edu/"); 
URL url = new URL("telnet:JohnDoe@the.machine.edu:23"); 

are all legal, absolute URL's. Each could be used for the showDocument method. The first one would direct the browser to bring up Seton Hall's homepage, the second one would invoke the mail method of the web browser to allow sending an email message to 'wachsmut@shu.edu', and the third one would tell the browser to open an FTP connection to the machine named 'ftp.shu.edu' and login to that machine using the username 'anonymous'. Finally, the last would invoke the telnet program that has been setup with your web browser, connect to machine 'the.machine.edu' on port 23, and login as JohnDoe. You will have to provide the password as usual for a telnet session. 

The second constructor is perhaps most useful in conjuction with the following methods that are part of the Applet class: 

public URL getCodeBase() 
Returns the URL of this applet. 
public URL getDocumentBase() 
Returns the URL of the document that contains this applet. 

hNote that the URL of the applet does not have to be the same as the URL of the document containing the applet. After all, the APPLET tag allows you to specify a CODEBASE indicating the location of the applet. For example: 

an HTML document has the URL address http://sciris.shu.edu/~wachsmut/test.html and contains the following HTML code: 

<APPLET CODEBASE="http://www.shu.edu/Java/"
        CODE="foo.class"  
        WIDTH=100
        HEIGHT=30>
</APPLET> 

Then getCodeBase() would return "http://www.shu.edu/Java", while getDocumentBase() would return "http://sciris.shu.edu/~wachsmut/". 

Therefore, one could use a URL constructor as follows: 

URL url = new URL(getCodeBase(),"whatever.html"); 
URL url = new URL(getDocumentBase(),"whatever.html"); 

Finally, before we can discuss the remaining two methods for getting images or audio clips, we need to mention some of the security restrictions for applets: 

Security Restrictions for Applets  

Since applets can be embedded in any web page, they could execute on your computer any time you surf the web. Therefore, some security restrictions are placed on applets by the web browser, to stop the applet from doing any harm to your computer. Some of these restrictions are as follows: 

an applet can not close down the web browser it is executing in, hence it can not execute the 'System.exit' method 
an applet is prevented to write anything to the local disk of the computer it is executing on 
an applet can only read documents located in 'web space' 
an applet can only read from the machine where the applet came from 
an applet can only connect to servers on the machine where the applet came from 

Note that these restrictions do not prevent an applet to instruct the browser to load any web page, but it does restrict the applet from loading an image unless the image is located on the same machine as the applet (but in possibly different directories). Actually, that is not so bad, because if your applet relies on an image located elsewhere, your program may not run any longer if the image is removed. 

Standalone programs do not have these security restriction. Therefore, if you need to create a program that saves data to a file, you have two alternatives: 

write a standalone program in Java. Such programs can read and write to the local disk just as any other programs can 
write an applet plus a standalone server. The applet can talk to the server (provided they are located on the same machine) and the server in turn (being a standalone program) can save the data to file. We will explore these details later

Loading Images  

Given the above security restrictions, a Java applet can load images in GIF or JPG format using the 'getImage' method. In fact, that method is pretty slick: it returns immediately, and - if the URL did refer to a valid image - the rest of the program can go on without interruption. For example, if the method 

public void init() 
{ 
   image = getImage(myURL);
   setup(); 
} 

executes, it does not wait until the image is completely retrieved from the URL. Instead, it simply starts loading the image and returns immediately, so that the next method 'setup()' can execute right away. That allows your applet to load the rest of its pieces, if necessary, and to start executing without having to wait for a long file coming from a potentially slow Internet connection. But, when you execute the paint method to draw the image, it may only draw an unfinished picture. Moreover, what if the paint method is drawn before the image has completed loading, but then is not called for a while even though the image is now complete ? To avoid that problem, the 'drawImage' method will inform the "ImageObserver" that the image is now finished, and the "ImageObserver" will then redraw the completed image. Recall the Java API description for the drawImage method: 

public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer) 

Draws the specified image with its top left corner at  in this graphics context's coordinate space. Transparent pixels in the image do not affect whatever pixels are already there. 

If the image has not yet been completely loaded, the image observer's imageUpdate method is notified as more of the image becomes available. 

public abstract boolean drawImage(Image img, int x, int y, 
                                  int width, int height, ImageObserver observer) 
Draws the specified image inside the specified rectangle of this graphics
context's coordinate space. The image is  scaled if necessary. Transparent
pixels in the image do not affect whatever pixels are already there. 



If the image has not yet been completely loaded, the image observer's imageUpdate
method is notified as more of the image becomes available. 
Thus, a typical call to the 'drawImage' method would be: 

public void paint(Graphics g) 
{ 
   g.drawImage(myImage,0,0,this); 
} 
so that the 'current object' is notified when the image has loaded completely. 



However, sometimes you may want wait until the image is entirely
loaded before continuing with the rest of the program. For example, when
doing animation, you do want to make sure all images are entirely loaded
first before starting the animation. Otherwise, the animation may look
very strange with partically loaded images. For that purpose, Java provides
a 'MediaTracker' class. Here's part of what the Java API says:

public  class  java.awt.MediaTracker extends  java.lang.Object
{
        // Fields
    public final static int ABORTED;
    public final static int COMPLETE;
    public final static int ERRORED;
    public final static int LOADING;
        // Constructors
    public MediaTracker(Component  comp); 
        // Methods
    public void addImage(Image  image, int  id);
    public boolean checkAll();
    public Object[] getErrorsAny();
    public void waitForAll();
    public void waitForID(int  id);
}
To use a MediaTracker, first instatiate one and tie it to the current component
using the 'this' keyword. Then add all images that need to be loaded to
the MediaTracker. The images still need to be loaded as usual, using 'getImage',
and they will start loading 'in the background' immediately. But now the
MediaTracker can inform you (or whatever component it was tied to) about
the status of the loading process. You can, for example, use the checkAll() method
to see if all images are done loading or not, or use the getErrorsAny() method
to check for any errors that have occured.  Using the 'waitForAll'
or 'waitForID(int id)' methods will wait until either all images, or an
image with a particular id, have completed loading. This is what's called
a 'blocking' method, meaning that once calls it blocks execution of the
rest of the program until, in this case, the indicated image(s) have finished
loading.



Examples:



import java.applet.*;
import java.awt.*;
import java.net.*;

public class ShowImage extends Applet
{
   private Image image;
   private URL location;
   private String imageName = "test.jpg";

   public void init()
   {
      try
      {
         location = new URL(getDocumentBase(), imageName);
      }
      catch(Exception e)
      {
         System.err.println("Invalid URL for image");
      }
      image = getImage(location);
   }
   public void paint(Graphics g)
   {
      g.drawImage(image,0,0,null); 
   }
}
Assuming the image "test.jpg" does exist in the same directory
as the HTML file containing this applet, you can compile and run it just
fine. But, nothing will appear on the screen until you resize the window.
Explain why.



Now change the paint method as follows:



   public void paint(Graphics g)
   {
      g.drawImage(image,0,0,this); 
   }
The image will now be drawn incrementally and (almost) without delay, especially
if "test.jpg" is a large file. In other words, you will see more
and more of the image, as it is being loaded, until finally the complete
image appears. 



Now change the init method as follows, but keeping the second version
of the paint method (using the this identifier).



   public void init()
   {
      MediaTracker tracker = new MediaTracker(this);
      try
      {
         location = new URL(getDocumentBase(), imageName);
         image = getImage(location); 
         tracker.addImage(image,0);
         tracker.waitForID(0);
      }
      catch(Exception e)
      {
         System.err.println("Invalid URL for image or loading error");
      }
   }
This time there will be a delay (during which you should really use the
showStatus message to keep the user informed), but then the image will
appear all at once, not incrementally as before. Note that the MediaTracker's
addImage and waitForID methods have been placed inside the try-catch block.
Aside from the fact that that is really where they should go logically,
the waitForID method also throws an Exception that must be caught. In the
current implementation, one try-catch block catches both interruptions (from
the URL constructor and the waitForID method).



Loading Sounds



Much of what has been said for loading images also applies to loading sounds.
There are just a few differences, however:

Currently, sound support is tied to applets only. That means that stand-alone programs can not read or play a sound clip (unless they use an additional package available for free from SUN). Images can be used in applets and stand-alone programs.
Sounds can not be added to the current version of the MediaTracker. SUN will hopefully remedy that situation soon. The getAudioClip method, as the getImage method, will return immediately.  It is somewhat unclear to me what exactly happens, when the sound starts loading, whether the sound can be played 'incrementally', or whether a 'play' method will block until the entire sound clip is downloaded, then start playing. Sound support seems to be not as well developed (and documented) as image support so far, sorry.
The only sound format currently supported is 'au' format from SUN. That means that WAV or Mac sounds need to be converted to SUN's 'au' format before they can be used by an applet. I have found that many sound converters promise to create true SUN au files, but often they were not acceptable to an applet. The best sound converter is a Unix program called 'sox'. It is fast, has no graphics interface (hence is fast) and converts pretty much anything into everything.

(bgw)