content: /*
   *    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.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   *    PackageManager.java
   *    Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
   */
  
  package weka.gui;
  
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.FlowLayout;
  import java.awt.GridLayout;
  import java.awt.Image;
  import java.awt.Toolkit;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.io.PrintStream;
  import java.net.URL;
  import java.net.URLConnection;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeMap;
  
  import javax.swing.BorderFactory;
  import javax.swing.ButtonGroup;
  import javax.swing.DefaultCellEditor;
  import javax.swing.DefaultComboBoxModel;
  import javax.swing.ImageIcon;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComboBox;
  import javax.swing.JEditorPane;
  import javax.swing.JFrame;
  import javax.swing.JLabel;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JProgressBar;
  import javax.swing.JRadioButton;
  import javax.swing.JScrollPane;
  import javax.swing.JSplitPane;
  import javax.swing.JTable;
  import javax.swing.JTextArea;
  import javax.swing.JToolBar;
  import javax.swing.ListSelectionModel;
  import javax.swing.SwingWorker;
  import javax.swing.event.HyperlinkEvent;
  import javax.swing.event.HyperlinkListener;
  import javax.swing.event.ListSelectionEvent;
  import javax.swing.event.ListSelectionListener;
  import javax.swing.table.DefaultTableModel;
  import javax.swing.table.JTableHeader;
  import javax.swing.table.TableColumnModel;
  
  import org.pentaho.packageManagement.Dependency;
  import org.pentaho.packageManagement.Package;
  import org.pentaho.packageManagement.PackageConstraint;
  
  import weka.core.Environment;
  import weka.core.Utils;
  import weka.core.WekaPackageManager;
  import weka.gui.beans.FileEnvironmentField;
  
  /**
   * A GUI interface the the package management system.
   * 
   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
   * @version $Revision: 7080 $
   */
  public class PackageManager extends JPanel {
    
    /** For serialization */
    private static final long serialVersionUID = -7463821313750352385L;
    
    protected static final String PACKAGE_COLUMN = "Package";
    protected static final String CATEGORY_COLUMN = "Category";
    protected static final String INSTALLED_COLUMN = "Installed version";
    protected static final String REPOSITORY_COLUMN = "Repository version";
    protected static final String LOADED_COLUMN = "Loaded";
    
    /** The JTable for displaying the package names and version numbers */
    protected JTable m_table = new ETable();
    
    protected JSplitPane m_splitP;
      
    // protected JTextArea m_packageDescription;
    
    /** An editor pane to display package information */
    protected JEditorPane m_infoPane;
    
    /** Installed radio button */
    protected JRadioButton m_installedBut = new JRadioButton("Installed");
    
    /** Available radio button */
    protected JRadioButton m_availableBut = new JRadioButton("Available");
    
    /** All radio button */
    protected JRadioButton m_allBut = new JRadioButton("All");
    
    /** Button for installing the selected package */
    protected JButton m_installBut = new JButton("Install");
    protected JCheckBox m_forceBut = new JCheckBox("Ignore dependencies/conflicts");
    
    /** Button for uninstalling the selected package */
    protected JButton m_uninstallBut = new JButton("Uninstall");
    
    protected JButton m_refreshCacheBut = new JButton("Refresh repository cache");
    
    protected JProgressBar m_progress = new JProgressBar(0, 100);
    protected JLabel m_detailLabel = new JLabel();
    
    protected JButton m_backB;
    protected LinkedList<URL> m_browserHistory = new LinkedList<URL>();
    protected static final String BROWSER_HOME = "http://www.cs.waikato.ac.nz/ml/weka/index_home_pm.html";
    protected JButton m_homeB;
    
    protected JToolBar m_browserTools;
    
    protected JLabel m_newPackagesAvailableL;
    
    protected DefaultTableModel m_model;
  
    protected Map<String, List<Object>> m_packageLookupInfo;
  
    protected List<Package> m_allPackages;
    protected List<Package> m_installedPackages;
    protected List<Package> m_availablePackages;
    
    /** The column in the table to sort the entries by */
    protected int m_sortColumn = 0;
    
    /** Reverse the sort order if the user clicks the same column header twice */
    protected boolean m_reverseSort = false;
    
    /** Button to pop up the file environment field widget */
    protected JButton m_unofficialBut = new JButton("File/URL");
    
    /** Widget for specifying a URL or path to an unofficial package to install */
    protected FileEnvironmentField m_unofficialChooser = 
      new FileEnvironmentField("File/URL", Environment.getSystemWide());
    protected JFrame m_unofficialFrame = null;
    
    protected Comparator<Package> m_packageComparator = new Comparator<Package>() {
  
      @Override
      public int compare(Package o1, Package o2) {
        String meta1 = "";
        String meta2 = "";
        if (m_sortColumn == 0) {
          meta1 = o1.getName();
          meta2 = o2.getName();
        } else {
          if (o1.getPackageMetaDataElement("Category") != null) {
            meta1 = o1.getPackageMetaDataElement("Category").toString();
          }
          
          if (o2.getPackageMetaDataElement("Category") != null) {
            meta2 = o2.getPackageMetaDataElement("Category").toString();
          }
        }
        
        
        int result = meta1.compareTo(meta2);
        if (m_reverseSort) {
          result = -result;
        }
        return result;
      }    
    };
    
    protected boolean m_installing = false;    
    
    class ProgressPrintStream extends PrintStream {
      
      private Progressable m_listener;
      
      public ProgressPrintStream(Progressable listener) {
        // have to invoke a super class constructor
        super(System.out);
        m_listener = listener;      
      }
      
      public void println(String string) {
        boolean messageOnly = false;
        if (string.startsWith("%%")) {
          string = string.substring(2);
          messageOnly = true;
        }
        if (!messageOnly) {
          System.out.println(string); // make sure the log picks it up
          m_listener.makeProgress(string);
        } else {
          m_listener.makeProgressMessageOnly(string);
        }
      }
      
      public void println(Object obj) {
        println(obj.toString());
      }
      
      public void print(String string) {
        boolean messageOnly = false;
        if (string.startsWith("%%")) {
          string = string.substring(2);
          messageOnly = true;
        }
        
        if (!messageOnly) {
          System.out.print(string); // make sure the log picks it up
          m_listener.makeProgress(string);
        } else {
          m_listener.makeProgressMessageOnly(string);
        }
      }
      
      public void print(Object obj) {
        print (obj.toString());
      }    
    }
    
    interface Progressable {
      void makeProgress(String progressMessage);
      void makeProgressMessageOnly(String progressMessage);
    }
    
    class EstablishCache extends SwingWorker<Void, Void> implements Progressable {
      private int m_progressCount = 0;
      private Exception m_error = null;
      
      private javax.swing.ProgressMonitor m_progress;
      
      public void makeProgress(String progressMessage) {
        m_progress.setNote(progressMessage);
        m_progressCount++;
        m_progress.setProgress(m_progressCount);
      }
      
      public void makeProgressMessageOnly(String progressMessage) {
        m_progress.setNote(progressMessage);
      }
      
      public Void doInBackground() {
        int numPackages = WekaPackageManager.numRepositoryPackages();
        if (numPackages < 0) {
          // there was some problem getting the file that holds this
          // information from the repository server - try to continue
          // anyway with a max value of 100 for the number of packages
          // (since all we use this for is setting the upper bound on
          // the progress bar).
          numPackages = 100;
        }
        m_progress = new javax.swing.ProgressMonitor(PackageManager.this, "Establising cache...", 
            "", 0, numPackages);
        ProgressPrintStream pps = new ProgressPrintStream(this);
        m_error = WekaPackageManager.establishCacheIfNeeded(pps);
  
        m_cacheEstablished = true;
        return null;
      }
      
      public void done() {
        m_progress.close();
        if (m_error != null) {
          displayErrorDialog("There was a problem establishing the package\n" +
          		"meta data cache. We'll try to use the repository" +
          		"directly.", m_error);
        }
      }
    }
    
    class CheckForNewPackages extends SwingWorker<Void, Void> {
      
      public Void doInBackground() {
        Map<String, String> localPackageNameList = 
          WekaPackageManager.getPackageList(true);
        
        if (localPackageNameList == null) {
          // quietly return and see if we can continue anyway
          return null;
        }
        
        Map<String, String> repositoryPackageNameList =
          WekaPackageManager.getPackageList(false);
        
        if (repositoryPackageNameList == null) {
          // quietly return and see if we can continue anyway
          return null;
        }
        
        if (repositoryPackageNameList.keySet().size() < 
            localPackageNameList.keySet().size()) {
          // package(s) have disappeared from the repository.
          // Force a cache refresh...
          RefreshCache r = new RefreshCache();
          r.execute();
          
          return null;
        }
        
        StringBuffer newPackagesBuff = new StringBuffer();
        
        for (String s : repositoryPackageNameList.keySet()) {
          if (!localPackageNameList.containsKey(s)) {
            newPackagesBuff.append(s + "<br>");
          }
        }
        
        if (newPackagesBuff.length() > 0) {
          String information = "<html><font size=-2>There are new packages available " +
          		"on the server (do a cache refresh for more " +
          		"information):<br><br>" + newPackagesBuff.toString() + 
          		"</font></html>";
          m_newPackagesAvailableL.setToolTipText(information);
          m_browserTools.add(m_newPackagesAvailableL);
  
          m_browserTools.revalidate();
        }
        
        return null;
      }
    } 
    
    class RefreshCache extends SwingWorker<Void, Void> implements Progressable {
      private int m_progressCount = 0;
      private Exception m_error = null;
      
      public void makeProgress(String progressMessage) {
        m_detailLabel.setText(progressMessage);
        m_progressCount++;
        m_progress.setValue(m_progressCount);
      }
      
      public void makeProgressMessageOnly(String progressMessage) {
        m_detailLabel.setText(progressMessage);
      }
      
      public Void doInBackground() {
        m_cacheRefreshInProgress = true;
        int numPackages = WekaPackageManager.numRepositoryPackages();
        if (numPackages < 0) {
          // there was some problem getting the file that holds this
          // information from the repository server - try to continue
          // anyway with a max value of 100 for the number of packages
          // (since all we use this for is setting the upper bound on
          // the progress bar).
          numPackages = 100;
        }
        m_progress.setMaximum(numPackages);
        m_refreshCacheBut.setEnabled(false);
        m_installBut.setEnabled(false);
        m_unofficialBut.setEnabled(false);
        m_installedBut.setEnabled(false);
        m_availableBut.setEnabled(false);
        m_allBut.setEnabled(false);      
        ProgressPrintStream pps = new ProgressPrintStream(this);
        m_error = WekaPackageManager.refreshCache(pps);
        getAllPackages();
        return null;
      }
      
      public void done() {
        m_progress.setValue(m_progress.getMinimum());
        if (m_error != null) {
          displayErrorDialog("There was a problem refreshing the package\n" +
              "meta data cache. We'll try to use the repository" +
              "directly.", m_error);
          m_detailLabel.setText("");
        } else {
          m_detailLabel.setText("Cache refresh completed");
        }
        
        m_installBut.setEnabled(true);
        m_unofficialBut.setEnabled(true);
        m_refreshCacheBut.setEnabled(true);
        m_installedBut.setEnabled(true);
        m_availableBut.setEnabled(true);
        m_allBut.setEnabled(true);
        
        updateTable();
        
        try {
          m_browserTools.remove(m_newPackagesAvailableL);
          m_browserTools.revalidate();
        } catch (Exception ex) { }
        
        m_cacheRefreshInProgress = false;
      }
    }
    
    private void pleaseCloseAppWindowsPopUp() {
      if (!Utils.getDontShowDialog("weka.gui.PackageManager.PleaseCloseApplicationWindows")) {
        JCheckBox dontShow = new JCheckBox("Do not show this message again");
        Object[] stuff = new Object[2];
        stuff[0] = "Please close any open Weka application windows\n" +
          "(Explorer, Experimenter, KnowledgeFlow, SimpleCLI)\n" + 
          "before proceeding.\n";
        stuff[1] = dontShow;
  
        JOptionPane.showMessageDialog(PackageManager.this, stuff, 
            "Weka Package Manager", JOptionPane.OK_OPTION);
  
        if (dontShow.isSelected()) {
          try {
            Utils.setDontShowDialog("weka.gui.PackageManager.PleaseCloseApplicationWindows");
          } catch (Exception ex) {
            // quietly ignore
          }
        }
      }
    }
    
    class UninstallTask extends SwingWorker<Void, Void> implements Progressable {
      
      private List<String> m_packageNamesToUninstall;
  //    private String m_packageName;
    //   private boolean m_successfulUninstall = false;
      private List<String> m_unsuccessfulUninstalls = new ArrayList<String>();
      
      private int m_progressCount = 0;
      
      public void setPackages(List<String> packageNames) {
        m_packageNamesToUninstall = packageNames;
      }
      
      public void makeProgress(String progressMessage) {
        m_detailLabel.setText(progressMessage);
        m_progressCount++;
        m_progress.setValue(m_progressCount);
        if (m_progressCount == m_progress.getMaximum()) {
          m_progress.setMaximum(m_progressCount + 5);
        }
      }
      
      public void makeProgressMessageOnly(String progressMessage) {
        m_detailLabel.setText(progressMessage);
      }
      
      public Void doInBackground() {
        m_installing = true;
        m_installBut.setEnabled(false);
        m_unofficialBut.setEnabled(false);
        m_uninstallBut.setEnabled(false);      
        m_refreshCacheBut.setEnabled(false);
        m_availableBut.setEnabled(false);
        m_allBut.setEnabled(false);
        m_installedBut.setEnabled(false);
        
        ProgressPrintStream pps = new ProgressPrintStream(this);
        m_progress.setMaximum(m_packageNamesToUninstall.size() * 5);
  
        for (int zz = 0; zz < m_packageNamesToUninstall.size(); zz++) {
  
          String packageName = m_packageNamesToUninstall.get(zz);
  
          boolean explorerPropertiesExist = 
            WekaPackageManager.installedPackageResourceExists(packageName, "Explorer.props");
  
          if (!m_forceBut.isSelected()) {
            List<Package> compromised = new ArrayList<Package>();
  
            // Now check to see which other installed packages depend on this one
            List<Package> installedPackages;
            try {
              installedPackages = WekaPackageManager.getInstalledPackages();
            } catch (Exception e) {
              e.printStackTrace();
              displayErrorDialog("Can't determine which packages are installed!", e);
              // return null; // can't proceed
              m_unsuccessfulUninstalls.add(packageName);
              continue;
            }
            for (Package p : installedPackages) {
              List<Dependency> tempDeps;
              try {
                tempDeps = p.getDependencies();
              } catch (Exception e) {
                e.printStackTrace();
                displayErrorDialog("Problem determining dependencies for package : " 
                    + p.getName(), e);
                //return null; // can't proceed
                m_unsuccessfulUninstalls.add(packageName);
                continue;
              }
  
              for (Dependency d : tempDeps) {
                if (d.getTarget().getPackage().getName().equals(packageName)) {
                  // add this installed package to the list
                  compromised.add(p);
                  break;
                }
              }
            }
  
            if (compromised.size() > 0) {
              StringBuffer message = new StringBuffer();
              message.append("The following installed packages depend on " 
                  + packageName + " :\n\n");
              for (Package p : compromised) {
                message.append("\t" + p.getName() + "\n");
              }
  
              message.append("\nDo you wish to proceed?");
              int result = JOptionPane.showConfirmDialog(PackageManager.this, message.toString(), 
                  "Weka Package Manager", JOptionPane.YES_NO_OPTION);
  
              if (result == JOptionPane.NO_OPTION) {
                // bail out here
                //return null;
                continue;
              }          
            }
          }
  
  //        m_progress.setMaximum(10);
          try {
            if (explorerPropertiesExist) {
              // need to remove any set Explorer properties first
              WekaPackageManager.removeExplorerProps(packageName);
            }
            WekaPackageManager.uninstallPackage(packageName, true, pps);
  
          } catch (Exception e) {
            e.printStackTrace();
            displayErrorDialog("Unable to uninstall package: " + packageName, e);
            //return null;
            m_unsuccessfulUninstalls.add(packageName);
            continue;
          }
        }
        
        WekaPackageManager.refreshGOEProperties();
        // m_successfulUninstall = true;
        
        return null;
      }
      
      public void done() {
        m_progress.setValue(m_progress.getMinimum());      
        if (m_unsuccessfulUninstalls.size() == 0) {
          m_detailLabel.setText("Packages removed successfully.");
          
          if (!Utils.getDontShowDialog("weka.gui.PackageManager.RestartAfterUninstall")) {
            JCheckBox dontShow = new JCheckBox("Do not show this message again");
            Object[] stuff = new Object[2];
            stuff[0] = "Weka might need to be restarted for\n" +
              "the changes to come into effect.\n";
            stuff[1] = dontShow;
  
            JOptionPane.showMessageDialog(PackageManager.this, stuff, 
                "Weka Package Manager", JOptionPane.OK_OPTION);
  
            if (dontShow.isSelected()) {
              try {
                Utils.setDontShowDialog("weka.gui.PackageManager.RestartAfterUninstall");
              } catch (Exception ex) {
                // quietly ignore
              }
            }
          }        
        } else {
          StringBuffer failedPackageNames = new StringBuffer();
          for (String p : m_unsuccessfulUninstalls) {
            failedPackageNames.append(p + "\n");
          }
          displayErrorDialog("The following package(s) could not be uninstalled\n"
              + "for some reason (check the log)\n" + failedPackageNames.toString()
              , "");
          m_detailLabel.setText("Finished uninstalling.");
        }
        
        m_unofficialBut.setEnabled(true);
        m_refreshCacheBut.setEnabled(true);
        m_availableBut.setEnabled(true);
        m_allBut.setEnabled(true);
        m_installedBut.setEnabled(true);
        
        // force refresh of installed and available packages
        m_installedPackages = null;
        m_availablePackages = null;
  //      m_installBut.setEnabled(true);
        m_installing = false;
        updateTable();
        if (m_table.getSelectedRow() >= 0) {
          // mainly to update the install/uninstall button status
          //displayPackageInfo(m_table.getSelectedRow());
          updateInstallUninstallButtonEnablement();
        }
      }
    }
    
    class UnofficialInstallTask extends SwingWorker<Void, Void> implements Progressable {
      
      private String m_target;
      private int m_progressCount = 0;
      private boolean m_errorOccurred = false;
      
      public void setTargetToInstall(String target) {
        m_target = target;
      }
      
      public void makeProgress(String progressMessage) {
        m_detailLabel.setText(progressMessage);
        m_progressCount++;
        m_progress.setValue(m_progressCount);
        if (m_progressCount == m_progress.getMaximum()) {
          m_progress.setMaximum(m_progressCount + 5);
        }
      }
      
      public void makeProgressMessageOnly(String progressMessage) {
        m_detailLabel.setText(progressMessage);
      }
      
      public Void doInBackground() {
        m_installing = true;
        m_installBut.setEnabled(false);
        m_uninstallBut.setEnabled(false);
        m_refreshCacheBut.setEnabled(false);
        m_unofficialBut.setEnabled(false);
        m_availableBut.setEnabled(false);
        m_allBut.setEnabled(false);
        m_installedBut.setEnabled(false);
        ProgressPrintStream pps = new ProgressPrintStream(this);
        m_progress.setMaximum(30);
        
        Package installedPackage = null;
        String toInstall = m_target;
        try {
          toInstall = Environment.getSystemWide().substitute(m_target);
        } catch (Exception ex) {}
        
        try {
          if (toInstall.toLowerCase().startsWith("http://") || 
              toInstall.toLowerCase().startsWith("https://")) {
            String packageName = 
              WekaPackageManager.installPackageFromURL(new URL(toInstall), pps);
            installedPackage = WekaPackageManager.getInstalledPackageInfo(packageName);
          } else if (toInstall.toLowerCase().endsWith(".zip")){
            String packageName = WekaPackageManager.installPackageFromArchive(toInstall, pps);
            installedPackage = WekaPackageManager.getInstalledPackageInfo(packageName);
          } else {
            displayErrorDialog("Unable to install package " +
                "\nfrom " + toInstall + ". Unrecognized as a URL or zip archive.",
                (String)null);
            m_errorOccurred = true;
            return null;
          }
        } catch (Exception ex) {
          displayErrorDialog("Unable to install package " +
              "\nfrom " + m_target + ". Check the log for error messages.",
               ex);
          m_errorOccurred = true;
          return null;
        }
        
        if (installedPackage != null) {
          try {        
            File packageRoot = 
              new File(WekaPackageManager.getPackageHome() + File.separator 
                  + installedPackage.getName());
            boolean loadCheck  = 
              WekaPackageManager.loadCheck(installedPackage, packageRoot, pps);
            
            if (!loadCheck) {
              displayErrorDialog("Package was installed correctly but could not " +
              		"be loaded. Check log for details", (String)null);
            }
          } catch (Exception ex) {
            displayErrorDialog("Unable to install package " +
                "\nfrom " + m_target + ".",
                ex);
            m_errorOccurred = true;
          }
  
          WekaPackageManager.refreshGOEProperties();
        }
        return null;
      }
      
      public void done() {
        m_progress.setValue(m_progress.getMinimum());
        if (m_errorOccurred) {
          m_detailLabel.setText("Problem installing - check log.");
        } else {
          m_detailLabel.setText("Package installed successfully.");
        }
        
        m_unofficialBut.setEnabled(true);
        m_refreshCacheBut.setEnabled(true);      
        m_availableBut.setEnabled(true);
        m_allBut.setEnabled(true);
        m_installedBut.setEnabled(true);
        
        // force refresh of installed and available packages
        m_installedPackages = null;
        m_availablePackages = null;
        
  //      m_installBut.setEnabled(true);
        m_installing = false;
        updateTable();
        if (m_table.getSelectedRow() >= 0) {
          // mainly to update the install/uninstall button status
          //displayPackageInfo(m_table.getSelectedRow());
          updateInstallUninstallButtonEnablement();
        }
      }
    }
     
    class InstallTask extends SwingWorker<Void, Void> implements Progressable {
  
      private List<String> m_packageNamesToInstall;
      private List<Object> m_versionsToInstall;
      
  //    private boolean m_successfulInstall = false;
      private List<Package> m_unsuccessfulInstalls = new ArrayList<Package>();
  
      private int m_progressCount = 0;
  
      public void setPackages(List<String> packagesToInstall) {
        m_packageNamesToInstall = packagesToInstall;
      }
  
      public void setVersions(List<Object> versionsToInstall) {
        m_versionsToInstall = versionsToInstall;
      }
  
      public void makeProgress(String progressMessage) {
        m_detailLabel.setText(progressMessage);
        m_progressCount++;
        m_progress.setValue(m_progressCount);
        if (m_progressCount == m_progress.getMaximum()) {
          m_progress.setMaximum(m_progressCount + 5);
        }
      }
      
      public void makeProgressMessageOnly(String progressMessage) {
        m_detailLabel.setText(progressMessage);
      }
  
      /*
       * Main task. Executed in background thread.
       */
      @Override
      public Void doInBackground() {
        m_installing = true;
        m_installBut.setEnabled(false);
        m_unofficialBut.setEnabled(true);
        m_uninstallBut.setEnabled(false);
        m_refreshCacheBut.setEnabled(false);
        m_availableBut.setEnabled(false);
        m_allBut.setEnabled(false);
        m_installedBut.setEnabled(false);
        ProgressPrintStream pps = new ProgressPrintStream(this);
        m_progress.setMaximum(m_packageNamesToInstall.size() * 30);
  
        for (int zz = 0; zz < m_packageNamesToInstall.size(); zz++) {
          Package packageToInstall = null;
          String packageName = m_packageNamesToInstall.get(zz);
          Object versionToInstall = m_versionsToInstall.get(zz);
          try {
            packageToInstall = WekaPackageManager.getRepositoryPackageInfo(packageName, 
               versionToInstall.toString());
          } catch (Exception e) {
            e.printStackTrace();
            displayErrorDialog("Unable to obtain package info for package: " 
                + packageName, e);
  //          return null; // bail out here
            m_unsuccessfulInstalls.add(packageToInstall);
            continue;
          }
  
          // check for any special installation instructions
          Object specialInstallMessage = 
            packageToInstall.getPackageMetaDataElement("MessageToDisplayOnInstallation");
          if (specialInstallMessage != null && 
              specialInstallMessage.toString().length() > 0) {
            String siM = specialInstallMessage.toString();
            try {
              siM = Environment.getSystemWide().substitute(siM);
            } catch (Exception ex) {
              // quietly ignore
            }
            JOptionPane.showMessageDialog(PackageManager.this, 
                packageToInstall + "\n\n" + siM, 
                "Weka Package Manager", JOptionPane.OK_OPTION);
          }
  
          if (!m_forceBut.isSelected()) {
            try {
              if (!packageToInstall.isCompatibleBaseSystem()) {
                List<Dependency> baseSysDep = packageToInstall.getBaseSystemDependency();
                StringBuffer depList = new StringBuffer();
                for (Dependency bd : baseSysDep) {
                  depList.append(bd.getTarget().toString() + " ");
                }
  
                JOptionPane.showMessageDialog(PackageManager.this, "Unable to install package " +
                    "\n" + packageName + " because it requires" +
                    "\n" + depList.toString(), 
                    "Weka Package Manager", JOptionPane.ERROR_MESSAGE);
                // bail out here
                //return null;
                m_unsuccessfulInstalls.add(packageToInstall);
                continue;
              }                    
            } catch (Exception e) {
              e.printStackTrace();
              displayErrorDialog("Problem determining dependency on base system" +
                  " for package: " + packageName, e);
              //return null; // can't proceed
              m_unsuccessfulInstalls.add(packageToInstall);
              continue;
            }
  
            // check to see if package is already installed
            boolean upOrDowngrading = false;
            if (packageToInstall.isInstalled()) {
              Package installedVersion = null;
              try {
                installedVersion = WekaPackageManager.getInstalledPackageInfo(packageName);
              } catch (Exception e) {
                e.printStackTrace();
                displayErrorDialog("Problem obtaining package info for package: " 
                    + packageName, e);
                //return null; // can't proceed
                m_unsuccessfulInstalls.add(packageToInstall);
                continue;
              }
  
              if (!packageToInstall.equals(installedVersion)) {
                int result = JOptionPane.showConfirmDialog(PackageManager.this, "Package " + 
                    installedVersion + " is already installed. Replace with " + 
                    packageToInstall + "?", "Weka Package Manager", JOptionPane.YES_NO_OPTION);
                if (result == JOptionPane.NO_OPTION) {
                  // bail out here
                  //return null;
                  m_unsuccessfulInstalls.add(packageToInstall);
                  continue;
                }
  
                if (!Utils.getDontShowDialog("weka.gui.PackageManager.RestartAfterUpgrade")) {
                  JCheckBox dontShow = new JCheckBox("Do not show this message again");
                  Object[] stuff = new Object[2];
                  stuff[0] = "Weka will need to be restared after installation for\n" +
                  "the changes to come into effect.\n";
                  stuff[1] = dontShow;
  
                  JOptionPane.showMessageDialog(PackageManager.this, stuff, 
                      "Weka Package Manager", JOptionPane.OK_OPTION);
  
                  if (dontShow.isSelected()) {
                    try {
                      Utils.setDontShowDialog("weka.gui.PackageManager.RestartAfterUpgrade");
                    } catch (Exception ex) {
                      // quietly ignore
                    }
                  }
                }
              } else {
                int result = JOptionPane.showConfirmDialog(PackageManager.this, "Package " + 
                    installedVersion + " is already installed. Install again?",
                    "Weka Package Manager", JOptionPane.YES_NO_OPTION);
                if (result == JOptionPane.NO_OPTION) {
                  // bail out here
                  //return null;
                  m_unsuccessfulInstalls.add(packageToInstall);
                  continue;
                }
              }                    
            }
  
  
            // Now get a full list of dependencies for this package and
            // check for any conflicts
            Map<String, List<Dependency>> conflicts = new HashMap<String, List<Dependency>>();
            List<Dependency> dependencies = null;
            try {          
              dependencies = 
                WekaPackageManager.getAllDependenciesForPackage(packageToInstall, conflicts);
            } catch (Exception e) {
              e.printStackTrace();
              displayErrorDialog("Problem determinining dependencies for package: "
                  + packageToInstall.getName(), e);
              //return null; // can't proceed
              m_unsuccessfulInstalls.add(packageToInstall);
              continue;
            }
  
            if (conflicts.size() > 0) {
              StringBuffer message = new StringBuffer();
              message.append("Package " + packageName + " requires the following packages:\n\n");
              Iterator<Dependency> depI = dependencies.iterator();
              while (depI.hasNext()) {
                Dependency d = depI.next();
                message.append("\t" + d +"\n");
              }
  
              message.append("\nThere are conflicting dependencies:\n\n");
              Set<String> pNames = conflicts.keySet();
              Iterator<String> pNameI = pNames.iterator();
              while (pNameI.hasNext()) {
                String pName = pNameI.next();
                message.append("Conflicts for " + pName + "\n");
                List<Dependency> confsForPackage = conflicts.get(pName);
                Iterator<Dependency> confs = confsForPackage.iterator();
                while (confs.hasNext()) {
                  Dependency problem = confs.next();
                  message.append("\t" + problem + "\n");
                }
              }
  
              JOptionPane.showConfirmDialog(PackageManager.this, message.toString(), 
                  "Weka Package Manager", JOptionPane.OK_OPTION);
  
              // bail out here
              //return null;
              m_unsuccessfulInstalls.add(packageToInstall);
              continue;
            }
  
            // Next check all dependencies against what is installed and
            // inform the user about which installed packages will be altered. Also
            // build the list of only those packages that need to be installed or
            // upgraded (excluding those that are already installed and are OK).
            List<PackageConstraint> needsUpgrade = new ArrayList<PackageConstraint>();
            List<Package> finalListToInstall = new ArrayList<Package>();
  
            Iterator<Dependency> depI = dependencies.iterator();
            boolean depsOk = true;
            while (depI.hasNext()) {
              Dependency toCheck = depI.next();
              if (toCheck.getTarget().getPackage().isInstalled()) {
                String toCheckName = 
                  toCheck.getTarget().getPackage().
                  getPackageMetaDataElement("PackageName").toString();
                try {
                  Package installedVersion = WekaPackageManager.getInstalledPackageInfo(toCheckName);
                  if (!toCheck.getTarget().checkConstraint(installedVersion)) {
                    needsUpgrade.add(toCheck.getTarget());
                    Package mostRecent = toCheck.getTarget().getPackage();
                    if (toCheck.getTarget() instanceof 
                        org.pentaho.packageManagement.VersionPackageConstraint) {
                      mostRecent = 
                        WekaPackageManager.mostRecentVersionWithRespectToConstraint(toCheck.getTarget());
                    }
                    finalListToInstall.add(mostRecent);
                  }
                } catch (Exception ex) {
                  ex.printStackTrace();
                  displayErrorDialog("An error has occurred while checking " +
                      "package dependencies", ex);
                  // bail out here
                  //return null;
                  depsOk = false;
                  break;
                }
              } else {
                try {
                  Package mostRecent = toCheck.getTarget().getPackage();
                  if (toCheck.getTarget() instanceof 
                      org.pentaho.packageManagement.VersionPackageConstraint) {
                    mostRecent = 
                      WekaPackageManager.mostRecentVersionWithRespectToConstraint(toCheck.getTarget());
                  }
                  finalListToInstall.add(mostRecent);
                } catch (Exception ex) {
                  ex.printStackTrace();
                  displayErrorDialog("An error has occurred while checking " +
                      "package dependencies", ex);
                  // bail out here
                  //return null;
                  depsOk = false;
                  break;
                }
              }
            }
            
            if (!depsOk) {
              // bail out on this package
              m_unsuccessfulInstalls.add(packageToInstall);
              continue;
            }
  
            if (needsUpgrade.size() > 0) {
              StringBuffer temp = new StringBuffer();
              for (PackageConstraint pc : needsUpgrade) {
                temp.append(pc + "\n");
              }
              int result = JOptionPane.showConfirmDialog(PackageManager.this, 
                  "The following packages will be upgraded in order to install:\n\n" + temp.toString(),
                  "Weka Package Manager", JOptionPane.YES_NO_OPTION);
  
              if (result == JOptionPane.NO_OPTION) {
                // bail out here
                //return null;
                m_unsuccessfulInstalls.add(packageToInstall);
                continue;
              }
  
              // now take a look at the other installed packages and see if
              // any would have a problem when these ones are upgraded
              boolean conflictsAfterUpgrade = false;
              List<Package> installed = null;
              try {
                installed = WekaPackageManager.getInstalledPackages();
              } catch (Exception e) {
                e.printStackTrace();
                displayErrorDialog("Unable to determine what packages are installed!", e);
                //return null; // can't proceed
                m_unsuccessfulInstalls.add(packageToInstall);
                continue;
              }
              List<Package> toUpgrade = new ArrayList<Package>();
              for (PackageConstraint pc : needsUpgrade) {
                toUpgrade.add(pc.getPackage());
              }
  
  
              // add the actual package the user is wanting to install if it
              // is going to be an up/downgrade rather than a first install since
              // other installed packages may depend on the currently installed version
              // and thus could be affected after the up/downgrade
              toUpgrade.add(packageToInstall);
  
              StringBuffer tempM = new StringBuffer();
              depsOk = true;
              for (int i = 0; i < installed.size(); i++) {
                Package tempP = installed.get(i);
                String tempPName = tempP.getName();
                boolean checkIt = true;
                for (int j = 0; j < needsUpgrade.size(); j++) {
                  if (tempPName.equals(needsUpgrade.get(j).getPackage().getName())) {
                    checkIt = false;
                    break;
                  }
                }
  
                if (checkIt) {
                  List<Dependency> problem = null;
                  try {
                    problem = tempP.getIncompatibleDependencies(toUpgrade);
                  } catch (Exception e) {
                    e.printStackTrace();
                    displayErrorDialog("An error has occurred while checking " +
                        "package dependencies", e);
                    // return null; // can't continue
                    depsOk = false;
                    break;
                  }
                  if (problem.size() > 0) {
                    conflictsAfterUpgrade = true;
  
                    tempM.append("Package " + tempP.getName() + " will have a compatibility" +
                    "problem with the following packages after upgrading them:\n");
                    Iterator<Dependency> dI = problem.iterator();
                    while (dI.hasNext()) {
                      tempM.append("\t" + dI.next().getTarget().getPackage() + "\n");
                    }
                  }
                }
              }
              
              if (!depsOk) {
                m_unsuccessfulInstalls.add(packageToInstall);
                continue;
              }
  
              if (conflictsAfterUpgrade) {
                JOptionPane.showConfirmDialog(PackageManager.this, tempM.toString() + "\n"
                    + "Unable to continue with installation.", 
                    "Weka Package Manager", JOptionPane.OK_OPTION);
  
               // return null; //bail out here
                m_unsuccessfulInstalls.add(packageToInstall);
                continue;
              }
            }
  
            if (finalListToInstall.size() > 0) {
              StringBuffer message = new StringBuffer();
              message.append("To install " + packageName + " the following packages will" +
              " be installed/upgraded:\n\n");
              for (Package p : finalListToInstall) {
                message.append("\t" + p + "\n");
              }
  
              int result = JOptionPane.showConfirmDialog(PackageManager.this, 
                  message.toString(),
                  "Weka Package Manager", JOptionPane.YES_NO_OPTION);
  
              if (result == JOptionPane.NO_OPTION) {
                // bail out here
                //return null;
                m_unsuccessfulInstalls.add(packageToInstall);
                continue;
              }
              m_progress.setMaximum(m_progress.getMaximum() + (finalListToInstall.size() * 30));
            }
  
            // OK, now we can download and install everything
  
            // first install the final list of dependencies
            try {
              WekaPackageManager.installPackages(finalListToInstall, pps);
            } catch (Exception e) {
              e.printStackTrace();
              displayErrorDialog("An error has occurred while installing " +
                  "dependent packages", e);
              //return null;
              m_unsuccessfulInstalls.add(packageToInstall);
              continue;
            }
  
            // Now install the package itself
            //m_progress.setMaximum(finalListToInstall.size() * 10 + 10);
            try {
              WekaPackageManager.installPackageFromRepository(packageName, 
                  versionToInstall.toString(), pps);
            } catch (Exception e) {
              e.printStackTrace();
              displayErrorDialog("Problem installing package: " + packageName,
                  e);
              // return null;
              m_unsuccessfulInstalls.add(packageToInstall);
              continue;
            }
          } else {
            //m_progress.setMaximum(10);
            // just install this package without checking/downloading dependencies etc.
            try {
              WekaPackageManager.installPackageFromRepository(packageName, 
                  versionToInstall.toString(), pps);
            } catch (Exception e) {
              e.printStackTrace();
              displayErrorDialog("Problem installing package: " + packageName,
                  e);
              //return null;
              m_unsuccessfulInstalls.add(packageToInstall);
              continue;
            }
          }
        }
  
  //      m_successfulInstall = true;
        
        // Make sure that the new stuff is available to all GUIs
        WekaPackageManager.refreshGOEProperties();
        return null;
      }
  
      public void done() {
        m_progress.setValue(m_progress.getMinimum());
        if (m_unsuccessfulInstalls.size() == 0) {
  //      if (m_successfulInstall) {
          m_detailLabel.setText("Package(s) installed successfully.");        
        } else {
          StringBuffer failedPackageNames = new StringBuffer();
          for (Package p : m_unsuccessfulInstalls) {
            failedPackageNames.append(p.getName() + "\n");
          }
          displayErrorDialog("The following package(s) could not be installed\n"
              + "for some reason (check the log)\n" + failedPackageNames.toString()
              , "");
          m_detailLabel.setText("Install complete.");
        }
        
        m_unofficialBut.setEnabled(true);
        m_refreshCacheBut.setEnabled(true);
        m_availableBut.setEnabled(true);
        m_allBut.setEnabled(true);
        m_installedBut.setEnabled(true);
        
        // force refresh of installed and available packages
        m_installedPackages = null;
        m_availablePackages = null;
        
  //      m_installBut.setEnabled(true);
        m_installing = false;
        updateTable();
        if (m_table.getSelectedRow() >= 0) {
          // mainly to update the install/uninstall button status
          //displayPackageInfo(m_table.getSelectedRow());
          updateInstallUninstallButtonEnablement();
        }
      }
    }
    
    /*public class ComboBoxRenderer extends JComboBox implements TableCellRenderer {
      public ComboBoxRenderer(String[] items) {
        super(items);
      }
  
      public Component getTableCellRendererComponent(JTable table, Object value,
          boolean isSelected, boolean hasFocus, int row, int column) {
        if (isSelected) {
          setForeground(table.getSelectionForeground());
          super.setBackground(table.getSelectionBackground());
        } else {
          setForeground(table.getForeground());
          setBackground(table.getBackground());
        }
  
        // Select the current value
        setSelectedItem(value);
        return this;
      }
    }*/
    
    protected class ComboBoxEditor extends DefaultCellEditor {
      public ComboBoxEditor() {
        super(new JComboBox(new String[] {"one", "two"}));
      }
      
      public Component getTableCellEditorComponent(JTable table, Object value,
          boolean isSelected, int row, int column) {
        String packageName = m_table.getValueAt(row, 
            getColumnIndex(PACKAGE_COLUMN)).toString();
        List<Object> catAndVers = m_packageLookupInfo.get(packageName);
        List<Object> repVersions = (List<Object>)catAndVers.get(1);
        
        String[] versions = repVersions.toArray(new String[1]);
        Component combo = getComponent();
        if (combo instanceof JComboBox) {
          ((JComboBox)combo).setModel(new DefaultComboBoxModel(versions));
          ((JComboBox)combo).setSelectedItem(value);
        } else {
          System.err.println("Uh oh!!!!!");
        }
        return combo;
      }
    }
    
    protected boolean m_cacheEstablished = false;
    protected boolean m_cacheRefreshInProgress = false;
    public static String PAGE_HEADER = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" +
      "<html>\n<head>\n<title>Waikato Environment for Knowledge Analysis (WEKA)</title>\n" +
      "<!-- CSS Stylesheet -->\n<style>body\n{\nbackground: #ededed;\ncolor: #666666;\n" +
      "font: 14px Tahoma, Helvetica, sans-serif;;\nmargin: 5px 10px 5px 10px;\npadding: 0px;\n" +
      "}\n</style>\n\n</head>\n<body bgcolor=\"#ededed\" text=\"#666666\">\n";
    
    private static String initialPage() {
      StringBuffer initialPage = new StringBuffer();
      initialPage.append(PAGE_HEADER);
      initialPage.append("<h1>WEKA Package Manager</h1>\n\n</body></html>\n");
      return initialPage.toString();
    }
    
    protected class HomePageThread extends Thread {
      public void run() {
        try {
          m_homeB.setEnabled(false);
          m_backB.setEnabled(false);
          URLConnection conn = null;
          URL homeURL = new URL(BROWSER_HOME);
          org.pentaho.packageManagement.PackageManager pm = 
            WekaPackageManager.getUnderlyingPackageManager();
          if (pm.setProxyAuthentication()) {
            conn = homeURL.openConnection(pm.getProxy());
          } else {
            conn = homeURL.openConnection();
          }
          
          // read the html for the home page - all we want to do here is make
          // sure that the web server is responding, so that we don't tie
          // up the JEditorPane indefinitely, since there seems to be no
          // way to set a timeout in JEditorPane
          conn.setConnectTimeout(10000); // 10 seconds
          BufferedReader bi = 
            new BufferedReader(new InputStreamReader(conn.getInputStream()));
          while (bi.readLine() != null) {
            //
          }
          
          m_infoPane.setPage(BROWSER_HOME);
        } catch (Exception ex) {
          // don't make a fuss
        } finally {
          m_homeB.setEnabled(true);
          m_backB.setEnabled(true);
        }
      }
    }
    
    private int getColumnIndex(String columnName) {
      return m_table.getColumn(columnName).getModelIndex();
    }
    
    public PackageManager() {
  
      EstablishCache ec = new EstablishCache();
      ec.execute();
  
  
         
      while (!m_cacheEstablished) {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e1) {
          e1.printStackTrace();
        }
      }
      
      // first try and get the full list of packages
      getAllPackages();
                          
      setLayout(new BorderLayout());
      
      ButtonGroup bGroup = new ButtonGroup();
      bGroup.add(m_installedBut);
      bGroup.add(m_availableBut);
      bGroup.add(m_allBut);
      
      JPanel butPanel = new JPanel();
      butPanel.setLayout(new BorderLayout());
      
      JPanel packageDisplayP = new JPanel();
      packageDisplayP.setLayout(new BorderLayout());    
      JPanel packageDHolder = new JPanel();
      packageDHolder.setLayout(new FlowLayout());
      packageDHolder.add(m_installedBut);
      packageDHolder.add(m_availableBut);
      packageDHolder.add(m_allBut);
      packageDisplayP.add(packageDHolder, BorderLayout.SOUTH);
      packageDisplayP.add(m_refreshCacheBut, BorderLayout.NORTH);
      JPanel officialHolder = new JPanel();
      officialHolder.setLayout(new BorderLayout());
      officialHolder.setBorder(BorderFactory.createTitledBorder("Official"));
      officialHolder.add(packageDisplayP, BorderLayout.WEST);
      
      butPanel.add(officialHolder, BorderLayout.WEST);
      
      m_refreshCacheBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          RefreshCache r = new RefreshCache();
          r.execute();
        }
      });
      
      JPanel unofficialHolder = new JPanel();
      unofficialHolder.setLayout(new BorderLayout());
      unofficialHolder.setBorder(BorderFactory.createTitledBorder("Unofficial"));
      unofficialHolder.add(m_unofficialBut, BorderLayout.NORTH);
      butPanel.add(unofficialHolder, BorderLayout.EAST);
  
      JPanel installP = new JPanel();    
      JPanel buttP = new JPanel();
      buttP.setLayout(new GridLayout(1,2));
      installP.setLayout(new BorderLayout());
      buttP.add(m_installBut);
      buttP.add(m_uninstallBut);
      m_installBut.setEnabled(false);
      m_uninstallBut.setEnabled(false);
      installP.add(buttP, BorderLayout.NORTH);
      installP.add(m_forceBut, BorderLayout.SOUTH);
      m_forceBut.setEnabled(false);
  //    butPanel.add(installP, BorderLayout.EAST);
      officialHolder.add(installP, BorderLayout.EAST);
      
      m_installBut.setToolTipText("Install the selected official package(s) " +
      		"from the list");
      m_uninstallBut.setToolTipText("Uninstall the selected package(s) from the list");
      m_unofficialBut.setToolTipText("Install an unofficial package from a file or URL");
      m_unofficialChooser.resetFileFilters();
      m_unofficialChooser.
        addFileFilter(new ExtensionFileFilter(".zip", "Package archive file"));
  
      m_unofficialBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          if (m_unofficialFrame == null) {
            final JFrame jf = new JFrame("Unofficial package install");
            jf.addWindowListener(new WindowAdapter() {
              public void windowClosing(WindowEvent e) {
                jf.dispose();
                m_unofficialBut.setEnabled(true);
                m_unofficialFrame = null;
              }
            });
            jf.setLayout(new BorderLayout());
            JButton okBut = new JButton("OK");
            JButton cancelBut = new JButton("Cancel");
            JPanel butHolder = new JPanel();
            butHolder.setLayout(new GridLayout(1,2));
            butHolder.add(okBut);
            butHolder.add(cancelBut);
            jf.add(m_unofficialChooser, BorderLayout.CENTER);
            jf.add(butHolder, BorderLayout.SOUTH);         
            jf.pack();
            jf.setVisible(true);
            m_unofficialFrame = jf;
            m_unofficialBut.setEnabled(false);
            cancelBut.addActionListener(new ActionListener() {
              public void actionPerformed(ActionEvent e) {
                if (m_unofficialFrame != null) {
                  jf.dispose();
                  m_unofficialBut.setEnabled(true);
                  m_unofficialFrame = null;
                }
              }
            });
            
            okBut.addActionListener(new ActionListener() {
              public void actionPerformed(ActionEvent e) {
                String target = m_unofficialChooser.getText();
                UnofficialInstallTask t = new UnofficialInstallTask();
                t.setTargetToInstall(target);
                t.execute();
                if (m_unofficialFrame != null) {
                  jf.dispose();
                  m_unofficialBut.setEnabled(true);
                  m_unofficialFrame = null;
                }
              }
            });
          }
        }
      });
  
      m_installBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          int[] selectedRows = m_table.getSelectedRows();
          
          if (selectedRows.length > 0) {
  
            //int selected = m_table.getSelectedRow();
            //if (selected != -1) {
            List<String> packageNames = new ArrayList<String>();
            List<Object> versions = new ArrayList<Object>();
            StringBuffer confirmList = new StringBuffer();
            for (int i = 0; i < selectedRows.length; i++) {
              String packageName = m_table.getValueAt(selectedRows[i], 
                  getColumnIndex(PACKAGE_COLUMN)).toString();
              packageNames.add(packageName);
              Object packageVersion = m_table.getValueAt(selectedRows[i], 
                  getColumnIndex(REPOSITORY_COLUMN));
              versions.add(packageVersion);
              confirmList.append(packageName + " " + packageVersion.toString() 
                  + "\n");
            }
            
            JTextArea jt = new JTextArea("The following packages will be " +
            		"installed/upgraded:\n\n" + confirmList.toString(), 10, 40);
            int result = JOptionPane.showConfirmDialog(PackageManager.this, 
                new JScrollPane(jt), 
                "Weka Package Manager", JOptionPane.YES_NO_OPTION);
            
            if (result == JOptionPane.YES_OPTION) {
              pleaseCloseAppWindowsPopUp();
  
              InstallTask task = new InstallTask();
              task.setPackages(packageNames);
              task.setVersions(versions);
              task.execute();
            }
          }
        }
      });
      
      m_uninstallBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          // int selected = m_table.getSelectedRow();
          
          int[] selectedRows = m_table.getSelectedRows();
          
          if (selectedRows.length > 0) {
            List<String> packageNames = new ArrayList<String>();
            StringBuffer confirmList = new StringBuffer();
            
            for (int i = 0; i < selectedRows.length; i++) {
              String packageName = m_table.getValueAt(selectedRows[i], 
                  getColumnIndex(PACKAGE_COLUMN)).toString();
              Package p = null;
              try {
                p = WekaPackageManager.getRepositoryPackageInfo(packageName);
              } catch (Exception e1) {         
  //              e1.printStackTrace();
    //            continue;
                // see if we can get installed package info
                try {
                p = WekaPackageManager.getInstalledPackageInfo(packageName);
                } catch (Exception e2) {
                  e2.printStackTrace();
                  continue;
                }
              }
              
              if (p.isInstalled()) {
                packageNames.add(packageName);
                confirmList.append(packageName + "\n");
              }
            }
            
            if (packageNames.size() > 0) {
              JTextArea jt = new JTextArea("The following packages will be " +
              		"uninstalled:\n" + confirmList.toString(), 10, 40);
              int result = JOptionPane.showConfirmDialog(PackageManager.this, 
                   new JScrollPane(jt), 
                  "Weka Package Manager", JOptionPane.YES_NO_OPTION);
  
              if (result == JOptionPane.YES_OPTION) {
                pleaseCloseAppWindowsPopUp();
                UninstallTask task = new UninstallTask();
                task.setPackages(packageNames);
                task.execute();
              }
            }
          }
          
  /*        if (selected != -1) {
            String packageName = m_table.getValueAt(selected, 
                getColumnIndex(PACKAGE_COLUMN)).toString();
            
            pleaseCloseAppWindowsPopUp();
            UninstallTask task = new UninstallTask();
            task.setPackage(packageName);
            task.execute();
          } */
        }
      });
      
      JPanel progressP = new JPanel();
      progressP.setLayout(new BorderLayout());
      progressP.setBorder(BorderFactory.
          createTitledBorder("Install/Uninstall/Refresh progress"));
      progressP.add(m_progress, BorderLayout.NORTH);
      progressP.add(m_detailLabel, BorderLayout.CENTER);
      butPanel.add(progressP, BorderLayout.CENTER);
      
      JPanel topPanel = new JPanel();
      topPanel.setLayout(new BorderLayout());
  //    topPanel.setBorder(BorderFactory.createTitledBorder("Packages"));
      topPanel.add(butPanel, BorderLayout.NORTH);
      m_allBut.setSelected(true);
      
      m_allBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_table.clearSelection();
          updateTable();
          updateInstallUninstallButtonEnablement();
        }
      });
      
      m_availableBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_table.clearSelection();
          updateTable();
          updateInstallUninstallButtonEnablement();
        }
      });
      
      m_installedBut.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_table.clearSelection();
          updateTable();
          updateInstallUninstallButtonEnablement();
        }
      });
      
      m_model = 
        new DefaultTableModel(new String[] {PACKAGE_COLUMN, CATEGORY_COLUMN, 
            INSTALLED_COLUMN, REPOSITORY_COLUMN, LOADED_COLUMN}, 15) {
        
          private static final long serialVersionUID = -2886328542412471039L;
  
        public boolean isCellEditable(int row, int col) {
          if (col != 3) {
            return false;
          } else {
            return true;
          }
        }
      }; 
      
      m_table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
      m_table.setColumnSelectionAllowed(false);
      m_table.setPreferredScrollableViewportSize(new Dimension(550, 200));
      m_table.setModel(m_model);
      if (System.getProperty("os.name").contains("Mac")) {
        m_table.setShowVerticalLines(true);
      } else {
        m_table.setShowVerticalLines(false);
      }
      m_table.setShowHorizontalLines(false);    
      m_table.getColumn("Repository version").setCellEditor(new ComboBoxEditor());
      m_table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
        public void valueChanged(ListSelectionEvent e) {
          if (!e.getValueIsAdjusting() && !m_cacheRefreshInProgress) {         
            ListSelectionModel lm = (ListSelectionModel) e.getSource();
            boolean infoDisplayed = false;
            for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
              if (lm.isSelectedIndex(i)) {
                if (!infoDisplayed) {
                  // display package info for the first one in the list
                  displayPackageInfo(i);
                  infoDisplayed = true;
                  break;
                }              
              }
            }
            updateInstallUninstallButtonEnablement();
          }
        }
      });
      
      JTableHeader header = m_table.getTableHeader();
      header.addMouseListener(new MouseAdapter() {
        public void mouseClicked(MouseEvent evt) {
          TableColumnModel colModel = m_table.getColumnModel();
  
          // The index of the column whose header was clicked
          int vColIndex = colModel.getColumnIndexAtX(evt.getX());
          
          // Return if not clicked on any column header or
          // clicked on the version number cols
          if (vColIndex == -1 || vColIndex > 1) {
              return;
          }
          
          if (vColIndex == m_sortColumn) {
            // toggle the sort order
            m_reverseSort = !m_reverseSort;
          } else {
            m_reverseSort = false;
          }
          m_sortColumn = vColIndex;
          updateTable();        
        }
      });
      
      topPanel.add(new JScrollPane(m_table), BorderLayout.CENTER);
          
  //    add(topPanel, BorderLayout.NORTH);
      
  /*    m_packageDescription = new JTextArea(10,10);
      m_packageDescription.setLineWrap(true); */
      
      try {
        //m_infoPane = new JEditorPane(BROWSER_HOME);
        String initialPage = initialPage();
        m_infoPane = new JEditorPane("text/html", initialPage);
      } catch (Exception ex) {
        m_infoPane = new JEditorPane();
      }
          
      m_infoPane.setEditable(false);
      m_infoPane.addHyperlinkListener(new HyperlinkListener() {
        public void hyperlinkUpdate(HyperlinkEvent event) {
          if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
            try {
              if (event.getURL().toExternalForm().endsWith(".zip") ||
                  event.getURL().toExternalForm().endsWith(".jar")) {
                // don't render archives!
              } else {
                if (m_browserHistory.size() == 0) {
                  m_backB.setEnabled(true);
                }
                m_browserHistory.add(m_infoPane.getPage());
                m_infoPane.setPage(event.getURL());
              }
            } catch(IOException ioe) {
              
            }
          }
        }
      });
      
      //JScrollPane sp = new JScrollPane(m_packageDescription);
      //JScrollPane sp = new JScrollPane(m_infoPane);
      //sp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
      //sp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
      JPanel browserP = new JPanel();
      browserP.setLayout(new BorderLayout());
      m_backB = new JButton(new ImageIcon(loadImage("weka/gui/images/back.gif")));
      m_backB.setToolTipText("Back");
      m_backB.setEnabled(false);
      m_backB.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
      m_homeB = new JButton(new ImageIcon(loadImage("weka/gui/images/home.gif")));
      m_homeB.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
      m_homeB.setToolTipText("Home");
      m_browserTools = new JToolBar();
      m_browserTools.add(m_backB);
      m_browserTools.add(m_homeB);
      m_browserTools.setFloatable(false);
      
      // create the new packages available icon
      m_newPackagesAvailableL = 
        new JLabel(new ImageIcon(loadImage("weka/gui/images/information.gif")));
      
      // Start loading the home page
      Thread homePageThread = new HomePageThread();
      
      homePageThread.setPriority(Thread.MIN_PRIORITY);
      homePageThread.start();
      
      m_backB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          URL previous = m_browserHistory.removeLast();
          try {
            m_infoPane.setPage(previous);
            if (m_browserHistory.size() == 0) {
              m_backB.setEnabled(false);
            }
          } catch (IOException ex) {
            //
          }
        }
      });
      
      m_homeB.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          try {
            URL back = m_infoPane.getPage();
            if (back != null) {
              m_browserHistory.add(back);
            }
            
            String initialPage = initialPage();
            m_infoPane.setContentType("text/html");
            m_infoPane.setText(initialPage);          
            HomePageThread hp = new HomePageThread();
            hp.setPriority(Thread.MIN_PRIORITY);
            hp.start();
          } catch (Exception ex) {
            // don't make a fuss
          }
        }
      });
      
      browserP.add(m_browserTools, BorderLayout.NORTH);
      browserP.add(new JScrollPane(m_infoPane), BorderLayout.CENTER);
    //  add(browserP, BorderLayout.CENTER);
      
      m_splitP = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topPanel, browserP);
      m_splitP.setOneTouchExpandable(true);
  
      add(m_splitP, BorderLayout.CENTER);
  
      updateTable();
      
      // check for any new packages on the server (if possible)
      CheckForNewPackages cp = new CheckForNewPackages();
      cp.execute();
    }
    
    private void updateInstallUninstallButtonEnablement() {
      boolean enableInstall = false;
      boolean enableUninstall = false;
      
      m_unofficialBut.setEnabled(true);
      
      if (!m_installing) {
        int[] selectedRows = m_table.getSelectedRows(); 
        // check the package to see whether we should enable the
        // install button or uninstall button. Once we've determined
        // that the list contains at least one package to be installed
        // and uninstalled we don't have to check any further
  
        for (int i = 0; i < selectedRows.length; i++) {
          if (!enableInstall || !enableUninstall) {
            enableInstall = true; // we should always be able to install an already installed package
            String packageName = m_table.getValueAt(selectedRows[i], 
                getColumnIndex(PACKAGE_COLUMN)).toString();
            try {
              Package p = WekaPackageManager.getRepositoryPackageInfo(packageName);
              if (!enableUninstall) {
                enableUninstall = p.isInstalled(); 
              }
  
              /*if (!enableInstall) {
                enableInstall = !p.isInstalled();
              } */
            } catch (Exception e1) {
              // not a repository package - just enable the uninstall button
              enableUninstall = true;
              enableInstall = false;
            }
          }
        }
      } else {
        m_unofficialBut.setEnabled(false);
      }
      
      // now set the button enablement
      m_installBut.setEnabled(enableInstall);
      m_forceBut.setEnabled(enableInstall);
      m_uninstallBut.setEnabled(enableUninstall);
    }
    
    private Image loadImage(String path) {
      Image pic = null;
      URL imageURL = this.getClass().getClassLoader().getResource(path);
      if (imageURL == null) {
        // ignore
      } else {
        pic = Toolkit.getDefaultToolkit().getImage(imageURL);
      }
      
      return pic;
    }
    
    /*private String getRepVersions(String packageName, String packageVersion) {
      List<Object> repVersions = m_packageVersionsLookup.get(packageName);
      StringBuffer repString = new StringBuffer();
      
      if (repVersions.size() > 1) {
        repString.append("(");
        for (int i = 0; i < repVersions.size(); i++) {
          if (!repVersions.get(i).equals(packageVersion)) {
            repString.append(repVersions.get(i).toString());
            if (i < repVersions.size() - 1) {
              repString.append(", ");
            }
          }
        }
        repString.append(")");
      } else {
        return "";
      }
        
      return repString.toString();
    } */ 
    
    private void updateTable() {
      
      if (m_installedPackages == null || m_availablePackages == null) {
        // update the loaded status
        for (Package p : m_allPackages) {
          String loadStatus = "";
          if (p.isInstalled()) {
            File packageRoot = new File(WekaPackageManager.getPackageHome().toString()
                + File.separator + p.getName());
            boolean loaded = WekaPackageManager.loadCheck(p, packageRoot);
            loadStatus = (loaded) ? "Yes" : "No - check log";
          }
          List<Object> catAndVers = m_packageLookupInfo.get(p.getName());
          catAndVers.set(2, loadStatus);
        }      
      }
      
      if (m_allBut.isSelected()) {
        m_model.setRowCount(m_allPackages.size());
        
        Collections.sort(m_allPackages, m_packageComparator);
        int row = 0;
        for (Package p : m_allPackages) {
          m_model.setValueAt(p.getName(), row, getColumnIndex(PACKAGE_COLUMN));
          
          String category = "";
          if (p.getPackageMetaDataElement("Category") != null) {
            category = (String)p.getPackageMetaDataElement("Category");
          }
          m_model.setValueAt(category, row, getColumnIndex(CATEGORY_COLUMN));
          
          String installedV = "";
          Object repositoryV = p.getPackageMetaDataElement("Version");
          // String repString = getRepVersions(p.getName(), repositoryV);
    //      String[] repVersions = getRepVersions2(p.getName(), repositoryV);
  //        repositoryV = repositoryV + " " + repString;
          
          if (p.isInstalled()) {        
            try {
              Package installed = WekaPackageManager.getInstalledPackageInfo(p.getName());
              installedV = installed.getPackageMetaDataElement("Version").toString();            
            } catch (Exception ex) {
              ex.printStackTrace();
              displayErrorDialog("An error has occurred while trying to obtain" +
              		" installed package info", ex);
            }
          }
          m_model.setValueAt(installedV, row, getColumnIndex(INSTALLED_COLUMN));
          m_model.setValueAt(repositoryV, row, getColumnIndex(REPOSITORY_COLUMN));
          List<Object> catAndVers = m_packageLookupInfo.get(p.getName());
          String loadStatus = (String)catAndVers.get(2);
          m_model.setValueAt(loadStatus, row, getColumnIndex(LOADED_COLUMN));
          row++;
        }
        
        m_table.revalidate();
        m_table.repaint();
      } else if (m_installedBut.isSelected()) {
        try {
          if (m_installedPackages == null) {
            m_installedPackages = WekaPackageManager.getInstalledPackages();
          }
          
          m_model.setRowCount(m_installedPackages.size());
  
          int row = 0;
          for (Package p : m_installedPackages) {
            m_model.setValueAt(p.getName(), row, getColumnIndex(PACKAGE_COLUMN));
  
            String installedV = p.getPackageMetaDataElement("Version").toString();
            String category = "";
            if (p.getPackageMetaDataElement("Category") != null) {
              category = p.getPackageMetaDataElement("Category").toString();
            }                    
            
            List<Object> catAndVers = m_packageLookupInfo.get(p.getName());
            Object repositoryV = "-----";
            if (catAndVers != null) {
              // handle non-repository packages
              List<Object> repVersions = (List<Object>) catAndVers.get(1);
              repositoryV = repVersions.get(0);
            }
  //          String repString = getRepVersions(p.getName(), repositoryV);
  //          repositoryV = repositoryV + " " + repString;          
  
            m_model.setValueAt(category, row, getColumnIndex(CATEGORY_COLUMN));
            m_model.setValueAt(installedV, row, getColumnIndex(INSTALLED_COLUMN));
            m_model.setValueAt(repositoryV, row, getColumnIndex(REPOSITORY_COLUMN));
            if (catAndVers != null) {
              String loadStatus = (String)catAndVers.get(2);
              m_model.setValueAt(loadStatus, row, getColumnIndex(LOADED_COLUMN));
            } else {
              // handle non-repository packages
              File packageRoot = new File(WekaPackageManager.getPackageHome().toString()
                  + File.separator + p.getName());
              boolean loaded = WekaPackageManager.loadCheck(p, packageRoot);
              String loadStatus = (loaded) ? "Yes" : "No - check log";
              m_model.setValueAt(loadStatus, row, getColumnIndex(LOADED_COLUMN));
            }
            row++;
          }
  
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      } else {
        try {
          if (m_availablePackages == null) {
            m_availablePackages = WekaPackageManager.getAvailablePackages();
          }
          
          m_model.setRowCount(m_availablePackages.size());
          
          int row = 0;
          for (Package p : m_availablePackages) {
            m_model.setValueAt(p.getName(), row, getColumnIndex(PACKAGE_COLUMN));          
            String category = "";
            if (p.getPackageMetaDataElement("Category") != null) {
              category = p.getPackageMetaDataElement("Category").toString();
            }
            
            String installedV = "";
            List<Object> catAndVers = m_packageLookupInfo.get(p.getName());
            List<Object> repVersions = (List<Object>) catAndVers.get(1);          
            Object repositoryV = repVersions.get(0);
  //          String repString = getRepVersions(p.getName(), repositoryV);
    //        repositoryV = repositoryV + " " + repString;
  
            m_model.setValueAt(category, row, getColumnIndex(CATEGORY_COLUMN));
            m_model.setValueAt(installedV, row, getColumnIndex(INSTALLED_COLUMN));
            m_model.setValueAt(repositoryV, row, getColumnIndex(REPOSITORY_COLUMN));
            String loadStatus = (String)catAndVers.get(2);
            m_model.setValueAt(loadStatus, row, getColumnIndex(LOADED_COLUMN));
            row++; 
          }
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    }
    
    private void displayPackageInfo(int i) {
      String packageName = m_table.getValueAt(i, 
          getColumnIndex(PACKAGE_COLUMN)).toString();
  
      boolean repositoryPackage = true;
      try {
        Package repP = WekaPackageManager.getRepositoryPackageInfo(packageName);
      } catch (Exception ex) {
        repositoryPackage = false;
      }
      String versionURL = WekaPackageManager.getPackageRepositoryURL().toString() 
        + "/" + packageName + "/index.html";
      
      try {
        URL back = m_infoPane.getPage();
        if (m_browserHistory.size() == 0 && back != null) {
          m_backB.setEnabled(true);
        }
        if (back != null) {
          m_browserHistory.add(back);
        }
        
        if (repositoryPackage) {
          m_infoPane.setPage(new URL(versionURL));
        } else {
          // try and display something on this non-official package
          try {
            Package p = WekaPackageManager.getInstalledPackageInfo(packageName);
            Map<?, ?> meta = p.getPackageMetaData();
            Set<?> keys = meta.keySet();
            StringBuffer sb = new StringBuffer();
            sb.append(weka.core.RepositoryIndexGenerator.HEADER);
            sb.append("<H1>" + packageName + " (Unofficial) </H1>");
            for (Object k : keys) {
              if (!k.toString().equals("PackageName")) {
                Object value = meta.get(k);
                sb.append(k + " : " + value + "<p>");
              }
            }
            sb.append("</html>\n");
            m_infoPane.setText(sb.toString());
          } catch (Exception e) {
            // ignore
          }
        }
      } catch (Exception ex) {
        ex.printStackTrace();
      }
      
      updateInstallUninstallButtonEnablement();
      if (m_availableBut.isSelected()) {
        m_uninstallBut.setEnabled(false);
      }
      
  /*    if (m_installing) {
        m_installBut.setEnabled(false);
        m_uninstallBut.setEnabled(false);
      } else {
        m_installBut.setEnabled(true);
        if (m_availableBut.isSelected()) {
          m_uninstallBut.setEnabled(false);
        } else {
          try {
            Package p = WekaPackageManager.getRepositoryPackageInfo(packageName);
            m_uninstallBut.setEnabled(p.isInstalled());
          } catch (Exception ex) {
            m_uninstallBut.setEnabled(false);
          }
        }
      } */
    }
    
    private void getPackagesAndEstablishLookup() throws Exception {
      m_allPackages = WekaPackageManager.getAllPackages();
  
      // now fill the lookup map
      m_packageLookupInfo = new TreeMap<String, List<Object>>();
      //Iterator<Package> i = allP.iterator();
      
      for (Package p : m_allPackages) {
        // Package p = i.next();
        String packageName = p.getName();
        String category = "";
        if (p.getPackageMetaDataElement("Category") != null) {
          category = p.getPackageMetaDataElement("Category").toString();
        }
        
        // check the load status of this package (if installed)
        String loadStatus = "";
        if (p.isInstalled()) {
          File packageRoot = new File(WekaPackageManager.getPackageHome().toString());
          boolean loaded = WekaPackageManager.loadCheck(p, packageRoot);          
          loadStatus = (loaded) ? "Yes" : "No - check log";
        }
  
        List<Object> versions = 
          WekaPackageManager.getRepositoryPackageVersions(packageName);
        List<Object> catAndVers = new ArrayList<Object>();
        catAndVers.add(category); catAndVers.add(versions); catAndVers.add(loadStatus);
        m_packageLookupInfo.put(packageName, catAndVers);
      }
    }
    
    private void getAllPackages() {
      try {
        getPackagesAndEstablishLookup();
      } catch (Exception ex) {
        // warn the user that we were unable to get the list of packages
        // from the repository
        ex.printStackTrace();
        System.err.println("A problem has occurred whilst trying to get all " +
        		"package information. Trying a cache refresh...");
        WekaPackageManager.refreshCache(System.out);
        try {
          // try again
          getPackagesAndEstablishLookup();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
    
    private void displayErrorDialog(String message, Exception e) {
      java.io.StringWriter sw = new java.io.StringWriter();
      e.printStackTrace(new java.io.PrintWriter(sw));
      
      String result = sw.toString();
      displayErrorDialog(message, result);
    }
    
    private void displayErrorDialog(String message, String stackTrace) {
      Object[] options = null;
      
      if (stackTrace != null && stackTrace.length() > 0) {
        options = new Object[2];
        options[0] = "OK"; options[1] = "Show error";
      } else {
        options = new Object[1];
        options[0] = "OK";
      }
      int result = JOptionPane.showOptionDialog(this,
          message,
          "Weka Package Manager",
          JOptionPane.YES_NO_OPTION,
          JOptionPane.ERROR_MESSAGE,
          null,
          options,
          options[0]);
  
      if (result == 1) {
        JTextArea jt = new JTextArea(stackTrace, 10, 40);
        JOptionPane.showMessageDialog(PackageManager.this, new JScrollPane(jt), 
            "Weka Package Manager", JOptionPane.OK_OPTION);
      }
    }
    
    /**
     * Setting the initial placement of the divider line on a JSplitPane
     * is problematic. Most of the time it positions itself just fine based
     * on the preferred and minimum sizes of the two things it divides. However,
     * sometimes it seems to set itself such that the top component is not visible
     * without manually setting the position. This method can be called (after
     * the containing frame is visible) to set the divider location to 40% of the
     * way down the window.
     */
    public void setInitialSplitPaneDividerLocation() {
      m_splitP.setDividerLocation(0.4);
    }
    
    public static void main(String[] args) {
      weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
      LookAndFeel.setLookAndFeel();
      
      PackageManager pm = new PackageManager();    
      
      final javax.swing.JFrame jf =
        new javax.swing.JFrame("Weka Package Manager");
      jf.getContentPane().setLayout(new BorderLayout());
      jf.getContentPane().add(pm, BorderLayout.CENTER);
      jf.addWindowListener(new java.awt.event.WindowAdapter() {
        public void windowClosing(java.awt.event.WindowEvent e) {
          jf.dispose();
          System.exit(0);
        }
      });
      Dimension screenSize = jf.getToolkit().getScreenSize();
      int width = screenSize.width * 8 / 10;
      int height = screenSize.height * 8 / 10;
      jf.setBounds(width/8, height/8, width, height);
      jf.setVisible(true);
      pm.setInitialSplitPaneDividerLocation();
    }
    
  }
  