/*
 * %W% %E%
 * 
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright notice, 
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 * 
 * Neither the name of Oracle or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

package jnlp.sample.JreInstaller;
import java.io.*;
import java.net.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.text.MessageFormat;
import javax.jnlp.DownloadService;
import javax.jnlp.DownloadServiceListener;
import javax.jnlp.ExtensionInstallerService;
import java.security.*;

/**
 *  Main class for JRE installer
 */
public class Main {
    private static final String JAVAWS_MERLIN_KEY = "Software\\JavaSoft\\Java Web Start\\1.0.1_02";
    private static final String JAVAWS_HOPPER_KEY = "Software\\JavaSoft\\Java Web Start\\1.2";
    private static final int BUFFER_SIZE = 32 * 1024;
    
    // Keep track of installer window 
    private static JFrame _installerFrame;
    private static  JLabel[]  _stepLabels = null;
    
    private static final int STEP_LICENSE  = 0;
    private static final int STEP_DOWNLOAD = 1;
    private static final int STEP_UNPACK   = 2;
    private static final int STEP_INSTALL  = 3;
    private static final int STEP_DONE     = 4;
    
    /** Does install of JRE */
    public static void install() {
	
        // Hide the JNLP Clients installer window and show own
        Config.getInstallService().hideStatusWindow();
	showInstallerWindow();
        
	
        // Make sure the destination exists.
        String path = Config.getInstallService().getInstallPath();
	if (Config.isWindowsInstall()) {
	    String defaultLocation = "C:\\Program Files\\Java\\j2re" +
		Config.getJavaVersion() + "\\";
	    File defaultDir = new File(defaultLocation);
            if (!defaultDir.exists()) {
	        defaultDir.mkdirs();
	    }
	    if (defaultDir.exists() && defaultDir.canWrite()) {
		path = defaultLocation; // use default if you can
	    }
	}

        File installDir = new File(path);

        if (!installDir.exists()) {
	    installDir.mkdirs();
	    if (!installDir.exists()) {
		// The installFailed string is only for debugging. No localization needed
		installFailed("couldntCreateDirectory", null);
		return;
	    }
        }
	
	// Show license if neccesary
        enableStep(STEP_LICENSE);        
	if (!showLicensing()) {
	    // The installFailed string is only for debugging. No localization needed
	    installFailed("Licensing was not accepted", null);
	};
	
	// Make sure that the data JAR is downloaded
        enableStep(STEP_DOWNLOAD);
	if (!downloadInstallerComponent()) {
	    // The installFailed string is only for debugging. No localization needed
	    installFailed("Unable to download data component", null);
	}
	
        String nativeLibName = Config.getNativeLibName();
        File installerFile = null;
	
        try {
	    // Load native library into process if found
	    if (nativeLibName != null && !Config.isSolarisInstall()) {
		System.loadLibrary(nativeLibName);
	    }
	    
	    // Unpack installer
	    enableStep(STEP_UNPACK);
	    String installResource = Config.getInstallerResource();
	    Config.trace("Installer resource: " + installResource);
	    installerFile = unpackInstaller(installResource);
	    
	    // To clean-up downloaded files
	    Config.trace("Unpacked installer to: " + installerFile);
	    if (installerFile == null) {
		// The installFailed string is only for debugging. No localization needed
		installFailed("Could not unpack installer components", null);
		return;
	    }
	    
	    enableStep(STEP_INSTALL);
	    setStepText(STEP_INSTALL, Config.getWindowStepWait(STEP_INSTALL));
	    
	    boolean success = false;
	    if (Config.isSolarisInstall()) {
		success = runSolarisInstaller(path, installerFile);
	    } else {
		success = runWindowsInstaller(path, installerFile);
	    }
	    
	    if (!success) {
		// The installFailed string is only for debugging. No localization needed
		installFailed("Could not run installer", null);
		return;
	    }
        } catch(UnsatisfiedLinkError ule) {
	    // The installFailed string is only for debugging. No localization needed
	    installFailed("Unable to load library: " + nativeLibName, null);
	    return;
        } finally {
	    if (installerFile != null) {
		installerFile.delete();
	    }
        }
        
        setStepText(STEP_INSTALL, Config.getWindowStep(STEP_INSTALL));
        enableStep(STEP_DONE);
        
        String execPath = path + Config.getJavaPath();
        Config.trace(execPath);
	
        /** Remove installer JAR from cache */
        removeInstallerComponent();
        
	// If we're running anything after 1.0.1 or not on Windows, just call
	// finishedInstall.  Otherwise, deny ExitVM permission so that we can
	// return here and do a reboot.  We have to do this because we need to
	// call ExtensionInstallerService.finishedInstall(), which registers
	// that our extension (the JRE) is installed.  Unfortunately pre-1.2 it
	// also does not understand that we are requesting a reboot, and calls
	// System.exit().  So for pre 1.2 we want to deny the permission to
	// exit the VM so we can return here and perform a reboot.
	boolean ispre12 = false;
	String version = Config.getJavaWSVersion();
	// get first tuple
	String v = version.substring(version.indexOf('-')+1);
	int i2 = v.indexOf('.');
	int v1 = Integer.parseInt(v.substring(0, i2));
	// get second tuple
	v = v.substring(i2+1);
	i2 = v.indexOf('.');
	if (i2 == -1) i2 = v.indexOf('-');
        if (i2 == -1) i2 = v.indexOf('[');
	if (i2 == -1) i2 = v.length();
	int v2 = Integer.parseInt(v.substring(0,i2));
	// are we pre 1.2?
	if (v1 < 1 || (v1 == 1 && v2 < 2)) ispre12 = true;

	if (Config.isWindowsInstall() && ispre12 && Config.isHopper()) {
	    // deny ExitVM permission then call finishedInstall
	    ProtectionDomain pd = (new Object()).getClass().getProtectionDomain();
	    CodeSource cs = pd.getCodeSource();
	    AllPermissionExceptExitVM perm = new AllPermissionExceptExitVM();
	    PermissionCollection newpc = perm.newPermissionCollection();
	    newpc.add (perm);
		
	    // run finishedInstall within the new context which excluded
	    // just the ExitVM permission
	    ProtectionDomain newpd = new ProtectionDomain(cs, newpc);
	    AccessControlContext newacc =
		new AccessControlContext(new ProtectionDomain[] {newpd});
	    final String fExecPath = execPath;
	    try {
		AccessController.doPrivileged(new PrivilegedExceptionAction() {
		    public Object run() throws SecurityException {
			finishedInstall(fExecPath);
			return null;
		    }
		}, newacc);
	    } catch (PrivilegedActionException pae) {
		// swallow the exception because we want ExitVM to fail silent
	    } catch (SecurityException se) {
		// swallow the exception because we want ExitVM to fail silent
	    }		
	} else {
	    // just call finished Install
	    finishedInstall(execPath);
	}

	if (Config.isWindowsInstall() &&
	    WindowsInstaller.IsRebootNecessary()) {
	    // reboot
	    if (!WindowsInstaller.askUserForReboot()) System.exit(0);
	} else {
	    System.exit(0);
	}
    }

