/*
* Copyright (c) 2009 The Jackson Laboratory
*
* This software was developed by Gary Churchill's Lab at The Jackson
* Laboratory (see http://research.jax.org/faculty/churchill).
*
* This 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 3 of the License, or
* (at your option) any later version.
*
* This software 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 software. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jax.qtl;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import org.jax.qtl.configuration.QtlApplicationConfigurationManager;
import org.jax.qtl.desktopOrganization.QtlMenubar;
import org.jax.qtl.project.QtlProjectManager;
import org.jax.qtl.project.gui.QtlProjectTree;
import org.jax.r.gui.ApplicationFrame;
import org.jax.r.jriutilities.RInterface;
import org.jax.r.jriutilities.RInterfaceFactory;
import org.jax.r.jriutilities.SilentRCommand;
import org.jax.r.rintegration.VersionStringComparator;
import org.jax.util.TextWrapper;
import org.jax.util.TypeSafeSystemProperties;
import org.jax.util.TypeSafeSystemProperties.OsFamily;
import org.jax.util.gui.desktoporganization.Desktop;
import org.jax.util.project.ProjectManager;
import org.rosuda.JRI.REXP;
/**
* The main application class for J/qtl
* @see QtlLauncher
* @author Lei Wu, Keith Sheppard
*/
public class QTL
{
/**
* our logger
*/
private static final Logger LOG = Logger.getLogger(
QTL.class.getName());
private static final String MINIMUM_RQTL_VERSION = "1.08";
private static QTL instance;
private final Desktop desktop;
private final QtlMenubar menubar;
private final ApplicationFrame applicationFrame;
/**
* @see #getProjectTree()
*/
private final QtlProjectTree projectTree;
/**
* Getter for the singleton QTL instance
* @return
* the singleton instance
*/
public static QTL getInstance()
{
// we have 2 if's so that we don't need to waste time synchronizing
// unless it's needed
if(QTL.instance == null)
{
synchronized(QTL.class)
{
if(QTL.instance == null)
{
QTL.instance = new QTL();
}
}
}
return QTL.instance;
}
/**
* constructor for initial screen
*/
private QTL()
{
this.desktop = new Desktop();
this.menubar = new QtlMenubar(this.desktop.getWindowMenu());
this.projectTree = new QtlProjectTree();
this.projectTree.setProjectManager(QtlProjectManager.getInstance());
this.applicationFrame = new ApplicationFrame(
"J/qtl - GUI for R/qtl ",
RInterfaceFactory.getRInterfaceInstance(),
this.menubar,
this.desktop,
this.projectTree,
QtlProjectManager.getInstance());
this.showGui();
this.initialCallsToR();
final QtlProjectManager projectManager = QtlProjectManager.getInstance();
projectManager.addPropertyChangeListener(
ProjectManager.ACTIVE_PROJECT_PROPERTY_NAME,
new PropertyChangeListener()
{
/**
* {@inheritDoc}
*/
public void propertyChange(PropertyChangeEvent evt)
{
QTL.this.getDesktop().closeAllWindows();
}
});
projectManager.addPropertyChangeListener(
ProjectManager.ACTIVE_PROJECT_FILE_PROPERTY_NAME,
new PropertyChangeListener()
{
/**
* {@inheritDoc}
*/
public void propertyChange(PropertyChangeEvent evt)
{
QtlApplicationConfigurationManager.getInstance().notifyActiveProjectFileChanged(
projectManager.getActiveProjectFile());
}
});
// Show tool tips immediately
ToolTipManager.sharedInstance().setInitialDelay(0);
// Keep the tool tip showing through the whole program
ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);
}
/**
* Getter for the main application frame
* @return
* the application frame
*/
public ApplicationFrame getApplicationFrame()
{
return this.applicationFrame;
}
/**
* Getter for the menu bar
* @return
* the menu bar
*/
public QtlMenubar getMenubar()
{
return this.menubar;
}
/**
* Getter for the desktop
* @return
* the desktop
*/
public Desktop getDesktop()
{
return this.desktop;
}
/**
* Getter for the project tree
* @return
* the project tree
*/
public QtlProjectTree getProjectTree()
{
return this.projectTree;
}
/**
* Execute some initialization calls to R.
*/
private void initialCallsToR()
{
QtlApplicationConfigurationManager configurationManager =
QtlApplicationConfigurationManager.getInstance();
configurationManager.setSaveOnExit(true);
Long rMemLimitMB =
configurationManager.getApplicationConfiguration().getRMemoryLimitMegabytes();
// intial calls to R
final RInterface rInterface = RInterfaceFactory.getRInterfaceInstance();
if(rMemLimitMB != null &&
TypeSafeSystemProperties.getOsFamily() == OsFamily.WINDOWS_OS_FAMILY)
{
String comment =
"Setting R memory ceiling " +
"(you can change this in the preferences panel)";
rInterface.insertComment(comment);
rInterface.evaluateCommandNoReturn(
"memory.limit(" + rMemLimitMB.intValue() + ")");
}
final String qtlVersionString = this.loadRQtlAndGetVersionString();
// TODO need a way of making sure this stuff doesn't get into the script
if(qtlVersionString == null)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
String message =
"It appears that the required library R/qtl is not " +
"installed. Would you like me to try installing R/qtl via " +
"the \"install.packages(...)\" command?";
String[] wrappedMessage = TextWrapper.wrapText(
message,
TextWrapper.DEFAULT_DIALOG_COLUMN_COUNT);
int response = JOptionPane.showConfirmDialog(
QTL.this.applicationFrame,
wrappedMessage,
"R/qtl Not Installed",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if(response == JOptionPane.YES_OPTION)
{
// this is long running so we shouldn't do it in the
// AWT thread
Thread installThread = new Thread(new Runnable()
{
public void run()
{
// install then validate
QTL.this.installRQtl();
QTL.this.runFinalRQtlInstallCheck();
}
});
installThread.start();
}
else
{
QTL.this.getApplicationFrame().closeApplication();
}
}
});
}
else
{
if(!this.isRQtlVersionValid(qtlVersionString))
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
String message =
"The installed version of R/qtl appears to be older " +
"than the minimum required version (" +
MINIMUM_RQTL_VERSION + "). Would you like J/qtl to " +
"attempt to replace R/qtl " + qtlVersionString +
" with an updated version?";
String[] wrappedMessage = TextWrapper.wrapText(
message,
TextWrapper.DEFAULT_DIALOG_COLUMN_COUNT);
int response = JOptionPane.showConfirmDialog(
QTL.this.applicationFrame,
wrappedMessage,
"Outdated R/qtl Version",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if(response == JOptionPane.YES_OPTION)
{
// this is long running so we shouldn't do it in the
// AWT thread
Thread installThread = new Thread(new Runnable()
{
public void run()
{
// remove, install then validate
rInterface.insertComment(
"removing outdated r/QTL version");
rInterface.evaluateCommandNoReturn(
"detach(\"qtl\")");
rInterface.evaluateCommand(
"remove.packages(\"qtl\")");
QTL.this.installRQtl();
QTL.this.runFinalRQtlInstallCheck();
}
});
installThread.start();
}
else
{
QTL.this.getApplicationFrame().closeApplication();
}
}
});
}
}
}
/**
* Check if the R/qtl install is valid and alert the user if it is not.
* It is assumed at this point that we have already tried to repair the
* installation and will not try again.
*/
private void runFinalRQtlInstallCheck()
{
String qtlVersionString = this.loadRQtlAndGetVersionString();
String errorMessage = null;
if(qtlVersionString == null)
{
errorMessage = "Attempted installation of R/qtl failed.";
}
else
{
if(!this.isRQtlVersionValid(qtlVersionString))
{
errorMessage =
"Failed to install a recent enough version of R/qtl. " +
"The installed version is " + qtlVersionString +
" but J/qtl requires at least version " +
MINIMUM_RQTL_VERSION + ".";
}
}
if(errorMessage != null)
{
// the "final" is so that we can pass this to the runnable using
// closure
final String finalErrorMessage = errorMessage;
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
String[] wrappedErrorMessage = TextWrapper.wrapText(
finalErrorMessage,
TextWrapper.DEFAULT_DIALOG_COLUMN_COUNT);
JOptionPane.showMessageDialog(
QTL.this.applicationFrame,
wrappedErrorMessage,
"R/qtl Installation Failure",
JOptionPane.ERROR_MESSAGE);
QTL.this.getApplicationFrame().closeApplication();
}
});
LOG.severe(errorMessage);
}
}
/**
* Determine if the given version is acceptable.
* @param rQtlVersion
* the R/qtl version to check
* @return
* true iff the version is good
*/
private boolean isRQtlVersionValid(String rQtlVersion)
{
VersionStringComparator versionComparator =
VersionStringComparator.getInstance();
return versionComparator.compare(rQtlVersion, MINIMUM_RQTL_VERSION) >= 0;
}
/**
* Install R/qtl using "install.packages"
*/
private void installRQtl()
{
RInterface rInterface = RInterfaceFactory.getRInterfaceInstance();
rInterface.insertComment(
"installing required library R/qtl");
rInterface.evaluateCommandNoReturn(
"install.packages(pkgs=\"qtl\", repos=\"http://cran.r-project.org\")");
}
/**
* Load R/qtl and return the version string
* @return
* the version string or null if the load fails
*/
private String loadRQtlAndGetVersionString()
{
final String qtlVersionCommand = "qtlversion()";
RInterface rInterface = RInterfaceFactory.getRInterfaceInstance();
rInterface.insertComment("load R/qtl library");
rInterface.evaluateCommandNoReturn("library(qtl)");
rInterface.insertComment("current R/qtl version");
// this one is just so that the user can see the output. the
// next one is for real
rInterface.evaluateCommandNoReturn(
qtlVersionCommand);
REXP qtlVersionExpression = rInterface.evaluateCommand(
new SilentRCommand(qtlVersionCommand));
// validate the expression and return it as a string or null
if(qtlVersionExpression == null)
{
return null;
}
else
{
String qtlVersionString = qtlVersionExpression.asString();
if(qtlVersionString == null || qtlVersionString.contains("Error"))
{
return null;
}
else
{
return qtlVersionString;
}
}
}
/**
* Create the GUI and show it.
*/
private void showGui()
{
SwingUtilities.invokeLater(new Runnable()
{
/**
* {@inheritDoc}
*/
public void run()
{
QTL.this.applicationFrame.setVisible(true);
}
});
}
/**
* The entry point for the J/QTL application.
* @param args
* command line arguments
*/
public static void main (String[] args)
{
// set the look and feel
try
{
SwingUtilities.invokeAndWait(new Runnable()
{
/**
* {@inheritDoc}
*/
public void run()
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// UIManager.setLookAndFeel("net.sourceforge.napkinlaf.NapkinLookAndFeel");
}
catch(Exception ex)
{
LOG.log(Level.WARNING, "failed to set system look-and-feel", ex);
}
}
});
}
catch(Exception ex)
{
LOG.log(Level.WARNING,
"caught exception trying to set look-and-feel",
ex);
}
QTL.writeSystemConfigurationToLog();
QTL.getInstance();
}
private static int BYTES_PER_MEGABYTE = (1<<20);
/**
* Log some system configuration information
*/
private static void writeSystemConfigurationToLog()
{
if(LOG.isLoggable(Level.FINE))
{
long maxMemoryBytes = Runtime.getRuntime().maxMemory();
if(maxMemoryBytes % BYTES_PER_MEGABYTE == 0)
{
// memory allocation is divisible by megabytes
LOG.fine(
"Maximum Java Memory: " +
(Runtime.getRuntime().maxMemory() / (BYTES_PER_MEGABYTE)) +
"MB");
}
else
{
LOG.fine(
"Maximum Java Memory: " +
Runtime.getRuntime().maxMemory() +
" bytes");
}
LOG.fine(
"java.library.path=" +
TypeSafeSystemProperties.getJavaLibraryPath());
LOG.fine("Environment Variables:");
for(Map.Entry<String, String> currEnvEntry: System.getenv().entrySet())
{
LOG.fine(currEnvEntry.getKey() + "=" + currEnvEntry.getValue());
}
}
}
}