/*
    Change environment variables on Windows
    Uses envvar.txt for the variables to set
    
    NOTE: Many developers think they need to change the classpath to
          make their Java app work on customers' machines. Fortunately,
          most JVMs that are freely distributable let you change the
          classpath on the command line. You can easily configure your
          customer's Java environment with JExpress by changing the
          setting on the JVM panel in JExpress Developer.

          If you still feel that you need to change the environment
          variable CLASSPATH, then you can use this class. Add the
          appropriate changes to envvar.txt.

          If this class does not perform as expected, please turn on debugging
          (i.e., set debugging=true) and use the diagnostic log (env.log).
          Also, the variables [$AppDir]/ and [$JvmDir]/ are only implemented for
          the CLASSPATH variable -- feel free to modify the code if you want to
          use them for other environment variables, too

    1. Include your envvar.txt file in the JExpressInstaller subdirectory
    2. envvar.txt is a plain text file that you create; its format is:
          action name=value
        where 
            action is replace or append
               (replace means that if this name already exists, then overwrite it
                with value; if the name doesn't already exist, then set it with value)
               (append means if name already exists, then append value to the
                existing value; if name doesn't already exist, then set it with value)

            name is the environment variable to set, and

            value is the value to set the environment variable

        For example, if you want to append the installation directory to the CLASSPATH, 
        the line would look like this (without leading spaces):

          append CLASSPATH=[$AppDir]/

    3. There must be a single blank space separating the action and the name
    4. There must be an equal sign separating the variable name from the value
    5. Any line that starts with ; is ignored
    6. There must be *no* blank lines in the file
    7. If you want to reset the environment variable, then the line should look like this:
         replace name=
    8. If you want to add to the classpath, then use the following command:
         replace CLASSPATH=value
       where value contains the appropriate information; you can include directories
        that are relative to the installation or the Java home directories
        when setting CLASSPATH;
          use [$AppDir]/ to reference the installation directory
          use [$JavaHomeDir]/ to reference the Java home directory
        for example, if you want to add a jar file called myapp.jar that is in the
         installation directory, then you would use the following line:
           replace CLASSPATH=[$AppDir]/myapp.jar

   9. The [$AppDir]/ and [$JavaHomeDir]/ only work with the CLASSPATH environment variable.
      Of course, you're welcome  to change this class so it works with other environment
      variables, too.
   10. You are welcome to adapt this class to meet your needs as long as you 
       only use it with the JExpress Professional product.

   11. If the class doesn't work as you expected, then you should turn debugging on (i.e.,
       change the initial setting of debugging to true) and create a plain text file called
       env.log in the same directory where you run the installer.

    Copyright (C) 1998-2006 DeNova
    
*/



import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.File;
import java.awt.BorderLayout;
import javax.swing.JLabel;
import com.denova.io.Log;
import com.denova.runtime.WindowsRegistry;
import com.denova.runtime.WindowsUtils;
import com.denova.ui.Fonts;
import com.denova.ui.WizardPanel;
import com.denova.util.PropertyList;
import com.denova.JExpress.Installer.CustomInstaller;
import com.denova.JExpress.Installer.InstallPropertyNames;