    private static final Permission exitVMPermission = new RuntimePermission("exitVM");
    
    private static final class AllPermissionExceptExitVM extends Permission {
	public AllPermissionExceptExitVM() {
	    super("<all permissions except Exit VM>");
	}

	public AllPermissionExceptExitVM(String name, String actions) {
	    this();
	}

	public boolean implies(Permission p) {
	    if (p instanceof RuntimePermission && 
		p.equals(exitVMPermission)) {
		return false;
	    } else return true;
	}
	
	public boolean equals(Object o) {
	    return (o instanceof AllPermissionExceptExitVM);
	}

	public String getActions() {
	    return new String("<all actions>");
	}

	public int hashCode() {
	    return 1;
	}

	public PermissionCollection newPermissionCollection() {
	    return new AllPermissionExceptExitVMCollection();
	}


    }

    private static final class AllPermissionExceptExitVMCollection
    extends PermissionCollection implements java.io.Serializable 
    {
	private boolean all_allowed; // true if any all permissions have been added

	/**
	 * Create an empty AllPermissions object.
	 *
	 */

	public AllPermissionExceptExitVMCollection() {
	    all_allowed = false;
	}

	/**
	 * Adds a permission to the AllPermissions. The key for the hash is
	 * permission.path.
	 *
	 * @param permission the Permission object to add.
	 *
	 * @exception IllegalArgumentException - if the permission is not a
	 *                                       AllPermission
	 *
	 * @exception SecurityException - if this AllPermissionCollection object
	 *                                has been marked readonly
	 */

	public void add(Permission permission)
	{
	    if (! (permission instanceof AllPermissionExceptExitVM))
		throw new IllegalArgumentException("invalid permission: "+
						   permission);
	    if (isReadOnly())
		throw new SecurityException("attempt to add a Permission to a readonly PermissionCollection");

	    all_allowed = true;
	}

	/**
	 * Check and see if this set of permissions implies the permissions 
	 * expressed in "permission".
	 *
	 * @param p the Permission object to compare
	 *
	 * @return always returns true.
	 */

	public boolean implies(Permission permission) 
	{
	    return ((permission instanceof RuntimePermission &&
		    permission.equals(exitVMPermission)) ?
		    false : all_allowed);
	}

	/**
	 * Returns an enumeration of all the AllPermission objects in the 
	 * container.
	 *
	 * @return an enumeration of all the AllPermission objects.
	 */
	public Enumeration elements()
	{
	    return new Enumeration() {
		private boolean hasMore = all_allowed;

		public boolean hasMoreElements() {
		    return hasMore;
		}

		public Object nextElement() {
		    hasMore = false;
		    return new AllPermissionExceptExitVM();
		}
	    };
	}
    }

