Browser.java Source Code

  • Browser Documentation and Examples
  • Browser Javadoc
    /*
     * Control a web browser from your java application.
     * Copyright (C) 2001-2010 Stephen Ostermiller
     * http://ostermiller.org/contact.pl?regarding=Java+Utilities
     * Copyright (C) 2005 Johann N. Loefflmann <jonelo@jonelo.de>
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation; either version 2 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * See LICENSE.txt for details.
     */
    package com.Ostermiller.util;
    
    import java.awt.*;
    import java.awt.event.*;
    import java.io.*;
    import java.lang.reflect.Method;
    import java.net.*;
    import java.text.MessageFormat;
    import java.util.*;
    import javax.swing.*;
    
    /**
     * Allows URLs to be opened in the system browser on Windows and Unix.
     * More information about this class is available from <a target="_top" href=
     * "http://ostermiller.org/utils/Browser.html">ostermiller.org</a>.
     *
     * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
     * @since ostermillerutils 1.00.00
     */
    public class Browser {
    
    	/**
    	 * The dialog that allows user configuration of the options for this class.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	protected static BrowserDialog dialog;
    
    	/**
    	 * Locale specific strings displayed to the user.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	protected static ResourceBundle labels = ResourceBundle.getBundle("com.Ostermiller.util.Browser",  Locale.getDefault());
    
    	/**
    	 * Set the locale used for getting localized
    	 * strings.
    	 *
    	 * @param locale Locale used to for i18n.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void setLocale(Locale locale){
    		labels = ResourceBundle.getBundle("com.Ostermiller.util.Browser",  locale);
    	}
    
    	/**
    	 * A list of commands to try in order to display the url.
    	 * The url is put into the command using MessageFormat, so
    	 * the URL will be specified as {0} in the command.
    	 * Some examples of commands to try might be:<br>
    	 * <code>rundll32 url.dll,FileProtocolHandler {0}</code></br>
    	 * <code>netscape {0}</code><br>
    	 * These commands are passed in order to exec until something works
    	 * when displayURL is used.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static String[] exec = null;
    
    	/**
    	 * Determine appropriate commands to start a browser on the current
    	 * operating system.  On windows: <br>
    	 * <code>rundll32 url.dll,FileProtocolHandler {0}</code></br>
    	 * On other operating systems, the "which" command is used to
    	 * test if Mozilla, netscape, and lynx(xterm) are available (in that
    	 * order).
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void init(){
    		exec = defaultCommands();
    	}
    
    	/**
    	 * Retrieve the default commands to open a browser for this system.
    	 * @return list of commands
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static String[] defaultCommands(){
    		String[] execLocal = null;
    		if ( System.getProperty("os.name").startsWith("Windows")){
    			execLocal = new String[]{
    				"rundll32 url.dll,FileProtocolHandler {0}",
    			};
    		} else if (System.getProperty("os.name").startsWith("Mac")){
    			ArrayList<String> browsers = new ArrayList<String>();
    			try {
    				Process p = Runtime.getRuntime().exec("which open");
    				if (p.waitFor() == 0){
    					browsers.add("open {0}");
    				}
    			} catch (IOException e){
    				// failure -- nothing added to list
    			} catch (InterruptedException e){
    				// failure -- nothing added to list
    			}
    			if (browsers.size() == 0){
    				execLocal = null;
    			} else {
    				execLocal = browsers.toArray(new String[0]);
    			}
    		} else if (System.getProperty("os.name").startsWith("SunOS")) {
    			execLocal = new String[]{"/usr/dt/bin/sdtwebclient {0}"};
    		} else {
    			ArrayList<String> browsers = new ArrayList<String>();
    			try {
    				Process p = Runtime.getRuntime().exec("which firefox");
    				if (p.waitFor() == 0){
    					browsers.add("firefox -remote openURL({0})");
    					browsers.add("firefox {0}");
    				}
    			} catch (IOException e){
    				// failure -- nothing added to list
    			} catch (InterruptedException e){
    				// failure -- nothing added to list
    			}
    			try {
    				Process p = Runtime.getRuntime().exec("which mozilla");
    				if (p.waitFor() == 0){
    					browsers.add("mozilla -remote openURL({0})");
    					browsers.add("mozilla {0}");
    				}
    			} catch (IOException e){
    				// failure -- nothing added to list
    			} catch (InterruptedException e){
    				// failure -- nothing added to list
    			}
    			try {
    				Process p = Runtime.getRuntime().exec("which opera");
    				if (p.waitFor() == 0){
    					browsers.add("opera -remote openURL({0})");
    					browsers.add("opera {0}");
    				}
    			} catch (IOException e){
    				// failure -- nothing added to list
    			} catch (InterruptedException e){
    				// failure -- nothing added to list
    			}
    			try {
    				Process p = Runtime.getRuntime().exec("which galeon");
    				if (p.waitFor() == 0){
    					browsers.add("galeon {0}");
    				}
    			} catch (IOException e){
    				// failure -- nothing added to list
    			} catch (InterruptedException e){
    				// failure -- nothing added to list
    			}
    			try {
    				Process p = Runtime.getRuntime().exec("which konqueror");
    				if (p.waitFor() == 0){
    					browsers.add("konqueror {0}");
    				}
    			} catch (IOException e){
    				// failure -- nothing added to list
    			} catch (InterruptedException e){
    				// failure -- nothing added to list
    			}
    			try {
    				Process p = Runtime.getRuntime().exec("which netscape");
    				if (p.waitFor() == 0){
    					browsers.add("netscape -remote openURL({0})");
    					browsers.add("netscape {0}");
    				}
    			} catch (IOException e){
    				// failure -- nothing added to list
    			} catch (InterruptedException e){
    				// failure -- nothing added to list
    			}
    			try {
    				Process p = Runtime.getRuntime().exec("which xterm");
    				if (p.waitFor() == 0){
    					p = Runtime.getRuntime().exec("which lynx");
    					if (p.waitFor() == 0){
    						browsers.add("xterm -e lynx {0}");
    					}
    				}
    			} catch (IOException e){
    				// failure -- nothing added to list
    			} catch (InterruptedException e){
    				// failure -- nothing added to list
    			}
    			if (browsers.size() == 0){
    				execLocal = null;
    			} else {
    				execLocal = browsers.toArray(new String[0]);
    			}
    		}
    		return execLocal;
    	}
    
    	/**
    	 * Save the options used to the given properties file.
    	 * Property names used will all start with com.Ostermiller.util.Browser
    	 * Properties are saved in such a way that a call to load(props); will
    	 * restore the state of this class.
    	 * If the default commands to open a browser are being used then
    	 * they are not saved in the properties file, assuming that the user
    	 * will want to use the defaults next time even if the defaults change.
    	 *
    	 * @param props properties file to which configuration is saved.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void save(Properties props){
    		boolean saveBrowser = false;
    		if (Browser.exec != null && Browser.exec.length > 0){
    			String[] execLocal = Browser.defaultCommands();
    			if (execLocal != null && execLocal.length == Browser.exec.length){
    				for (int i=0; i<execLocal.length; i++){
    					if (!execLocal[i].equals(Browser.exec[i])){
    						saveBrowser = true;
    					}
    				}
    			} else {
    				saveBrowser = true;
    			}
    		}
    		if (saveBrowser){
    			StringBuffer sb = new StringBuffer();
    			for (int i=0; Browser.exec != null && i < Browser.exec.length; i++){
    				sb.append(Browser.exec[i]).append('\n');
    			}
    			props.put("com.Ostermiller.util.Browser.open", sb.toString());
    		} else {
    			props.remove("com.Ostermiller.util.Browser.open");
    		}
    	}
    
    	/**
    	 * Load the options for this class from the given properties file.
    	 * This method is designed to work with the save(props) method.  All
    	 * properties used will start with com.Ostermiller.util.Browser.  If
    	 * no configuration is found, the default configuration will be used.
    	 * If this method is used, a call to Browser.init(); is not needed.
    	 *
    	 * @param props properties file from which configuration is loaded.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void load(Properties props){
    		if (props.containsKey("com.Ostermiller.util.Browser.open")){
    			java.util.StringTokenizer tok = new java.util.StringTokenizer(
    				props.getProperty("com.Ostermiller.util.Browser.open"),
    				"\r\n",
    				false
    			);
    			int count = tok.countTokens();
    			String[] exec = new String[count];
    			for (int i=0; i < count; i++){
    				exec[i] = tok.nextToken();
    			}
    			Browser.exec = exec;
    		} else {
    			Browser.init();
    		}
    	}
    
    	/**
    	 * Display a URL in the system browser.
    	 *
    	 * Browser.init() should be called before calling this function or
    	 * Browser.exec should be set explicitly.
    	 *
    	 * For security reasons, the URL will may not be passed directly to the
    	 * browser as it is passed to this method.  The URL may be made safe for
    	 * the exec command by URLEncoding the URL before passing it.
    	 *
    	 * @param url the url to display
    	 * @throws IOException if the url is not valid or the browser fails to star
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void displayURL(String url) throws IOException {
    		if (exec == null || exec.length == 0){
    			if (System.getProperty("os.name").startsWith("Mac")){
    				boolean success = false;
    				try {
    					Class<?> nSWorkspace;
    						if (new File("/System/Library/Java/com/apple/cocoa/application/NSWorkspace.class").exists()){
    							 // Mac OS X has NSWorkspace, but it is not in the classpath, add it.
    							 ClassLoader classLoader = new URLClassLoader(new URL[]{new File("/System/Library/Java").toURL()});
    							 nSWorkspace = Class.forName("com.apple.cocoa.application.NSWorkspace", true, classLoader);
    						} else {
    							 nSWorkspace = Class.forName("com.apple.cocoa.application.NSWorkspace");
    						}
    						Method sharedWorkspace = nSWorkspace.getMethod("sharedWorkspace", new Class[] {});
    						Object workspace = sharedWorkspace.invoke(null, new Object[] {});
    						Method openURL = nSWorkspace.getMethod("openURL", new Class[] {Class.forName("java.net.URL")});
    						success = ((Boolean)openURL.invoke(workspace, new Object[] {new java.net.URL(url)})).booleanValue();
    					//success = com.apple.cocoa.application.NSWorkspace.sharedWorkspace().openURL(new java.net.URL(url));
    				} catch (Exception x) {
    					success = false;
    				}
    				if (!success){
    					try {
    						 Class<?> mrjFileUtils = Class.forName("com.apple.mrj.MRJFileUtils");
    						 Method openURL = mrjFileUtils.getMethod("openURL", new Class[] {Class.forName("java.lang.String")});
    						 openURL.invoke(null, new Object[] {url});
    						 //com.apple.mrj.MRJFileUtils.openURL(url);
    					} catch (Exception x){
    						 System.err.println(x.getMessage());
    						 throw new IOException(labels.getString("failed"));
    					}
    				}
    			} else {
    				throw new IOException(labels.getString("no"+"command"));
    			}
    		} else {
    			// for security, see if the url is valid.
    			// this is primarily to catch an attack in which the url
    			// starts with a - to fool the command line flags, but
    			// it could catch other stuff as well, and will throw a
    			// MalformedURLException which will give the caller of this
    			// function useful information.
    			new URL(url);
    			// escape any weird characters in the url.  This is primarily
    			// to prevent an attacker from putting in spaces
    			// that might fool exec into allowing
    			// the attacker to execute arbitrary code.
    			StringBuffer sb = new StringBuffer(url.length());
    			for (int i=0; i<url.length(); i++){
    				char c = url.charAt(i);
    				if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
    						|| c == '.' || c == ':' || c == '&' || c == '@' || c == '/' || c == '?'
    						|| c == '%' || c =='+' || c == '=' || c == '#' || c == '-' || c == '\\'){
    					//characters that are necessary for URLs and should be safe
    					//to pass to exec.  Exec uses a default string tokenizer with
    					//the default arguments (white space) to separate command line
    					//arguments, so there should be no problem with anything but
    					//white space.
    					sb.append(c);
    				} else {
    					c = (char)(c & 0xFF); // get the lowest 8 bits (URLEncoding)
    					if (c < 0x10){
    						sb.append("%0" + Integer.toHexString(c));
    					} else {
    						sb.append("%" + Integer.toHexString(c));
    					}
    				}
    			}
    			String[] messageArray = new String[1];
    			messageArray[0] = sb.toString();
    			String command = null;
    			boolean found = false;
    			// try each of the exec commands until something works
    			try {
    				for (int i=0; i<exec.length && !found; i++){
    					try {
    						// stick the url into the command
    						command = MessageFormat.format(exec[i], (Object[])messageArray);
    						// parse the command line.
    						ArrayList<String> argumentList = new ArrayList<String>();
    						BrowserCommandLexer lex = new BrowserCommandLexer(new StringReader(command));
    						String t;
    						while ((t = lex.getNextToken()) != null) {
    							argumentList.add(t);
    						}
    						String[] args = new String[argumentList.size()];
    						args = argumentList.toArray(args);
    						// the windows url protocol handler doesn't work well with file URLs.
    						// Correct those problems here before continuing
    						// Java File.toURL() gives only one / following file: but
    						// we need two.
    						// If there are escaped characters in the url, we will have
    						// to create an Internet shortcut and open that, as the command
    						// line version of the rundll doesn't like them.
    						boolean useShortCut = false;
    						if (args[0].equals("rundll32") && args[1].equals("url.dll,FileProtocolHandler")){
    							if (args[2].startsWith("file:/")){
    								if (args[2].charAt(6) != '/'){
    									args[2] = "file://" + args[2].substring(6);
    								}
    								if (args[2].charAt(7) != '/'){
    									args[2] = "file:///" + args[2].substring(7);
    								}
    								useShortCut = true;
    							} else if (args[2].toLowerCase().endsWith("html") || args[2].toLowerCase().endsWith("htm")){
    								useShortCut = true;
    							}
    						}
    						if (useShortCut){
    							File shortcut = File.createTempFile("OpenInBrowser", ".url");
    							shortcut = shortcut.getCanonicalFile();
    							shortcut.deleteOnExit();
    							PrintWriter out = new PrintWriter(new FileWriter(shortcut));
    							out.println("[InternetShortcut]");
    							out.println("URL=" + args[2]);
    							out.close();
    							args[2] = shortcut.getCanonicalPath();
    						}
    						// start the browser
    						Process p = Runtime.getRuntime().exec(args);
    
    						// give the browser a bit of time to fail.
    						// I have found that sometimes sleep doesn't work
    						// the first time, so do it twice.  My tests
    						// seem to show that 1000 milliseconds is enough
    						// time for the browsers I'm using.
    						for (int j=0; j<2; j++){
    							 try {
    								Thread.sleep(1000);
    							 } catch (InterruptedException ix){
    								 throw new RuntimeException(ix);
    							 }
    						}
    						if (p.exitValue() == 0){
    							 // this is a weird case.  The browser exited after
    							 // a couple seconds saying that it successfully
    							 // displayed the url.  Either the browser is lying
    							 // or the user closed it *really* quickly.  Oh well.
    							 found = true;
    						}
    					} catch (IOException x){
    						// the command was not a valid command.
    						System.err.println(labels.getString("warning") + " " + x.getMessage());
    					}
    				}
    				if (!found){
    					// we never found a command that didn't terminate with an error.
    					throw new IOException(labels.getString("failed"));
    				}
    			} catch (IllegalThreadStateException e){
    				// the browser is still running.  This is a good sign.
    				// lets just say that it is displaying the url right now!
    			}
    		}
    	}
    
    	/**
    	 * Display the URLs, each in their own window, in the system browser.
    	 *
    	 * Browser.init() should be called before calling this function or
    	 * Browser.exec should be set explicitly.
    	 *
    	 * If more than one URL is given an HTML page containing JavaScript will
    	 * be written to the local drive, that page will be opened, and it will
    	 * open the rest of the URLs.
    	 *
    	 * @param urls the list of urls to display
    	 * @throws IOException if the url is not valid or the browser fails to star
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void displayURLs(String[] urls) throws IOException {
    		if (urls == null || urls.length == 0){
    			return;
    		}
    		if (urls.length == 1){
    			displayURL(urls[0]);
    			return;
    		}
    		File shortcut = File.createTempFile("DisplayURLs", ".html");
    		shortcut = shortcut.getCanonicalFile();
    		shortcut.deleteOnExit();
    		PrintWriter out = new PrintWriter(new FileWriter(shortcut));
    		out.println("<!-- saved from url=(0014)about:internet -->");
    		out.println("<html>");
    		out.println("<head>");
    		out.println("<title>" + labels.getString("html.openurls") + "</title>");
    		out.println("<script language=\"javascript\" type=\"text/javascript\">");
    		out.println("function displayURLs(){");
    		for (int i=1; i<urls.length; i++){
    			out.println("window.open(\"" + urls[i] + "\", \"_blank\", \"toolbar=yes,location=yes,directories=yes,status=yes,menubar=yes,scrollbars=yes,resizable=yes\");");
    		}
    		out.println("location.href=\"" + urls[0] + "\";");
    		out.println("}");
    		out.println("</script>");
    		out.println("</head>");
    		out.println("<body onload=\"javascript:displayURLs()\">");
    		out.println("<noscript>");
    		for (String element: urls) {
    			out.println("<a target=\"_blank\" href=\"" + element + "\">" + element + "</a><br>");
    		}
    		out.println("</noscript>");
    		out.println("</body>");
    		out.println("</html>");
    		out.close();
    		displayURL(shortcut.toURL().toString());
    	}
    
    	/**
    	 * Display the URL in a new window.
    	 *
    	 * Uses javascript to check history.length to determine if the browser opened a
    	 * new window already.  If it did, the url is shown in that window, if not, it is
    	 * shown in new window.
    	 *
    	 * Some browsers do not allow the length of history to be viewed by a web page.  In that
    	 * case, the url will be displayed in the current window.
    	 *
    	 * Browser.init() should be called before calling this function or
    	 * Browser.exec should be set explicitly.
    	 *
    	 * @param url the url to display in a new window.
    	 * @throws IOException if the url is not valid or the browser fails to star
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void displayURLinNew(String url) throws IOException {
    		displayURLsinNew (new String[] {url});
    	}
    
    	/**
    	 * Display the URLs, each in their own window, in the system browser and the first in
    	 * the named window.
    	 *
    	 * The first URL will only be opened in the named window if the browser did no
    	 * open it in a new window to begin with.
    	 *
    	 * Browser.init() should be called before calling this function or
    	 * Browser.exec should be set explicitly.
    	 *
    	 * An html page containing javascript will
    	 * be written to the local drive, that page will be opened, and it will
    	 * open all the urls.
    	 *
    	 * @param urls the list of urls to display
    	 * @throws IOException if the url is not valid or the browser fails to star
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void displayURLsinNew(String[] urls) throws IOException {
    		if (urls == null || urls.length == 0){
    			return;
    		}
    		File shortcut = File.createTempFile("DisplayURLs", ".html");
    		shortcut.deleteOnExit();
    		shortcut = shortcut.getCanonicalFile();
    		PrintWriter out = new PrintWriter(new FileWriter(shortcut));
    		out.println("<!-- saved from url=(0014)about:internet -->");
    		out.println("<html>");
    		out.println("<head>");
    		out.println("<title>" + labels.getString("html.openurls") + "</title>");
    		out.println("<script language=\"javascript\" type=\"text/javascript\">");
    		out.println("function displayURLs(){");
    		out.println("var hlength = 0;");
    		out.println("try {");
    		out.println("hlength = history.length;");
    		out.println("} catch (e) {}");
    		out.println("if (hlength>0) {");
    		out.println("window.open(\"" + urls[0] + "\", \"_blank\", \"toolbar=yes,location=yes,directories=yes,status=yes,menubar=yes,scrollbars=yes,resizable=yes\");");
    		out.println("}");
    		for (int i=1; i<urls.length; i++){
    			out.println("window.open(\"" + urls[i] + "\", \"_blank\", \"toolbar=yes,location=yes,directories=yes,status=yes,menubar=yes,scrollbars=yes,resizable=yes\");");
    		}
    		out.println("if (hlength==0) {");
    		out.println("location.href=\"" + urls[0] + "\";");
    		out.println("} else {");
    		out.println("history.back()");
    		out.println("}");
    		out.println("}");
    		out.println("</script>");
    		out.println("</head>");
    		out.println("<body onload=\"javascript:displayURLs()\">");
    		out.println("<noscript>");
    		for (String element: urls) {
    			out.println("<a target=\"_blank\" href=\"" + element + "\">" + element + "</a><br>");
    		}
    		out.println("</noscript>");
    		out.println("</body>");
    		out.println("</html>");
    		out.close();
    		displayURL(shortcut.toURL().toString());
    	}
    
    	/**
    	 * Display the URL in the named window.
    	 *
    	 * If the browser opens a new window by default, this will likely cause a duplicate window
    	 * to be opened.
    	 *
    	 * Browser.init() should be called before calling this function or
    	 * Browser.exec should be set explicitly.
    	 *
    	 * @param url the url to display
    	 * @param namedWindow the name of the desired window.
    	 * @throws IOException if the url is not valid or the browser fails to star
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void displayURL(String url, String namedWindow) throws IOException {
    		displayURLs (new String[] {url}, new String[] {namedWindow});
    	}
    
    	/**
    	 * Display the URLs in the named windows.
    	 *
    	 * If the browser opens a new window by default, this will likely cause a duplicate window
    	 * to be opened.  This method relies on the browser to support javascript.
    	 *
    	 * Browser.init() should be called before calling this function or
    	 * Browser.exec should be set explicitly.
    	 *
    	 * Extra names for windows will be ignored, and if there are too few names, the remaining
    	 * windows will be named "_blank".
    	 *
    	 * @param urls the list of urls to display
    	 * @param namedWindows the list of names for the windows.
    	 * @throws IOException if the url is not valid or the browser fails to star
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void displayURLs(String[] urls, String[] namedWindows) throws IOException {
    		if (urls == null || urls.length == 0){
    			return;
    		}
    		File shortcut = File.createTempFile("DisplayURLs", ".html");
    		shortcut.deleteOnExit();
    		shortcut = shortcut.getCanonicalFile();
    		PrintWriter out = new PrintWriter(new FileWriter(shortcut));
    		out.println("<!-- saved from url=(0014)about:internet -->");
    		out.println("<html>");
    		out.println("<head>");
    		out.println("<title>" + labels.getString("html.openurls") + "</title>");
    		out.println("<base target=\"" + ((namedWindows==null||namedWindows.length==0||namedWindows[0]==null)?"_blank":namedWindows[0]) + "\">");
    		out.println("<script language=\"javascript\" type=\"text/javascript\">");
    		for (int i=1; i<urls.length; i++){
    			out.println("window.open(\"" + urls[i] + "\", \"" + ((namedWindows==null||namedWindows.length<=i||namedWindows[i]==null)?"_blank":namedWindows[i]) + "\", \"toolbar=yes,location=yes,directories=yes,status=yes,menubar=yes,scrollbars=yes,resizable=yes\");");
    		}
    		out.println("location.href=\"" + urls[0] + "\";");
    		out.println("</script>");
    		out.println("</head>");
    		out.println("<body onload=\"javascript:displayURLs()\">");
    		out.println("<noscript>");
    		for (String element: urls) {
    			out.println("<a target=\"" + ((namedWindows==null||namedWindows.length==0||namedWindows[0]==null)?"_blank":namedWindows[0]) + "\" href=\"" + element + "\">" + element + "</a><br>");
    		}
    		out.println("</noscript>");
    		out.println("</body>");
    		out.println("</html>");
    		out.close();
    		displayURL(shortcut.toURL().toString());
    	}
    
    	/**
    	 * Display the URLs the first in the given named window.
    	 *
    	 * If the browser opens a new window by default, this will likely cause a duplicate window
    	 * to be opened.  This method relies on the browser to support javascript.
    	 *
    	 * Browser.init() should be called before calling this function or
    	 * Browser.exec should be set explicitly.
    	 *
    	 * @param urls the list of urls to display
    	 * @param namedWindow the name of the first window to use.
    	 * @throws IOException if the url is not valid or the browser fails to star
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static void displayURLs(String[] urls, String namedWindow) throws IOException {
    		displayURLs(urls, new String[] {namedWindow});
    	}
    
    	/**
    	 * Open the url(s) specified on the command line in your browser.
    	 *
    	 * @param args Command line arguments (URLs)
    	 */
    	public static void main(String[] args){
    		try {
    			Browser.init();
    			if (Browser.dialogConfiguration(null)){
    				if (args.length == 0){
    					Browser.displayURLs(new String[]{
    						"http://www.google.com/",
    						"http://dmoz.org/",
    						"http://ostermiller.org",
    					}, "fun");
    				} else if (args.length == 1){
    					Browser.displayURL(args[0], "fun");
    				} else {
    					Browser.displayURLs(args, "fun");
    				}
    			}
    			try {
    				Thread.sleep(10000);
    			} catch (InterruptedException ix){
    				throw new RuntimeException(ix);
    			}
    		} catch (IOException e){
    			System.err.println(e.getMessage());
    		}
    		System.exit(0);
    	}
    
    	/**
    	 * Show a dialog that allows the user to configure the
    	 * command lines used for starting a browser on their system.
    	 *
    	 * @param owner The frame that owns the dialog.
    	 * @return whether or not the dialog has changed
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	public static boolean dialogConfiguration(Frame owner){
    		dialogConfiguration(owner, null);
    		return Browser.dialog.changed();
    	}
    
    	/**
    	 * Show a dialog that allows the user to configure the
    	 * command lines used for starting a browser on their system.
    	 * String used in the dialog are taken from the given
    	 * properties.  This dialog can be customized or displayed in
    	 * multiple languages.
    	 * <P>
    	 * Properties that are used:
    	 * com.Ostermiller.util.BrowserDialog.title<br>
    	 * com.Ostermiller.util.BrowserDialog.description<br>
    	 * com.Ostermiller.util.BrowserDialog.label<br>
    	 * com.Ostermiller.util.BrowserDialog.defaults<br>
    	 * com.Ostermiller.util.BrowserDialog.browse<br>
    	 * com.Ostermiller.util.BrowserDialog.ok<br>
    	 * com.Ostermiller.util.BrowserDialog.cancel<br>
    	 *
    	 * @param owner The frame that owns this dialog.
    	 * @param props contains the strings used in the dialog.
    	 * @return whether or not the dialog has changed
    	 * @deprecated  Use the com.Ostermiller.util.Browser resource bundle to set strings for the given locale.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	@Deprecated public static boolean dialogConfiguration(Frame owner, Properties props){
    		if (Browser.dialog == null){
    			Browser.dialog = new BrowserDialog(owner);
    		}
    		if (props != null){
    			Browser.dialog.setProps(props);
    		}
    		Browser.dialog.show();
    		return Browser.dialog.changed();
    	}
    
    	/**
    	 * Where the command lines are typed.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	private static JTextArea description;
    
    	/**
    	 * Where the command lines are typed.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	private static JTextArea commandLinesArea;
    
    	/**
    	 * The reset button.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	private static JButton resetButton;
    
    	/**
    	 * The browse button.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	private static JButton browseButton;
    
    	/**
    	 * The label for the field in which the name is typed.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	private static JLabel commandLinesLabel;
    
    	/**
    	 * File dialog for choosing a browser
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	private static JFileChooser fileChooser;
    
    	/**
    	 * A panel used in the options dialog.  Null until getDialogPanel() is called.
    	 */
    	private static JPanel dialogPanel = null;
    	private static Window dialogParent = null;
    
    	/**
    	 * If you wish to add to your own dialog box rather than have a separate
    	 * one just for the browser, use this method to get a JPanel that can
    	 * be added to your own dialog.
    	 *
    	 * mydialog.add(Browser.getDialogPanel(mydialog));
    	 * Browser.initPanel();
    	 * mydialog.show();
    	 * if (ok_pressed){
    	 * &nbsp;&nbsp;Browser.userOKedPanelChanges();
    	 * }
    	 *
    	 * @param parent window into which panel with eventually be placed.
    	 * @return the dialog
    	 * @since ostermillerutils 1.02.22
    	 */
    	public static JPanel getDialogPanel(Window parent){
    		dialogParent = parent;
    		if (dialogPanel == null){
    			commandLinesArea = new JTextArea("", 8, 40);
    			JScrollPane scrollpane = new JScrollPane(commandLinesArea);
    			resetButton = new JButton(labels.getString("dialog.reset"));
    			browseButton = new JButton(labels.getString("dialog.browse"));
    			commandLinesLabel = new JLabel(labels.getString("dialog.commandLines"));
    			description = new JTextArea(labels.getString("dialog.description"));
    			description.setEditable(false);
    			description.setOpaque( false );
    
    			ActionListener actionListener = new ActionListener() {
    				public void actionPerformed(ActionEvent e){
    					Object source = e.getSource();
    					if (source == resetButton){
    						setCommands(Browser.defaultCommands());
    					} else if (source == browseButton){
    						if (fileChooser == null){
    							fileChooser = new JFileChooser();
    						}
    						if (fileChooser.showOpenDialog(dialogParent) == JFileChooser.APPROVE_OPTION){
    							String app = fileChooser.getSelectedFile().getPath();
    							StringBuffer sb = new StringBuffer(2 * app.length());
    							for (int i=0; i<app.length(); i++){
    								char c = app.charAt(i);
    								// escape these two characters so that we can later parse the stuff
    								if (c == '\"' || c == '\\') {
    									sb.append('\\');
    								}
    								sb.append(c);
    							}
    							app = sb.toString();
    							if (app.indexOf(" ") != -1){
    								app = '"' + app + '"';
    							}
    							String commands = commandLinesArea.getText();
    							if (commands.length() != 0 && !commands.endsWith("\n") && !commands.endsWith("\r")){
    								commands += "\n";
    							}
    							commandLinesArea.setText(commands + app + " {0}");
    						}
    					}
    				}
    			};
    
    			GridBagLayout gridbag = new GridBagLayout();
    			GridBagConstraints c = new GridBagConstraints();
    			c.insets.top = 5;
    			c.insets.bottom = 5;
    			dialogPanel = new JPanel(gridbag);
    			dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 20, 5, 20));
    
    			c.gridwidth = GridBagConstraints.REMAINDER;
    			c.anchor = GridBagConstraints.WEST;
    			gridbag.setConstraints(description, c);
    			dialogPanel.add(description);
    
    			c.gridy = 1;
    			c.gridwidth = GridBagConstraints.RELATIVE;
    			gridbag.setConstraints(commandLinesLabel, c);
    			dialogPanel.add(commandLinesLabel);
    			JPanel buttonPanel = new JPanel();
    			c.anchor = GridBagConstraints.EAST;
    			browseButton.addActionListener(actionListener);
    			buttonPanel.add(browseButton);
    			resetButton.addActionListener(actionListener);
    			buttonPanel.add(resetButton);
    			gridbag.setConstraints(buttonPanel, c);
    			dialogPanel.add(buttonPanel);
    
    			c.gridy = 2;
    			c.gridwidth = GridBagConstraints.REMAINDER;
    			c.anchor = GridBagConstraints.WEST;
    			gridbag.setConstraints(scrollpane, c);
    			dialogPanel.add(scrollpane);
    		}
    		return dialogPanel;
    	}
    
    
    	/**
    	 * A modal dialog that presents configuration option for this class.
    	 *
    	 * @since ostermillerutils 1.00.00
    	 */
    	private static class BrowserDialog extends JDialog {
    
    		/**
    		 * Serial version ID
    		 */
    		private static final long serialVersionUID = -6021583796243635266L;
    
    		/**
    		 * The OK button.
    		 *
    		 * @since ostermillerutils 1.00.00
    		 */
    		private JButton okButton;
    
    		/**
    		 * The cancel button.
    		 *
    		 * @since ostermillerutils 1.00.00
    		 */
    		private JButton cancelButton;
    
    		/**
    		 * The label for the field in which the name is typed.
    		 *
    		 * @since ostermillerutils 1.00.00
    		 */
    		private JLabel commandLinesLabel;
    
    		/**
    		 * update this variable when the user makes an action
    		 *
    		 * @since ostermillerutils 1.00.00
    		 */
    		private boolean pressed_OK = false;
    
    
    		/**
    		 * Properties that are used:
    		 * com.Ostermiller.util.BrowserDialog.title<br>
    		 * com.Ostermiller.util.BrowserDialog.description<br>
    		 * com.Ostermiller.util.BrowserDialog.label<br>
    		 * com.Ostermiller.util.BrowserDialog.defaults<br>
    		 * com.Ostermiller.util.BrowserDialog.browse<br>
    		 * com.Ostermiller.util.BrowserDialog.ok<br>
    		 * com.Ostermiller.util.BrowserDialog.cancel<br>
    		 *
    		 * @deprecated  Use the com.Ostermiller.util.Browser resource bundle to set strings for the given locale.
    		 * @since ostermillerutils 1.00.00
    		 */
    		@Deprecated private void setProps(Properties props){
    			if (props.containsKey("com.Ostermiller.util.BrowserDialog.title")){
    				setTitle(props.getProperty("com.Ostermiller.util.BrowserDialog.title"));
    			}
    			if (props.containsKey("com.Ostermiller.util.BrowserDialog.description")){
    				description.setText(props.getProperty("com.Ostermiller.util.BrowserDialog.description"));
    			}
    			if (props.containsKey("com.Ostermiller.util.BrowserDialog.label")){
    				commandLinesLabel.setText(props.getProperty("com.Ostermiller.util.BrowserDialog.label"));
    			}
    			if (props.containsKey("com.Ostermiller.util.BrowserDialog.defaults")){
    				resetButton.setText(props.getProperty("com.Ostermiller.util.BrowserDialog.defaults"));
    			}
    			if (props.containsKey("com.Ostermiller.util.BrowserDialog.browse")){
    				browseButton.setText(props.getProperty("com.Ostermiller.util.BrowserDialog.browse"));
    			}
    			if (props.containsKey("com.Ostermiller.util.BrowserDialog.ok")){
    				okButton.setText(props.getProperty("com.Ostermiller.util.BrowserDialog.ok"));
    			}
    			if (props.containsKey("com.Ostermiller.util.BrowserDialog.cancel")){
    				cancelButton.setText(props.getProperty("com.Ostermiller.util.BrowserDialog.cancel"));
    			}
    			pack();
    		}
    
    		/**
    		 * Whether the user pressed the applied changes.
    		 * true if OK was pressed or the user otherwise applied new changes,
    		 * false if cancel was pressed or dialog was closed with no changes.
    		 * If called before the dialog is displayed and closed, the results
    		 * are not defined.
    		 *
    		 * @return if the user made changes to the browser configuration.
    		 *
    		 * @since ostermillerutils 1.00.00
    		 */
    		public boolean changed() {
    			return pressed_OK;
    		}
    
    		/**
    		 * Create this dialog with the given parent and title.
    		 *
    		 * @param parent window from which this dialog is launched
    		 * @since ostermillerutils 1.00.00
    		 */
    		public BrowserDialog(Frame parent) {
    			super(parent, labels.getString("dialog.title"), true);
    			setLocationRelativeTo(parent);
    			// super calls dialogInit, so we don't need to do it again.
    		}
    
    		/**
    		 * Called by constructors to initialize the dialog.
    		 *
    		 * @since ostermillerutils 1.00.00
    		 */
    		@Override protected void dialogInit(){
    
    			super.dialogInit();
    
    			getContentPane().setLayout(new BorderLayout());
    
    			getContentPane().add(getDialogPanel(this), BorderLayout.CENTER);
    
    			JPanel panel = new JPanel(new FlowLayout());
    			okButton = new JButton(labels.getString("dialog.ok"));
    			okButton.addActionListener(new ActionListener() {
    				public void actionPerformed(ActionEvent e){
    					pressed_OK = true;
    					BrowserDialog.this.setVisible(false);
    				}
    			});
    			panel.add(okButton);
    			cancelButton = new JButton(labels.getString("dialog.cancel"));
    			cancelButton.addActionListener(new ActionListener() {
    				public void actionPerformed(ActionEvent e){
    					pressed_OK = false;
    					BrowserDialog.this.setVisible(false);
    				}
    			});
    			panel.add(cancelButton);
    
    			getContentPane().add(panel, BorderLayout.SOUTH);
    
    			pack();
    		}
    
    		/**
    		 * Shows the dialog.
    		 *
    		 * @since ostermillerutils 1.00.00
    		 * @deprecated use setVisible(true);
    		 */
    		@Override @Deprecated public void show(){
    			setVisible(true);
    		}
    
    		/**
    		 * @see java.awt.Component#setVisible(boolean)
    		 */
    		@Override public void setVisible(boolean visible){
    			if (visible){
    				initPanel();
    				super.setVisible(true);
    				if (pressed_OK){
    					userOKedPanelChanges();
    				}
    			} else {
    				super.setVisible(false);
    			}
    		}
    
    
    	}
    
    	private static void setCommands(String[] newExec){
    		StringBuffer sb = new StringBuffer();
    		for (int i=0; newExec != null && i < newExec.length; i++){
    			sb.append(newExec[i]).append('\n');
    		}
    		commandLinesArea.setText(sb.toString());
    	}
    
    	/**
    	 * If you are using the getDialogPanel() method to create your own dialog, this
    	 * method should be called every time before you display the dialog.
    	 *
    	 * mydialog.add(Browser.getDialogPanel(mydialog));
    	 * Browser.initPanel();
    	 * mydialog.show();
    	 * if (ok_pressed){
    	 * &nbsp;&nbsp;Browser.userOKedPanelChanges();
    	 * }
    	 *
    	 * @since ostermillerutils 1.02.22
    	 */
    	public static void initPanel(){
    		setCommands(exec);
    	}
    
    	/**
    	 * If you are using the getDialogPanel() method to create your own dialog, this
    	 * method should be called after you display the dialog if the user pressed ok.
    	 *
    	 * mydialog.add(Browser.getDialogPanel(mydialog));
    	 * Browser.initPanel();
    	 * mydialog.show();
    	 * if (ok_pressed){
    	 * &nbsp;&nbsp;Browser.userOKedPanelChanges();
    	 * }
    	 *
    	 * @since ostermillerutils 1.02.22
    	 */
    	public static void userOKedPanelChanges(){
    		java.util.StringTokenizer tok = new java.util.StringTokenizer(commandLinesArea.getText(), "\r\n", false);
    		int count = tok.countTokens();
    		String[] exec = new String[count];
    		for (int i=0; i < count; i++){
    			exec[i] = tok.nextToken();
    		}
    		Browser.exec = exec;
    	}
    }