public class ChangeEnv extends WizardPanel
                               implements InstallPropertyNames {

    public ChangeEnv (PropertyList properties) {

        // initialize WizardPanel superclass
        super (properties);

        // let them know what's happening
        setLayout ( new BorderLayout () );
        JLabel notice = new JLabel("Changing environment variables", JLabel.CENTER);
        notice.setFont(Fonts.Bold);
        add(notice);
    }

    /** 
     * Enter the panel. 
     *
     * Do not call this method; only the wizard should call it.
     */
    public synchronized void enter()
    {
        // this panel simply changes the environment varialbes
        // it looks at the text file envvar.txt for the variables to set

        applicationDirectory = getPropertyList (). getProperty ( ApplicationDirectory, "" );

        try {

            // if we're on Windows, add the environment variable to autoexec.bat
            if ( isWindows() ) {
                setEnvOnWindows ();
            }

        }
        catch ( Exception e ) {

            // if there's no file, then there's nothing to do
            debug ( "exception while setting environment variables" );
            log ( e );
        }

	    // we don't want any user interaction
	    // so move them to the next panel automatically
	    showNextPanel ();
    }

    // set the environment variables under Windows
    public void setEnvOnWindows ()
    throws Exception {

        // get the envvar file from the JAR
        File envVarFile = new File ( envVarFilename );
        CustomInstaller. getResourceAsFile ( envVarFile. getPath () );
        log ( "extracted " + envVarFile. getPath () );

        // open envvar.txt
        FileReader fr = new FileReader ( envVarFile );
        BufferedReader envVarReader = new BufferedReader ( fr );
        log ( "opened envvar.txt for buffered read" );

        String line = envVarReader. readLine ();
        while ( line != null ) {


            // if this isn't a comment line
            if ( line. charAt (0) != ';' ) {

                log ( "non-comment envvar line is " + line );

                // determine where the components of the line end
                int endActionIndex = line. indexOf ( " " );
                int endVarIndex = line. indexOf ( "=" );

                // if the line contained the substrings, then add the variable
                if ( endActionIndex != -1 &&
                     endVarIndex != -1 ) {

                    String value;
                    String action = line. substring ( 0, endActionIndex );
                    String variable = line. substring ( endActionIndex + 1, endVarIndex );

                    log ( "action is " + action + " variable is " + variable );

                    if ( line. length() > endVarIndex + 1 ) {
                        value = line. substring ( endVarIndex + 1 );
                        log ( "value is " + value );
                    }
                    else {
                        value = "";
                        log ( "value is blank" );
                    }

                    // clear any errors
                    WindowsUtils. clearError ();

                    if ( isWindowsNT() ) {

                        log ( "running on WinNT" );

                        setWindowsNtEnvVar ( action. trim (),
                                             variable. trim (),
                                             value. trim () );
                    }

                    else {
                        log ( "error after isWindowsNT() call " + WindowsUtils. getLastError() );
                        log ( "running on Win95/98" );
                        setWindows95EnvVar ( action. trim (),
                                             variable. trim (),
                                             value. trim () );
                    }
                }
            }

            else {
                log ( "comment envvar line is " + line );
            }

            Thread. yield ();

            // get the next line
            line = envVarReader. readLine ();
        }

        envVarReader. close();
        fr. close ();
        envVarFile. delete ();
        log ( "closed reader and deleted file" );
    }

    // set a variable in Windows NT environment
    public void setWindowsNtEnvVar ( String action,
                                     String variable,
                                     String value )
    throws Exception {

        boolean ok = false;
        String keyName;

       // we need to handle classpath specially
        value = setClasspathValue ( variable, value );
        log ( "after setClasspathValue value is " + value );

        if ( action. equalsIgnoreCase ( AppendAction ) ) {

            String oldValue;

            if ( changeNtSystemWide ) {
                // see if the system wide variable exists
                keyName = NtSystem + "\\" + NtEnvironment;
                log ( "query " + keyName );
                oldValue = WindowsRegistry. getData ( keyName, variable );
                if ( oldValue != null &&
                     oldValue. length () > 0 ) {
                    if ( oldValue. endsWith ( File. pathSeparator ) ) {
                        value = oldValue + value;
                    }
                    else {
                        value = oldValue + File. pathSeparator + value;
                    }
                    log ( "  old value for " + variable + " is " + oldValue );
                }
                else {
                    log ( "  no old value for " + variable );
                }
            }

            // also see if the user's environment variable exists
            keyName = NtCurrentUser + "\\" + NtEnvironment;
            log ( "query " + keyName );
            oldValue = WindowsRegistry. getData ( keyName, variable );
            if ( oldValue != null &&
                 oldValue. length () > 0 ) {
                value = oldValue + File. pathSeparator + value;
                log ( "  old value for " + variable + " is " + oldValue );
            }
            else {
                log ( "  no old value for " + variable );
            }
        }

        if ( changeNtSystemWide ) {
            // try a system wide change
            keyName = NtSystem + "\\" + NtEnvironment;
            log ( "replace action for " + keyName );
            ok = WindowsRegistry. replaceData ( keyName, variable, value );
            log ( "after replaceData ok is " + String. valueOf ( ok ) );
        }

        // change the user's environment, too
        keyName = NtCurrentUser + "\\" + NtEnvironment;
        log ( "replace value for " + keyName + "\\" + variable + " to " + value );
        ok = WindowsRegistry. replaceData ( keyName, variable, value );
        log ( "after replaceData ok = " + String. valueOf ( ok ) );
    }

    // set a variable in Windows 95/98 environment
    public void setWindows95EnvVar ( String action,
                                   String variable,
                                   String value )
    throws Exception {

        boolean matchFound = false;


        File inFile = new File ( WinFilename );
        if ( inFile. exists () ) {

            matchFound = changeWindows95EnvVar ( action, variable, value );
        }

        // if the variable didn't exist, then add it to top of the file
        if ( ! matchFound ) {

            addWindows95EnvVar ( action, variable, value );
        }

        // make a backup and then copy the file to it's permanent location
        com.denova.io.FileSystem. copyFile ( WinFilename, BackupWinFilename );
        com.denova.io.FileSystem. copyFile ( TempWinFilename, WinFilename );

        // delete our temporary file
        File outFile = new File ( TempWinFilename );
        outFile. delete ();
    }

    // if the variable exists, change it
    public boolean changeWindows95EnvVar ( String action,
                                           String variable,
                                           String value )
    throws Exception {

        final String SetLine = "set";
        boolean matchFound = false;


        File inFile = new File ( WinFilename );
        FileReader inputFile = new FileReader ( inFile );
        BufferedReader inputReader = new BufferedReader ( inputFile );

        File outFile = new File ( TempWinFilename );
        FileWriter outputFile = new FileWriter ( outFile );
        BufferedWriter outputWriter = new BufferedWriter ( outputFile );

        // look for the variable, save all lines in a temporary file
        String line = inputReader. readLine ();
        while ( line != null ) {

            if ( ! matchFound ) {

                // if this line sets an environment variable
                if ( line. length() > SetLine. length () ) {

                    String startLine = line. substring ( 0, SetLine. length () );

                    if ( startLine. equalsIgnoreCase ( SetLine ) ) {

                        int endIndex = line. indexOf ( "=" );

                        if ( endIndex != -1 ) {

                            startLine = line. substring ( SetLine. length () + 1,
                                                          endIndex );

                            // and it's the variable we want to set
                            if ( startLine. equalsIgnoreCase ( variable ) ) {

                                matchFound = true;

                                set95EnvVar ( line,
                                              outputWriter,
                                              action,
                                              variable,
                                              value );
                            }
                        }
                    }
                }

                if ( ! matchFound ) {

                    saveLine ( line, outputWriter );
                }
            }

            else {

                saveLine ( line, outputWriter );
            }

            // get the next line
            line = inputReader. readLine ();

        } // while

        inputReader. close ();
        inputFile. close ();
        outputWriter. close ();
        outputFile. close ();

        return matchFound;
    }

    // add a new variable to Windows' environment
    public void addWindows95EnvVar ( String action,
                                     String variable,
                                     String value )
    throws Exception {

        File outFile = new File ( TempWinFilename );
        FileWriter outputFile = new FileWriter ( outFile );
        BufferedWriter outputWriter = new BufferedWriter ( outputFile );

        set95EnvVar ( "",
                      outputWriter,
                      action,
                      variable,
                      value );

        File inFile = new File ( WinFilename );
        if ( inFile. exists () ) {

            FileReader inputFile = new FileReader ( inFile );
            BufferedReader inputReader = new BufferedReader ( inputFile );

            String line = inputReader. readLine ();
            while ( line != null ) {

                // save the line to the new temporary file
                saveLine ( line, outputWriter );

                // get the next line
                line = inputReader. readLine ();

            } // while

            inputReader. close ();
            inputFile. close ();
        }

        outputWriter. close ();
        outputFile. close ();
    }

    // set the actual variable
    public void set95EnvVar ( String line,
                              BufferedWriter outputWriter,
                              String action,
                              String variable,
                              String value )
    {
        String newLine;

        // we need to handle classpath specially
        value = setClasspathValue ( variable, value );

        // if action equals replace or there isn't a line
        // then use the new setting
        if ( action. equalsIgnoreCase ( "replace" ) ||
             line. length() <= 0 ) {

            newLine = "set " + variable + "=" + value;

            try {
                saveLine ( newLine, outputWriter );
            }
            catch ( Exception e ) {
                // !!!!! you may want to handle this situation
            }
        }

        else if ( action. equalsIgnoreCase ( "append" ) ) {

            if ( isWindows() ) {

                newLine = line + File. pathSeparator + value;
            }

            else {

                newLine = line + ":" + value;
            }

            try {
                saveLine ( newLine, outputWriter );
            }
            catch ( Exception e ) {
                // !!!!! you may want to handle this situation
            }
         }
    }

    // handle classpath environment variable
    public String setClasspathValue ( String variable,
                                      String value )
    {
        // we need to handle classpath specially
        if ( variable. equalsIgnoreCase ( "classpath" ) ) {

            final String AppDirVar = "[$AppDir]/";
            final String JavaHomeDirVar = "[$JavaHomeDir]/";
            String appDir = getPropertyList (). getProperty ( ApplicationDirectory, "" );
            String javaHomeDir = System. getProperty ( "java.home" );

            log ( "classpath variable is " + variable );

            // change all references to [$AppDir] to the actual directory
            value = changeDirname ( value, AppDirVar, appDir );
            log ( "value after AppDirVar changeDirname is " + value );

            // change all references to [$JavaHomeDir] to the actual directory
            value = changeDirname ( value, JavaHomeDirVar, javaHomeDir );
            log ( "value after JavaHomeDirVar changeDirname is " + value );

            // use the correct version of the directory separator
            value = value. replace ( '/', File. separatorChar );
            log ( "value after File.separatorChar changeDirname is " + value );

            // if there are any spaces in the directory names
            if ( value. indexOf ( " " ) != -1 ) {

                // add quotes so Windows doesn't get confused
                value = "\"" + value + "\"";
                log ( "value after adding quotes is " + value );
            }
        }

        else {
            log ( "non-classpath variable is " + variable );
        }

        return value;
    }

    // change the directory variable
    public String changeDirname ( String value,
                                  String varDirname,
                                  String fullDirname )
    {
        String newValue;
        String tempValue;
        int startIndex, endIndex;

        // default to the starting value
        newValue = value;

        // in value, change all references to varDirname to the fullDirname
        startIndex = newValue. indexOf ( varDirname );
        if ( startIndex == -1 ) {
            log ( varDirname + " is not found in " + newValue );
        }

        while ( startIndex != -1 ) {

            endIndex = startIndex + varDirname. length ();

            // save the beginning of the value
            if ( startIndex > 0 ) {

                tempValue = newValue. substring ( 0, startIndex );
            }

            else {

                tempValue = "";
            }
            log ( "found " + varDirname + " in " + newValue );

            // add the actual application directory
            tempValue += fullDirname;
            tempValue += "/";

            // add the rest of the original value
            tempValue += newValue. substring ( endIndex, newValue. length() );

            // remember our handy work
            newValue = tempValue;
            log ( "value after " + varDirname + " replaced is " + newValue );

            // see if there are any other AppDir variables we need to change
            startIndex = newValue. indexOf ( varDirname );
        }

        return newValue;
    }


    // save the line
    public void saveLine ( String line,
                           BufferedWriter outputWriter )
    throws Exception {

        outputWriter. write ( line );

        // add the appropriate line ending for the OS
        if ( isWindows() ) {

            outputWriter. write ( "\r\n" );
        }

        else {

            outputWriter. write ( '\n' );
        }
    }

    // don't let the user bypass this command before we're done
    public boolean isNextButtonEnabled () {
        return false;
    }

    // don't let the user back up before we're done
    public boolean isPreviousButtonEnabled () {
        return false;
    }

    void debug ( String s ) {
        if ( debugging ) {
            log (s);
        }
    }

    static private void log ( String s ) {
        startLog ();
        envLog. write ( s );
    }

    static private void log ( Exception e ) {
        startLog ();
        envLog. write ( e );
    }

    static private void startLog () {
        if ( envLog == null ) {
            envLog = new Log ( "env" );
        }
    }

    static private void stopLogging () {
        if ( envLog != null ) {
            envLog. stopLogging ();
        }
    }

    static Log envLog;
    static final boolean debugging = true;
    static final boolean changeNtSystemWide = false;
    static String applicationDirectory;


    // the name of the file with the environment variables
    static final String envVarFilename = "envvar.txt";
    static final String WinFilename = "c:\\autoexec.bat";
    static final String BackupWinFilename = "c:\\autoexec.bak";
    static final String TempWinFilename = "autoexec.new";
    static final String ReplaceAction = "replace";
    static final String AppendAction = "append";
    static final String NtSystem = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control";
    static final String NtEnvironment = "Environment";
    static final String NtCurrentUser = "HKEY_CURRENT_USER";

}  // ChangeEnv