    /** Download data component JAR */
    static public boolean downloadInstallerComponent() {
        DownloadService downloadService = Config.getDownloadService();
        DownloadServiceListener listener = downloadService.getDefaultProgressWindow();
        String compName = Config.getInstallerLocation();
        String compVer = Config.getInstallerVersion();	    
        try {
	    URL codebase = Config.getBasicService().getCodeBase();
	    URL url = new URL(codebase, compName);
	    String urlstr = url.toString();
     
	    if (!downloadService.isResourceCached(url, compVer)) {
		// The installFailed string is only for debugging. No localization needed
		Config.trace("Downloading: " + urlstr);
		// Do download
		downloadService.loadResource(url,  compVer, listener);		
	    }		
        } catch(IOException ioe) {
	    Config.trace("Unable to download: " + compName + "/" + compVer);
	    return false;
        }
	return true;
    }
    
    /** Remove data component data JAR from cache */
    static public void removeInstallerComponent() {
	DownloadService downloadService = Config.getDownloadService();
	if (downloadService != null) {
	    String component = Config.getInstallerLocation();
	    String version   = Config.getInstallerVersion();
	    try {
		URL codebase = Config.getBasicService().getCodeBase();
		URL url = new URL(codebase, component);
		component = url.toString();
		Config.trace("Removing: " + component + "/" + version);
		downloadService.removeResource(url, version);
	    } catch(IOException ioe) {
		Config.trace("Unable to remove " + component + "/" + version);
	    }
	} else {
	    Config.trace("No download service found");
	}
    }
    
