/* * 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.r.configuration; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipInputStream; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import org.jax.r.jaxbgenerated.FileType; import org.jax.r.jaxbgenerated.LaunchRUsingType; import org.jax.r.jaxbgenerated.RApplicationConfiguration; import org.jax.r.jaxbgenerated.RApplicationStateType; import org.jax.r.jaxbgenerated.RInstallationType; import org.jax.r.jaxbgenerated.RLaunchConfigurationType; import org.jax.r.rintegration.RInstallation; import org.jax.r.rintegration.RLaunchConfiguration; import org.jax.r.rintegration.RLaunchConfiguration.LaunchUsingEnum; import org.jax.util.ConfigurationUtilities; import org.jax.util.io.FileUtilities; /** * Base class for R application configuration managers. * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A> */ public abstract class RApplicationConfigurationManager { /** * Our logger */ private static final Logger LOG = Logger.getLogger( RApplicationConfigurationManager.class.getName()); private static final int MAX_RECENT_PROJECT_HISTORY_LENGTH = 10; /** * @see #getSaveOnExit() */ private volatile boolean saveOnExit; /** * our configuration */ private RApplicationConfiguration applicationConfiguration; /** * the file containing all of our configuration data */ private File configurationFile; /** * the file containing all of our configuration data */ private File applicationStateFile; /** * the application state */ private RApplicationStateType applicationState; /** * our default config utilities */ private ConfigurationUtilities configurationUtilities; /** * the JAXB context that we're using */ private final JAXBContext jaxbContext; /** * Constructor * @param jaxbContext * the JAXB context that we should use for marshalling */ public RApplicationConfigurationManager(JAXBContext jaxbContext) { this.jaxbContext = jaxbContext; try { this.configurationUtilities = new ConfigurationUtilities(); // add a shutdown hook so that we can save away any configuration // changes that are made when this app closes Runtime.getRuntime().addShutdownHook( new Thread() { @Override public void run() { try { if(RApplicationConfigurationManager.this.saveOnExit) { if(RApplicationConfigurationManager.this.saveApplicationConfiguration()) { if(LOG.isLoggable(Level.FINE)) { LOG.fine( "completed saving application" + " configuration on exit"); } } else { LOG.severe( "attempt to save application " + "configuration on exit was " + "unsuccessfull"); } if(RApplicationConfigurationManager.this.saveApplicationState()) { if(LOG.isLoggable(Level.FINE)) { LOG.fine( "completed saving application" + "state on exit"); } } else { LOG.severe( "attempt to save application " + "state on exit was " + "unsuccessfull"); } } else { if(LOG.isLoggable(Level.FINE)) { LOG.fine( "exiting without saving" + " application configuration"); } } } catch(Exception ex) { LOG.log(Level.SEVERE, "failed to save application configuration" + " on exit", ex); } } }); this.initializeApplicationConfiguration(); } catch(Exception ex) { LOG.log(Level.SEVERE, "Failed to initialize application", ex); } } /** * Getter for the application state. This is meant to hold "session" * information that allows us to restore the application to the state * that it was in before the user last closed. * @return * the application state */ public RApplicationStateType getApplicationState() { return this.applicationState; } /** * Getter for the configuration file that should be used. * @return * the configuration file's name */ protected abstract String getConfigurationFileName(); /** * Getter for the application state file that we should use * @return * the application state file's name */ protected abstract String getApplicationStateFileName(); /** * Getter for the configuration zip resource that we should use * @return * the resource path for the zip file */ protected abstract String getConfigurationZipResourceName(); /** * Create a new application state instance * @return * the new application state instance */ protected abstract RApplicationStateType createNewApplicationState(); /** * load the application configuration or create it if it doesn't exist * @throws JAXBException * if we catch a jaxb exception while unmarshalling * @throws URISyntaxException */ private void initializeApplicationConfiguration() throws JAXBException, URISyntaxException { // create the config dir if it doesn't yet exist this.configurationFile = new File( this.configurationUtilities.getBaseDirectory(), this.getConfigurationFileName()); this.applicationStateFile = new File( this.configurationUtilities.getBaseDirectory(), this.getApplicationStateFileName()); if(!this.configurationFile.exists()) { ZipInputStream configZipIn = new ZipInputStream( this.getClass().getResourceAsStream( this.getConfigurationZipResourceName())); try { this.configurationUtilities.getBaseDirectory().mkdirs(); FileUtilities.unzipToDirectory( configZipIn, this.configurationUtilities.getBaseDirectory()); configZipIn.close(); } catch(IOException ex) { LOG.log(Level.SEVERE, "failed to restore application configuration", ex); } } Unmarshaller jaxbUnmarshaller = this.jaxbContext.createUnmarshaller(); // read in the configuration file if it exists if(this.configurationFile.isFile()) { if(LOG.isLoggable(Level.FINE)) { LOG.fine( "loading configuration file: " + this.configurationFile.getAbsolutePath()); } this.applicationConfiguration = (RApplicationConfiguration)jaxbUnmarshaller.unmarshal( this.configurationFile); } else { LOG.severe( "the configuration file is missing (or not the " + "correct file type)."); } // read in the application state if it exists if(this.applicationStateFile.isFile()) { if(LOG.isLoggable(Level.FINE)) { LOG.fine( "loading application state file: " + this.applicationStateFile.getAbsolutePath()); } this.applicationState = (RApplicationStateType)jaxbUnmarshaller.unmarshal( this.applicationStateFile); } else { if(LOG.isLoggable(Level.FINE)) { LOG.fine( "the application state file is missing, so we're " + "just going to create a new one from scratch"); } this.applicationState = this.createNewApplicationState(); } } /** * Determines whether or not we attempt to save data on * {@linkplain Runtime#addShutdownHook(Thread) virtual machine shutdown}. * @return * true iff we will try to save on exit */ public boolean getSaveOnExit() { return this.saveOnExit; } /** * Setter for {@link #saveOnExit}. * @param saveOnExit * the new value for {@link #saveOnExit} * @see #getSaveOnExit() */ public void setSaveOnExit(boolean saveOnExit) { this.saveOnExit = saveOnExit; } /** * Getter for the R application configuration. This shared instance * allows updates to be visible in all parts of the application * using this configuration. * @return * the application configuration */ public RApplicationConfiguration getApplicationConfiguration() { return this.applicationConfiguration; } /** * Convert the given jaxb R installation type to a native java type. * @param jaxbRInstallation * the jaxb type * @return * the native type */ private static RInstallation fromJaxbToNativeRInstallation( RInstallationType jaxbRInstallation) { if(jaxbRInstallation == null) { return null; } else { String rHome = jaxbRInstallation.getRHomeDirectory(); String libDir = jaxbRInstallation.getLibraryDirectory(); String version = jaxbRInstallation.getVersion(); RInstallation rInstallation = new RInstallation( new File(rHome), new File(libDir), version); return rInstallation; } } /** * Convert the given list of jaxb R installations into an array of * native R installations * @param jaxbRInstallations * the jaxb types to convert * @return * the native types */ public static RInstallation[] fromJaxbToNativeRInstallations( List<RInstallationType> jaxbRInstallations) { if(jaxbRInstallations == null) { return new RInstallation[0]; } else { RInstallation[] rInstallations = new RInstallation[jaxbRInstallations.size()]; Iterator<RInstallationType> jaxbInstalIter = jaxbRInstallations.iterator(); for(int i = 0; i < rInstallations.length; i++) { rInstallations[i] = fromJaxbToNativeRInstallation( jaxbInstalIter.next()); } return rInstallations; } } /** * Convert from a jaxb R launch config type to a native java type * @param jaxbRLaunchConfiguration * the JAXB type to convert * @return * the native java type */ public static RLaunchConfiguration fromJaxbToNativeRLaunchConfiguration( RLaunchConfigurationType jaxbRLaunchConfiguration) { if(jaxbRLaunchConfiguration == null) { return null; } else { LaunchUsingEnum nativeLaunchRUsingType; switch(jaxbRLaunchConfiguration.getLaunchRUsing()) { case ENVIRONMENT_VARIABLES: nativeLaunchRUsingType = LaunchUsingEnum.LAUNCH_USING_ENVIRONMENT; break; case SELECTED_R_INSTALLATION: nativeLaunchRUsingType = LaunchUsingEnum.LAUNCH_USING_SELECTED_INSTALLATION; break; default: LOG.severe( "missed a launch R using enum type: " + jaxbRLaunchConfiguration.getLaunchRUsing()); nativeLaunchRUsingType = null; break; } RInstallation nativeRInstallation = fromJaxbToNativeRInstallation( jaxbRLaunchConfiguration.getSelectedRInstallation()); RLaunchConfiguration nativeRLaunchConfiguration = new RLaunchConfiguration( nativeLaunchRUsingType, nativeRInstallation); return nativeRLaunchConfiguration; } } /** * Convert from a native java R installation type to a JAXB R * installation type. * @param nativeRInstallation * the native instance * @return * a JAXB instance */ private static RInstallationType fromNativeToJaxbRInstallation( RInstallation nativeRInstallation) { if(nativeRInstallation == null) { return null; } else { org.jax.r.jaxbgenerated.ObjectFactory rObjectFactory = new org.jax.r.jaxbgenerated.ObjectFactory(); RInstallationType jaxbRInstallation = rObjectFactory.createRInstallationType(); jaxbRInstallation.setLibraryDirectory( nativeRInstallation.getLibraryDirectory().getAbsolutePath()); jaxbRInstallation.setRHomeDirectory( nativeRInstallation.getRHomeDirectory().getAbsolutePath()); jaxbRInstallation.setVersion( nativeRInstallation.getRVersion()); return jaxbRInstallation; } } /** * Convert from a native java R installation array to a JAXB R * installation list * @param nativeRInstallations * the native java type * @return * the JAXB type */ public static List<RInstallationType> fromNativeToJaxbRInstallations( RInstallation[] nativeRInstallations) { if(nativeRInstallations == null) { return Collections.emptyList(); } else { // convert to and fill in the jaxb installations List<RInstallationType> jaxbDetectedRInstallations = new ArrayList<RInstallationType>( nativeRInstallations.length); for(RInstallation currNativeInstallation: nativeRInstallations) { RInstallationType currJaxbRInstallation = fromNativeToJaxbRInstallation( currNativeInstallation); jaxbDetectedRInstallations.add(currJaxbRInstallation); } return jaxbDetectedRInstallations; } } /** * Convert from a native java R launch config type to a JAXB type * @param nativeRLaunchConfiguration * the native java type * @return * the JAXB type */ public static RLaunchConfigurationType fromNativeToJaxbRLaunchConfiguration( RLaunchConfiguration nativeRLaunchConfiguration) { if(nativeRLaunchConfiguration == null) { return null; } else { org.jax.r.jaxbgenerated.ObjectFactory rObjectFactory = new org.jax.r.jaxbgenerated.ObjectFactory(); RLaunchConfigurationType jaxbRLaunchConfiguration = rObjectFactory.createRLaunchConfigurationType(); switch(nativeRLaunchConfiguration.getLaunchUsing()) { case LAUNCH_USING_ENVIRONMENT: jaxbRLaunchConfiguration.setLaunchRUsing( LaunchRUsingType.ENVIRONMENT_VARIABLES); break; case LAUNCH_USING_SELECTED_INSTALLATION: jaxbRLaunchConfiguration.setLaunchRUsing( LaunchRUsingType.SELECTED_R_INSTALLATION); break; default: LOG.severe( "missed an launch using enum type: " + nativeRLaunchConfiguration.getLaunchUsing()); break; } jaxbRLaunchConfiguration.setSelectedRInstallation( fromNativeToJaxbRInstallation( nativeRLaunchConfiguration.getSelectedInstallation())); return jaxbRLaunchConfiguration; } } /** * Save any configuration changes to the application configuration file. * @see #getApplicationConfiguration() * @return * true iff the configuration is successfully saved */ public boolean saveApplicationConfiguration() { try { // save to file FileOutputStream configFileOut = new FileOutputStream( this.configurationFile); Marshaller marshaller = this.jaxbContext.createMarshaller(); marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal( this.applicationConfiguration, configFileOut); configFileOut.close(); return true; } catch(Exception ex) { LOG.log(Level.SEVERE, "failed to save application configuration due to exception", ex); return false; } } /** * Save any configuration changes to the application state. * @see #getApplicationState() * @return * true iff the state is successfully saved */ public boolean saveApplicationState() { try { // save to file FileOutputStream applicationStateFileOut = new FileOutputStream( this.applicationStateFile); Marshaller marshaller = this.jaxbContext.createMarshaller(); marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal( this.getApplicationState(), applicationStateFileOut); applicationStateFileOut.close(); return true; } catch(Exception ex) { LOG.log(Level.SEVERE, "failed to save application configuration due to exception", ex); return false; } } /** * Tell the confguration that the active project file changed so that we * can add the new file to the recent files list * @param activeProjectFile * the new active project file */ public void notifyActiveProjectFileChanged(File activeProjectFile) { if(activeProjectFile != null) { // update the "recent project files" String absolutePath = activeProjectFile.getAbsolutePath(); List<FileType> recentProjects = this.applicationState.getRecentProjectFile(); Iterator<FileType> recentProjectsIter = recentProjects.iterator(); while(recentProjectsIter.hasNext()) { if(recentProjectsIter.next().getFileName().equals(absolutePath)) { // remove any duplicates 1st recentProjectsIter.remove(); } } org.jax.r.jaxbgenerated.ObjectFactory objectFactory = new org.jax.r.jaxbgenerated.ObjectFactory(); FileType newJaxbProjFile = objectFactory.createFileType(); newJaxbProjFile.setFileName(absolutePath); recentProjects.add(0, newJaxbProjFile); // make sure the list doesn't grow beyond the max int recentProjectsSize = recentProjects.size(); if(recentProjectsSize > MAX_RECENT_PROJECT_HISTORY_LENGTH) { for(int i = recentProjectsSize - 1; i >= MAX_RECENT_PROJECT_HISTORY_LENGTH; i--) { recentProjects.remove(i); } } } } }