Forget browsers and the appletviewer. Test and debug your applets as applications O.K. You’re past the Hello World applet and on to something much bigger, much more interesting. You still need a browser-based interface, so you’re going to develop your program as an applet. But debugging the applet by inserting printlns in Netscape is a bear, and the appletviewer never seems to work right anymore. Or maybe you’re writing a program that would be useful as both an applet and as a standalone application. You could insert the main function in your Applet subclass, and add code to handle command-line arguments, window manipulation, and image loading yourself, now that the browser’s AppletContext is no longer there for you.AppletContext is an interface, so you can’t even instantiate an AppletContext object to provide the functions that the browser’s AppletContext normally provides. But you could implement the interface. And if you implemented it in a very generic fashion, you could put it away in your own toolchest to reuse over and over. This article shows you how to do just that. In fact, you don’t even have to write the implementation yourself because the source code is included at the end of this article.The class and the interfacesTo accomplish our goal of replicating the browser-based environment, we actually have to implement a few interfaces — in particular, AppletContext and AppletStub. AppletContext is supposed to represent the applet’s environment — normally the browser and enclosing HTML document. The AppletStub is used by the Applet superclass to help implement the applet functions you may call such as getAppletContext() and getParameter(). We’re going to implement one other interface as well: URLStreamHandlerFactory. This will be discussed later. Since we’re only implementing interfaces so far, we still have the option of extending something. The browser provides the window in which the applet is drawn, so we need a Frame object. I’ve created a class I call DummyAppletContext that extends Frame; its definition begins:public class DummyAppletContext extends Frame implements AppletStub, AppletContext, URLStreamHandlerFactory { InitializationI have a couple of ways to instantiate a DummyAppletContext; one of the most useful is directly from a main function (as shown below) in the DummyAppletContext class itself. This way, I don’t have to define main in any applet just to run it as a standalone application. I will be able to run applets as is, through my DummyAppletContext. public static void main ( String args[] ) { new DummyAppletContext( args ); } The new operator above calls the constructor that takes the argument list. I assume that the first argument is the name of the Applet subclass and try to instantiate the class. I use the Class static function forName() to get the Class object, and then call its newInstance() function to instantiate the applet. You can get a host of exceptions from this one line, and they are all non-recoverable. So if I catch any exception, I simply print it out and quit. If it works, I call a private initialization function that I use in all of the constructors. Here is the code for the constructor: public DummyAppletContext( String args[] ) { super ( args[0] ); try { Applet applet = (Applet)Class.forName( args[0] ).newInstance(); init( applet, 640, 480, args, 1 ); } catch ( Exception e ) { e.printStackTrace(); System.exit( 1 ); } } One of the other constructors (shown below) takes an existing applet object. I use this constructor when I want to implement the main function in another class, such as the Applet subclass itself. Actually, this is just a convenience. With a main function in the Applet subclass, I can start a program by running the Java interpreter on the Applet subclass, rather than having to run it on DummyAppletContext and specify the Applet subclass separately (java MyApplet versus java DummyAppletContext MyApplet). It also allows me to specify a default width and height in the applet. (I provide one other constructor like this one, that doesn’t require the default width and height arguments.) public DummyAppletContext( Applet applet, int default_width, int default_height, String args[] ) { super ( applet.getClass().getName() ); init( applet, default_width, default_height, args, 0 ); } The init function does most of the setup magic. Its arguments include the applet object, default size, command-line arguments, and the start index for the arguments. Remember, we used the first argument in one of the constructors to determine the Applet subclass to load, by its name only. In that case, startidx — the index from which to start parsing the applet’s arguments and parameters — is 1, but otherwise it is 0. The init function first tells the URL class that this object will now be the default URLStreamHandlerFactory. (We are implementing the interface for this.) It then adds the given applet to a Vector of applets that will only contain this one applet, and it tells the applet that this object will act as its AppletStub. Here is the init function: private void init( Applet applet, int default_width, int default_height, String args[], int startidx ) { URL.setURLStreamHandlerFactory( this ); applets.addElement( applet ); applet.setStub(this); initial_width = default_width; initial_height = default_height; parseArgs( args, startidx ); status = new TextField(); status.setEditable( false ); add( "Center", applet ); add( "South", status ); applet.init(); appletResize( initial_width, initial_height ); show(); applet.start(); } The arguments are parsed by simply looping through the array elements and adding every pair of arguments to a hashtable of name/value pairs. The arguments -width and -height are treated specially, and override the default width and height of the applet. They are not added to the hashtable. The argument parsing occurs in the function parseArgs, shown here: public void parseArgs( String args[], int startidx ) { for ( int idx = startidx; idx < ( args.length - startidx ); idx+=2 ) { try { if ( args[idx].equals( "-width" ) ) { initial_width = Integer.parseInt( args[idx+1] ); } else if ( args[idx].equals( "-height" ) ) { initial_height = Integer.parseInt( args[idx+1] ); } else { params.put( args[idx], args[idx+1] ); } } catch ( NumberFormatException nfe ) { System.err.println("Warning: command line argument "+args[idx]+ " is not a valid number." ); } } } The init function continues by setting up the status area (used by the function showStatus) using an uneditable AWT Text object. It adds the applet and status area components to the frame (the DummyAppletContext) according to the default BorderLayout policy, calls the applet’s init function, and resizes the window as specified. Finally, the window is displayed, and the applet’s init and start functions are called. (We never have to call stop, and start is never called again since we’re not in a browser. Also, I have never used the destroy method for anything, so I don’t call it. But if you have a need for it, I would recommend calling it before every System.exit() call, with a test first to see if init() was called.)I only have to override one Frame function, handleEvent(), as shown below, so I can catch the WINDOW_DESTROY event if the user presses the Close icon on the window bar. public boolean handleEvent( Event evt ) { if ( evt.id == Event.WINDOW_DESTROY ) { System.exit(0); } return super.handleEvent(evt); } AppletStubAppletStub declares a few functions that we have to implement:isActive — always returns truegetDocumentBase — returns a “file” URL for the current directorygetCodeBase — returns the same thing that getDocumentBase returnsgetParameter — indexes the hashtable we built in parseArgs and returns the matching value or null if not theregetAppletContext — returns “this” object (our DummyAppletContext)appletResize — attempts to resize the window to accomodate a request to resize the appletMost of these functions are pretty straightforward. However, I did have to do some special things to make getDocumentBase to work the way I wanted it to. I started by creating a reference to a dummy file. Using an object of the File class, I called getAbsolutePath() to get the full path name of the file. For DOS (Windows), I had a file name with a bunch of backslashes in it. My objective was to create a URL, so I had to replace these slashes with forward slashes. Also, the typical browser expects the colon (:) in a DOS filename to be replaced with a vertical bar (|) in the URL. The code below performs a transformation of the dummy file to what appears to be a Netscape-compliant URL. public URL getDocumentBase() { URL url = null; try { File dummy = new File( "dummy.html" ); String path = dummy.getAbsolutePath(); if ( ! File.separator.equals( "/" ) ) { StringBuffer buffer = new StringBuffer(); if ( path.charAt(0) != File.separator.charAt(0) ) { buffer.append( "/" ); } StringTokenizer st = new StringTokenizer( path, File.separator ); while ( st.hasMoreTokens() ) { buffer.append( st.nextToken() + "/" ); } if ( File.separator.equals( "" ) && ( buffer.charAt(2) == ':' ) ) { buffer.setCharAt( 2, '|' ); } else { } path = buffer.toString(); path = path.substring( 0, path.length()-1 ); } url = new URL( "file", "", -1, path ); } catch ( MalformedURLException mue ) { mue.printStackTrace(); } return url; } The only other AppletStub function implementation of note is appletResize(). In this function, I not only found that I needed to take into account the size of the status text box, but I also had to accomodate the window decorations (for example, the title bar). Java provides the function needed to get that information in Frame’s insets() function. Here is the appletResize function: public void appletResize( int width, int height ) { Insets insets = insets(); resize( ( width + insets.left + insets.right ), ( height + status.preferredSize().height + insets.top + insets.bottom ) ); } AppletContextThe functions required to implement AppletContext include: getAudioClip — returns null, because there doesn’t seem to be a toolkit for audio clips in my JDK. (You could handle this differently, returning your own implementation of AudioClip.)getImage — gets an image from the given URL. For the purposes of the DummyAppletContext, all URLs are assumed to be references to a local file. Therefore getImage converts the URL to a file name, and uses the AWT Toolkit object to load the image.getApplet — is supposed to return an applet by name. I never name my applet, and there are no other applets, so this always returns null.getApplets — returns an Enumeration of the applets in this AppletContext. There is only one, so this returns an Enumeration of one element. The Enumeration is created from the Vector we filled in the init function.showDocument — There are two variations of this function, neither one of which actually shows a document. In a browser, showDocument requests that a document at the given URL be loaded. I actually do show this request in the status area, but I don’t attempt to retrieve or show the document.showStatus — writes the given text to the Text object used as the status area.The getImage() function uses a private function filenameFromURL() to convert the URL back to a legal file name for the current operating system. Again, I have to make special provisions for DOS, taking into account variations I have seen from time to time. In particular, I have to convert the URL’s vertical bar back to a colon. private String filenameFromURL( URL url ) { String filename = url.getFile(); if ( filename.charAt(1) == '|' ) { StringBuffer buf = new StringBuffer( filename ); buf.setCharAt( 1, ':' ); filename = buf.toString(); } else if ( filename.charAt(2) == '|' ) { StringBuffer buf = new StringBuffer( filename ); buf.setCharAt( 2, ':' ); filename = buf.toString(); } return filename; } URLStreamHandlerFactoryURLStreamHandlerFactory has only one function: createURLStreamHandler(). I implement this function in order to cause my implementation of URLStreamHandler to be used whenever the applet tries to open a connection to a URL. Now, when I call openStream() on a URL in my Java application, it actually opens a stream to the local file for input. Here is createURLStreamHandler(), from which I return an instance of my DummyURLStreamHandler : public URLStreamHandler createURLStreamHandler( String protocol ) { return new DummyURLStreamHandler(); } I also define this DummyURLStreamHandler as a non-public class in the same file. It has one function, openConnection, that returns a URLConnection object — in this case, my own version called DummyURLConnection: class DummyURLStreamHandler extends URLStreamHandler { protected final URLConnection openConnection( URL u ) throws IOException { return new DummyURLConnection( u ); } } Again, we have to define our own implementation of the class URLConnection, so the last class defined in DummyAppletContext.java is DummyURLConnection. This class has two significant functions: connect — converts the URL to a legal filename and opens a FileInputStreamgetInputStream — calls connect if not open, and returns the stream public void connect() throws IOException { if ( ! connected ) { String filename = url.getFile(); if ( filename.charAt(1) == '|' ) { StringBuffer buf = new StringBuffer( filename ); buf.setCharAt( 1, ':' ); filename = buf.toString(); } else if ( filename.charAt(2) == '|' ) { StringBuffer buf = new StringBuffer( filename ); buf.setCharAt( 2, ':' ); filename = buf.toString(); } instream = new FileInputStream( filename ); } } public InputStream getInputStream() throws IOException { if ( ! connected ) { connect(); } if ( instream == null ) { throw new IOException(); } return instream; } } Try it out!Here’s the TrackFade applet from the Instant Java book (see Resources below).You need a Java-enabled browser to view this applet.This applet actually has a number of applet parameters you can use to configure everything from the background color or image, to the font size, style, colors, and text position. I’m only showing the bare minimum to simplify this example:<applet code=TrackFade.class width=200 height=80> <param name=TextCount value=1> <param name=text1 value="Great App!"> You can't see my great app! You need a Java-enabled browser. </applet> When running the applet as an application, through the DummyAppletContext, all of these parameters would be entered on the command line as follows: java DummyAppletContext TrackFade -width 200 -height 80 TextCount 1 Text1 "Great App!" I have noticed that the DOS shell, still used by all Windows platforms as far as I can tell, has a very short maximum command-line length, which seems to truncate long argument lists without warning. If you need to overcome this, the only thing I can suggest is that you change the DummyAppletContext to retrieve the parameters from a file, like the appletviewer does from an HTML file, for example.Try it yourself! Download the source for the DummyAppletContext, compile it, put it and your applet in your classpath (such as your current directory), and run it using the Java interpreter. If you want to try this with the TrackFade object, you can download the class files. You will find that this is not just a nifty way to application-enable a simple applet like TrackFade. It also makes debugging your applets much easier by removing the need for a browser or the appletviewer.Rich Kadel is a senior software engineer and the lead engineer in charge of Java development at DTAI Inc., in San Diego, CA. He was the primary developer of PAL++, a large C++ software library with almost 300 C++ classes for building data- and graphically-oriented applications. He currently leads the development of DTAI’s Interactive Org Chart, a Java-based intranet application first developed for Netscape’s AppFoundry program. DTAI is a progressive technology company that develops advanced software products and provides custom computer solutions to a number of market areas. DTAI’s software engineers are experts in object-oriented programming with Java, C++, and C, developing Internet, graphical, and database applications for Unix and Windows. DTAI also performs Internet server administration, Web-page development, and advanced graphic design and production. Java