    /** Runs a Solaris installer */
    static public boolean runSolarisInstaller(String installPath, File installFile) {
	
	/** Build temp. script file */
	File script = null;
	boolean success = false;
	try {
	    script = SolarisInstaller.createTempShellScript();
	    
	    String[] args = new String[3];
	    args[0] = installPath;
	    args[1] = script.getAbsolutePath();
	    args[2] = installFile.getAbsolutePath();
	    String execString = getExecuteString(args);
	    success = SolarisInstaller.execute(execString);
	} catch(IOException ioe) {
	    Config.trace("Got ioe: " + ioe);
	    return false;
	} finally {
	    if (script != null) script.delete();
	}
	return success;
    }
    
    
    /** Runs a Windows installer */
    static public boolean runWindowsInstaller(String installPath, File installFile) {
	boolean deleteHopperKey = false;
	boolean deleteMerlinKey = false;
	// If Hopper, and JavaWS can update, ask the user if they want
	// to update.
	if (Config.isHopper() &&
	    !WinRegistry.doesSubKeyExist(WinRegistry.HKEY_LOCAL_MACHINE,
					 JAVAWS_HOPPER_KEY)) {
	    int res =
		JOptionPane.showConfirmDialog(_installerFrame,
					      Config.getJavaWSConfirmMessage(),
					      Config.getJavaWSConfirmTitle(),
					      JOptionPane.YES_NO_OPTION);
	    if (res == JOptionPane.NO_OPTION) {
		// create the registry key so that JavaWS will not install
		WinRegistry.setStringValue(WinRegistry.HKEY_LOCAL_MACHINE,
					   JAVAWS_HOPPER_KEY,
					   "Home", "");
		// flag to delete the key later
		deleteHopperKey = true;
	    }
	}

	// If Merlin, never update JavaWS.  1.0.1_02 bundled with Merlin does
	// not have the ability to update while JavaWS is running.  So just
	// prevent the update by spoofing the registry key.
	if (Config.isMerlin()) {
	    WinRegistry.setStringValue(WinRegistry.HKEY_LOCAL_MACHINE,
				       JAVAWS_MERLIN_KEY,
				       "Home", "");
	    deleteMerlinKey = true;
	}
	    
	/** Build temp. script file */
	boolean success = false;
	File iss = null;
	try {
	    String[] args = new String[2];
	    args[0] = installFile.getAbsolutePath();
	    if (Config.getJavaVersion().startsWith("1.4.2")) {
	        args[1] = "/s /v\"/qn WEBSTARTICON=1 INSTALLDIR=\\\""+installPath +"\\\"\"";

	    }
	    else { 
		iss = WindowsInstaller.createTempISSScript(
			installPath,
			Config.getJavaVersion());
	        args[1] = iss.getAbsolutePath();
	    }
	    String execString = getExecuteString(args);
	    success = WindowsInstaller.execute(execString);
	} catch(IOException ioe) {
	    return false;
	} finally {
	    if (iss != null) iss.delete();
	}

	// delete any spoofed keys we created earlier
	if (deleteHopperKey) {
	    WinRegistry.deleteKey(WinRegistry.HKEY_LOCAL_MACHINE,
				  JAVAWS_HOPPER_KEY);
	}
	if (deleteMerlinKey) {
	    WinRegistry.deleteKey(WinRegistry.HKEY_LOCAL_MACHINE,
				  JAVAWS_MERLIN_KEY);
	}

	// 4662215 cannot reboot here because the config hasn't been written
	// by JavaWS yet.  Reboot later, after installSucceeded has been
	// called.
	// WindowsInstaller.rebootIfNecessary();
	
	return success;
    }
    
    /**
     * Returns the string to execute, which is determined
     * from the property <code>install.execString</code>. MessageFormat
     * is used to reformat the string. The first argument is the
     * the install path, with the remaining arguments the paths to the
     * extracted files that were downloaded.
     */
    static private String getExecuteString(String[] args) {
	String execString = Config.getExecString();
	if (execString == null) {
	    Config.trace("No exec string specified");
	    return null;
	}
	String apply  = MessageFormat.format(execString, args);
	Config.trace("exec string !" + apply + "!");
	return apply;
    }
    
    /** Unpacks a resource to a temp. file
     */
    static public File unpackInstaller(String resourceName) {
	// Array to hold all results (this code is slightly more
	// generally that it needs to be)
	File[] results = new File[1];
	URL[] urls = new URL[1];
	
	// Determine size of download	
	ClassLoader cl = Main.class.getClassLoader();
	urls[0] = cl.getResource(Config.getInstallerResource());
	if (urls[0] == null) {
	    Config.trace("Could not find resource: " + Config.getInstallerResource());
	    return null;
	}
	
	int totalSize = 0;
	int totalRead = 0;
	for(int i = 0; i < urls.length; i++) {
	    if (urls[i] != null) {
		try {
		    URLConnection connection = urls[i].openConnection();
		    totalSize += connection.getContentLength();
		} catch(IOException ioe) {
		    Config.trace("Got exception: " + ioe);
		    return null;
		}
	    }
	}
	
	// Unpack each file
	for(int i = 0; i < urls.length; i++) {
	    if (urls[i] != null) {
		// Create temp. file to store unpacked file in
		InputStream in = null;
		OutputStream out = null;
		try {
		    // Use extension from URL (important for dll files)
		    String extension = new File(urls[i].getFile()).getName();
		    int lastdotidx = (extension != null) ? extension.lastIndexOf('.') : -1;
		    if (lastdotidx == -1) {
			extension = ".dat";
		    } else {
			extension = extension.substring(lastdotidx);
		    }
		    
		    // Create output stream
		    results[i] = File.createTempFile("jre", extension);
		    results[i].deleteOnExit();
		    out  = new FileOutputStream(results[i]);
		    
		    // Create inputstream
		    URLConnection connection = urls[i].openConnection();
		    in = connection.getInputStream();
		    
		    int read = 0;
		    byte[] buf = new byte[BUFFER_SIZE];
		    while ((read = in.read(buf)) != -1) {
			out.write(buf, 0, read);
			// Notify delegate
			totalRead += read;
			if (totalRead > totalSize && totalSize != 0) totalSize = totalRead;
			
			// Update UI
			if (totalSize != 0) {
			    int percent = (100 * totalRead) / totalSize;
			    setStepText(STEP_UNPACK, 
					Config.getWindowStepProgress(STEP_UNPACK, percent));
			}
		    }
		} catch(IOException ie) {
		    Config.trace("Got exception while downloading resource: " + ie);
		    for(int j = 0; j < results.length; j++) {
			if (results[j] != null) results[j].delete();
		    }
		    return null;
		} finally {
		    try {
			if (in != null)  in.close();
			if (out != null) out.close();
		    } catch(IOException io) { /* ignore */ }
		}
	    }
	}
	
	setStepText(STEP_UNPACK, Config.getWindowStep(STEP_UNPACK));
	return results[0];
    }
    
    /**
     * Invoked when the install finishes.
     */
    public static void finishedInstall(final String execPath) {
	// Use a runnable as more than likely we've queued up a bunch of
	// Runnables
	try {
	    SwingUtilities.invokeAndWait(new Runnable() {
		public void run() {
		    // do nothing
		}
	    });
	} catch(java.lang.reflect.InvocationTargetException ite) {
	    Config.trace("Unexpected exception: " + ite);
	} catch(InterruptedException ie) {
	    Config.trace("Unexpected exception: " + ie);
	}
	String platformVersion = Config.getPlatformVersion();
	Config.getInstallService().setJREInfo(platformVersion, execPath);
	Config.getInstallService().
	    installSucceeded(Config.isWindowsInstall() &&
			     WindowsInstaller.IsRebootNecessary());
    }
    
    /**
     * Invoked when the installer has failed.
     */
    public static void installFailed(final String description, Throwable th) {
	Config.trace("installFailed: " + description + " " + th);
	if (SwingUtilities.isEventDispatchThread()) {
	    Config.getInstallService().installFailed();
	    System.exit(-1);
	} else {
	    try {
		SwingUtilities.invokeAndWait(new Runnable() {
			    public void run() {
				Config.getInstallService().installFailed();
				System.exit(-1);
			    }
			});
	    } catch(java.lang.reflect.InvocationTargetException ite) {
		Config.trace("Unexpected exception: " + ite);
	    } catch(InterruptedException ie) {
		Config.trace("Unexpected exception: " + ie);
	    }
	}
    }
    
    /** Main method. Invokes installer or uninstaller */
    public static void main(String[] args) {
	// Only supports install
	if (args.length > 0 && args[0].equals("install")) {
	    install();
	}
    }
    
    public static boolean showLicensing() {
	if (Config.getLicenseResource() == null) return true;	
	ClassLoader cl = Main.class.getClassLoader();
	URL url = cl.getResource(Config.getLicenseResource());
	if (url == null) return true;
	
	String license = null;
	try {
	    URLConnection con = url.openConnection();
	    int size = con.getContentLength();
	    byte[] content = new byte[size];
	    InputStream in = new BufferedInputStream(con.getInputStream());
	    in.read(content);
	    license = new String(content);
	} catch(IOException ioe) {
	    Config.trace("Got exception when reading " + Config.getLicenseResource() + ": " + ioe);
	    return false;
	}
	
	// Build dialog
	JTextArea ta = new JTextArea(license);
	ta.setEditable(false);
	final JDialog jd = new JDialog(_installerFrame, true);
        Container comp = jd.getContentPane();
        jd.setTitle(Config.getLicenseDialogTitle());
	comp.setLayout(new BorderLayout(10, 10));
	comp.add(new JScrollPane(ta), "Center");
	Box box = new Box(BoxLayout.X_AXIS);
	box.add(box.createHorizontalStrut(10));
	box.add(new JLabel(Config.getLicenseDialogQuestionString()));
	box.add(box.createHorizontalGlue());
	JButton acceptButton = new JButton(Config.getLicenseDialogAcceptString());
	JButton exitButton = new JButton(Config.getLicenseDialogExitString());
	box.add(acceptButton);
	box.add(box.createHorizontalStrut(10));
	box.add(exitButton);
	box.add(box.createHorizontalStrut(10));
	jd.getRootPane().setDefaultButton(acceptButton);
	Box box2 = new Box(BoxLayout.Y_AXIS);
	box2.add(box);
	box2.add(box2.createVerticalStrut(5));
	comp.add(box2, "South");
	jd.pack();
	
	final boolean accept[] = new boolean[1];
	acceptButton.addActionListener(new ActionListener() {
		    public void actionPerformed(ActionEvent e) {
			accept[0] = true;
			jd.hide();
			jd.dispose();
		    }
		});
	
	exitButton.addActionListener(new ActionListener() {
		    public void actionPerformed(ActionEvent e) {
			accept[0] = false;
			jd.hide();
			jd.dispose();
		    }
		});
	
	// Apply any defaults the user may have, constraining to the size
	// of the screen, and default (packed) size.
	Rectangle size =  new Rectangle(0, 0, 500, 300);
	Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
	size.width = Math.min(screenSize.width, size.width);
	size.height = Math.min(screenSize.height, size.height);
	// Center the window
	jd.setBounds((screenSize.width - size.width) / 2,
			 (screenSize.height - size.height) / 2,
		     size.width, size.height);
	
	// Show dialog
	jd.show();
	
	return accept[0];
    }
    
    //
    // Build installer window
    //     
    static public void showInstallerWindow() {
	_installerFrame = new JFrame(Config.getWindowTitle());
	
	Container cont = _installerFrame.getContentPane();
	cont.setLayout(new BorderLayout());
	
	// North pane	
	Box topPane = new Box(BoxLayout.X_AXIS);	
	JLabel title = new JLabel(Config.getWindowHeading());
	Font titleFont  = new Font("SansSerif", Font.BOLD, 22);
	title.setFont(titleFont);
	title.setForeground(Color.black);	
	
        // Create Sun logo
	URL urlLogo = Main.class.getResource(Config.getWindowLogo());
	Image img = Toolkit.getDefaultToolkit().getImage(urlLogo);	
	MediaTracker md = new MediaTracker(_installerFrame);
	md.addImage(img, 0);
	try { md.waitForAll();
	} catch(Exception ioe) { Config.trace(ioe.toString()); }
	if (md.isErrorID(0)) Config.trace("Error loading image");
	Icon sunLogo = new ImageIcon(img);
	JLabel logoLabel = new JLabel(sunLogo);			
	logoLabel.setOpaque(true);		
	topPane.add(topPane.createHorizontalStrut(5));
	topPane.add(title);	
	topPane.add(topPane.createHorizontalGlue());	
	topPane.add(logoLabel);
	topPane.add(topPane.createHorizontalStrut(5));
	
	// West Pane
	Box westPane = new Box(BoxLayout.X_AXIS);	
	westPane.add(westPane.createHorizontalStrut(10));
	
	// South Pane
	Box bottomPane = new Box(BoxLayout.X_AXIS);
	bottomPane.add(bottomPane.createHorizontalGlue());
	JButton abortButton = new JButton(Config.getWindowAbortButton());
	abortButton.setMnemonic(Config.getWindowAbortMnemonic()); 
	bottomPane.add(abortButton);
	bottomPane.add(bottomPane.createHorizontalGlue());	
	bottomPane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
	
	// Center Pane
	Box centerPane = new Box(BoxLayout.Y_AXIS);
        JLabel hidden = new JLabel(Config.getWindowHiddenLabel());
        hidden.setVisible(false);
        centerPane.add(hidden);
	_stepLabels = new JLabel[5];	
	for(int i = 0; i < _stepLabels.length; i++) {
	    _stepLabels[i] = new JLabel(Config.getWindowStep(i));
	    _stepLabels[i].setEnabled(false);
	    centerPane.add(_stepLabels[i]);

	    // install label's length will expand,so set a longer size.
            if(i == STEP_INSTALL) {
                Dimension dim = new JLabel(Config.
                        getWindowStepWait(STEP_INSTALL)).getPreferredSize();
                _stepLabels[i].setPreferredSize(dim);
            }
	}
        hidden = new JLabel(Config.getWindowHiddenLabel());
        hidden.setVisible(false);
        centerPane.add(hidden);
        
	// Setup box layout
	cont.add(topPane, "North");
	cont.add(westPane, "West");
	cont.add(bottomPane, "South");
	cont.add(centerPane, "Center");	
	
	_installerFrame.pack();
	Dimension dim = _installerFrame.getSize();

        // hard code to ensure title is completely visible on Sol/lin.
        if(dim.width < 400) {
            dim.width = 400;
            _installerFrame.setSize(dim);
        }
	
	Rectangle size =  _installerFrame.getBounds();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        size.width = Math.min(screenSize.width, size.width);
        size.height = Math.min(screenSize.height, size.height);
        // Put window at 1/4, 1/4 of screen resoluion
        _installerFrame.setBounds((screenSize.width - size.width) / 4,
				      (screenSize.height - size.height) / 4,
				  size.width, size.height);	
	
	// Setup event listners
	_installerFrame.addWindowListener(new WindowAdapter() {
		    public void windowClosing(WindowEvent we) {
			installFailed("Window closed", null);
		    }
		});
	
	abortButton.addActionListener(new ActionListener() {
		    public void actionPerformed(ActionEvent ae) {
			installFailed("Abort pressed", null);
		    }
		});
	
	// Show window
	_installerFrame.show();
    }   
    
    
    public static void enableStep(final int s) {
	SwingUtilities.invokeLater(new Runnable() {
		    public void run() {			
			for(int i = 0; i < _stepLabels.length; i++) {
			    _stepLabels[i].setEnabled(i == s);
			}
		    }
		});    
    }
    
    public static void setStepText(final int step, final String text) {
	SwingUtilities.invokeLater(new Runnable() {
		    public void run() {
			_stepLabels[step].setText(text);
		    }
		});
    }    
}


