/******************************************************************************* * CogTool Copyright Notice and Distribution Terms * CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * CogTool is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * CogTool 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CogTool; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * CogTool makes use of several third-party components, with the * following notices: * * Eclipse SWT version 3.448 * Eclipse GEF Draw2D version 3.2.1 * * Unless otherwise indicated, all Content made available by the Eclipse * Foundation is provided to you under the terms and conditions of the Eclipse * Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this * Content and is also available at http://www.eclipse.org/legal/epl-v10.html. * * CLISP version 2.38 * * Copyright (c) Sam Steingold, Bruno Haible 2001-2006 * This software is distributed under the terms of the FSF Gnu Public License. * See COPYRIGHT file in clisp installation folder for more information. * * ACT-R 6.0 * * Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere & * John R Anderson. * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * Apache Jakarta Commons-Lang 2.1 * * This product contains software developed by the Apache Software Foundation * (http://www.apache.org/) * * jopt-simple version 1.0 * * Copyright (c) 2004-2013 Paul R. Holser, Jr. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Mozilla XULRunner 1.9.0.5 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/. * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The J2SE(TM) Java Runtime Environment version 5.0 * * Copyright 2009 Sun Microsystems, Inc., 4150 * Network Circle, Santa Clara, California 95054, U.S.A. All * rights reserved. U.S. * See the LICENSE file in the jre folder for more information. ******************************************************************************/ package edu.cmu.cs.hcii.cogtool; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import java.util.logging.ConsoleHandler; import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import joptsimple.OptionParser; import joptsimple.OptionSet; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import edu.cmu.cs.hcii.cogtool.controller.CommandFile; import edu.cmu.cs.hcii.cogtool.controller.ControllerNexus; import edu.cmu.cs.hcii.cogtool.controller.ControllerRegistry; import edu.cmu.cs.hcii.cogtool.controller.ProjectController; import edu.cmu.cs.hcii.cogtool.controller.RootController; import edu.cmu.cs.hcii.cogtool.model.CogToolSerialization; import edu.cmu.cs.hcii.cogtool.model.Project; import edu.cmu.cs.hcii.cogtool.ui.ProjectContextSelectionState; import edu.cmu.cs.hcii.cogtool.ui.ProjectInteraction; import edu.cmu.cs.hcii.cogtool.ui.RcvrExceptionHandler; import edu.cmu.cs.hcii.cogtool.util.DelayedWorkManager; import edu.cmu.cs.hcii.cogtool.util.DelayedWorkPhase; import edu.cmu.cs.hcii.cogtool.util.ErrorDialog; import edu.cmu.cs.hcii.cogtool.util.L10N; import edu.cmu.cs.hcii.cogtool.util.OSUtils; import edu.cmu.cs.hcii.cogtool.util.ObjectPersister; import edu.cmu.cs.hcii.cogtool.util.RecoverableException; import edu.cmu.cs.hcii.cogtool.util.Subprocess; import edu.cmu.cs.hcii.cogtool.util.WindowUtil; public class CogTool { public static final int ERROR_STATUS = 1; // Don't allow instantiation. private CogTool() { } private static final String LOG_FILE_PATTERN = "/cogtool%u.%g.log"; private static final int LOG_FILE_MAX_SIZE = 1000000; private static final int LOG_FILES_MAX_COUNT = 4; private static final Level DEFAULT_LOG_LEVEL = Level.INFO; // Grab the root logger for us to have convenient access to for displaying // our state. public static final Logger logger = Logger.getLogger(""); static { logger.setLevel(DEFAULT_LOG_LEVEL); } // TODO replace a lot of our System.err.printlns and System.out.printlns // by logging calls, and consider adding logging calls in other // useful places. public static boolean projectManagesOthers = false; // persistence // TODO this variable is never accessed: should we ensure that // CogToolSerialization is loaded in some other way? public static final CogToolSerialization serialization = CogToolSerialization.ONLY; /** * Controller registration */ public static ControllerNexus<Project> controllerNexus = new ControllerNexus<Project>(); /** * Delayed selection work should be added to this phase. */ public static final DelayedWorkPhase selectionPhase = new DelayedWorkPhase(); /** * Delayed repaint work should be added to this phase. */ public static final DelayedWorkPhase repaintPhase = new DelayedWorkPhase(); /** * When delayed work is to be performed, invoke * "delayedWorkMgr.doDelayedWork(notCanceled)", where notCanceled is * <code>false</code> if the user canceled the operation, * <code>true</code> otherwise. */ public static final DelayedWorkManager delayedWorkMgr = new DelayedWorkManager(); private static final String VERSION = System.getProperty("cogtool.version"); private static String revision = System.getProperty("cogtool.revision"); private static final String BUILD_TIME = System.getProperty("cogtool.build"); private static ReportInteraction interaction = new ReportInteraction() { public void reportException(String title, String msg, Exception e) { ErrorDialog dlog = new ErrorDialog(title, msg, e); dlog.open(); } }; private static final int ERROR_HEIGHT = 450; // Set to non-null only on Macintosh; we can use this to get at // Macintosh specific functionality. See the explanation on // RootView.java. private static RootController rootCtl = null; // The exportCVSKludge doesn't work on Windows public static File exportCSVKludgeDir = null; public static boolean quietCommands = false; public static void main(String args[]) { // Do a clean exit if running on a machine with an old JRE. if (! OSUtils.supportCogTool()) { System.out.println("JRE 1.5 or later is required to run CogTool"); // TODO: add simple window with message? System.exit(1024); } if (revision == null) { // we're running under the development environment; get the revision // dynamically, and also make our subprocesses echo what's going // on to stdout revision = getRevisionAtRuntime(); Subprocess.setDebug(true); } // Insert the two phases into the delayed work manager, selection // first and repaint second, since selection can cause repaint requests delayedWorkMgr.addDelayedWork(selectionPhase); delayedWorkMgr.addDelayedWork(repaintPhase); try { if (OSUtils.MACOSX) { if (!OSUtils.isIntelMac()) { System.out.println("CogTool no longer runs on PowerPC"); System.exit(16); } // we need to create the RootController, but will never // actually need to interact with it programmatically rootCtl = new RootController(); } // TODO temporary kludge until we update the preference dialog to supply // a UI for turning this on and off CogToolPref.IS_LOGGING.setBoolean(true); enableLogging(CogToolPref.IS_LOGGING.getBoolean()); OptionParser parser = new OptionParser("f:i:re:s:qQ"); // The psn is supplied on MacOS when a GUI application is double-clicked; // we just ignore it, but need to recognize it so we can ignore it. parser.accepts("psn", "process serial number (ignored)").withRequiredArg(); OptionSet opts = parser.parse(args); if (opts.has("Q")) { quietCommands = true; } List<String> filesToLoad = new ArrayList<String>(); for (Object obj : opts.nonOptionArguments()) { filesToLoad.add((String)obj); } List<RecoverableException> loadExceptions = new ArrayList<RecoverableException>(); ProjectController openedProject = null; File file = null; if (! filesToLoad.isEmpty()) { ObjectPersister persister = ObjectPersister.ONLY; for (String fn : filesToLoad) { try { file = new File(fn); Project proj = (Project) persister.load(file); openedProject = ProjectController.openController(proj, false, true); } catch (Exception e) { RecoverableException re = new RecoverableException("Error occurred while loading: " + fn, e); RcvrExceptionHandler.recover(re, interaction); } } } if (opts.has("f")) { openedProject = (new CommandFile((String)opts.valueOf("f"))).run(); } else { if (opts.has("i")) { openedProject = ProjectController.newProjectController(); openedProject.importFile = new File((String)opts.valueOf("i")); openedProject.importFileComputes = opts.has("r"); openedProject.performAction(CogToolLID.ImportXML, new ProjectContextSelectionState(openedProject.getModel())); } if (openedProject != null && opts.has("e")) { openedProject.exportFile = (String)opts.valueOf("e"); openedProject.exportResultsToCSV(); } if (openedProject != null && opts.has("s")) { openedProject.saveAsFilename((String)opts.valueOf("s")); } if (opts.has("q")) { System.exit(0); } } if (openedProject != null) { if (System.getProperty("edu.cmu.cs.hcii.cogtool.ExportCSVKludge") != null) { exportCSVKludgeDir = file.getAbsoluteFile().getParentFile(); openedProject.exportCSVKludge(); System.exit(0); } } if (openedProject == null) { // no project was opened successfully, or none were specified, // so create a fresh project and use its Interaction to // report load errors ProjectController c = ProjectController.newProjectController(); c.setLocation(25.0, 25.0); String response = c.getInteraction().createNewOrOpenExisting(); if (response == ProjectInteraction.CREATE) { // Populate the empty project to avoid a "blank screen". c.populateProject(); } else if (response == null) { c.requestClose(); } else { if (response == ProjectInteraction.OPEN) { c.openExistingProject(c.getLocation()); } else { c.performAction(CogToolLID.OpenProjectFile, response, false); } if (ControllerRegistry.ONLY.openControllerCount() > 1) { c.requestClose(); } } } // } else if (command.equals("export-csv")) { // if (openedProject == null) { // System.err.println(String.format( // L10N.get("CT.NoFileForCSV", // "Couldn't open %s for CSV export."), // arguments.get(0))); // System.exit(32); // } // exportCSVKludgeDir = // file.getAbsoluteFile().getParentFile(); // openedProject.exportCSVKludge(); // System.exit(0); // } // Note that the following catches and does not rethrow any // SWTExceptions. This means reportTopLevelException never gets // a chance to report these to the user. WindowUtil.interact(); } catch (RecoverableException e) { RcvrExceptionHandler.recover(e, interaction); } catch (Exception e) { System.err.println("Catching exception..."); reportTopLevelAnomaly(e); } catch (Error e) { // Before trying to throw up a dialog it is important that we dump // the trace to stderr, the same way as would happen if this went // uncaught. At least that way if we are in so hosed a state that // we can't even popup the dialog we've made the patient no worse // than it would have been had we not caught this. We don't even // wait for reportTopLevelAnomoly before printing it in case just // that extra level of function call will make a difference in // whether or not we succeed. System.err.println("Catching error..."); e.printStackTrace(); reportTopLevelAnomaly(e); } catch (Throwable e) { // This shouldn't be possible -- the only unchecked Throwables // are Errors and RuntimeExceptions, both of which should have // been caught above. But just in case someone does something // deeply bizarre and has something we call in this try able to // throw a checked non-Exception Throwable, let's be extra paranoid. System.err.println("Catching throwable..."); reportTopLevelAnomaly(new Exception( L10N.get("CT.UncaughtThrowable", ("Uncaught non-Exception, non-Error Throwable " + "propagated all the way to top-level.")), e)); } // All the windows successfully closed -- quit the application. // See the comment in WindowUtil.interact() for an explanation of why // the globalDisplay might already be disposed by the time we get // here. if (! WindowUtil.GLOBAL_DISPLAY.isDisposed()) { WindowUtil.GLOBAL_DISPLAY.close(); } // Just calling the preceding is *not* sufficient to ensure we quit, // in the rare case where we've thrown a non-recoverable Exception // but left a background thread alive System.exit(-1); } /** * Attend to an Exception or Error caught at top level. * Attempts to interact with the user, presenting a stack trace and * related information. If this also throws an Exception, we just * give up in disgust, spitting out what we know to the console. * * @param e the Throwable that was caught at top level */ public static void reportTopLevelAnomaly(Throwable e) { try { final Shell window = new Shell(WindowUtil.GLOBAL_DISPLAY, SWT.TITLE | SWT.BORDER | SWT.RESIZE); window.setText(L10N.get("UnexpectedError", "Unexpected Error")); Layout layout = new GridLayout(1, false); window.setLayout(layout); Label announcement = new Label(window, SWT.LEFT); announcement.setText(L10N.get("ErrorHappened", "An unexpected error has occurred. Please contact CogTool support with the following information:")); GridData announcementLayout = new GridData(); announcementLayout.grabExcessHorizontalSpace = true; announcement.setLayoutData(announcementLayout); Text trace = new Text(window, SWT.LEFT | SWT.READ_ONLY | SWT.MULTI | SWT.V_SCROLL); StringWriter stringBuffer = new StringWriter(); String version = getVersion(); stringBuffer.write(version); stringBuffer.write('\n'); String runtimeDesc = OSUtils.runtimeDescription(); stringBuffer.write(runtimeDesc); stringBuffer.write('\n'); stringBuffer.write(getMemoryUsage()); stringBuffer.write('\n'); e.printStackTrace(new PrintWriter(stringBuffer)); // echo version and trace information to the console System.err.println(version); System.err.println(runtimeDesc); if (! (e instanceof Error)) { // If it's an Error, we've already done this e.printStackTrace(); } trace.setText(stringBuffer.toString()); int lineHeight = trace.getLineHeight(); int lineCount = trace.getLineCount(); int height = (OSUtils.MACOSX) ? ERROR_HEIGHT : Math.min(lineHeight * lineCount, ERROR_HEIGHT); GridData traceLayout = new GridData(); traceLayout.grabExcessHorizontalSpace = true; traceLayout.heightHint = height; trace.setLayoutData(traceLayout); Button okButton = new Button(window, SWT.PUSH); okButton.setText(L10N.get("B.Exit", "Exit CogTool")); okButton.setFocus(); // ignore return value from setFocus() window.setDefaultButton(okButton); GridData okLayout = new GridData(GridData.HORIZONTAL_ALIGN_CENTER); okButton.setLayoutData(okLayout); // Dismiss the dialog box when OK button selected, with success okButton.addListener(SWT.Selection, new Listener() { public void handleEvent(Event evt) { window.close(); } }); // Exits when window has been disposed. WindowUtil.displayAndInteract(window); } catch (Throwable anotherExceptionOrError) { // Again, no need to localize System.err.println("Another Exception or Error was thrown while " + "attending to one caught at top level. " + "This is a bug."); System.err.println("\n*** The new Exception or Error:"); anotherExceptionOrError.printStackTrace(); System.err.println("\n*** The original Exception or Error:"); e.printStackTrace(); System.exit(ERROR_STATUS); } } /** * Returns a description of the version of CogTool currently running. * The result begins with the word "Version: ", and typically contains the * version number, Subversion revision number, and build date. * @return a String describing the version of the running application */ public static String getVersion() { StringBuilder result = new StringBuilder(); // Build number if (VERSION != null) { result.append(VERSION); } // revision number if (revision != null) { if (result.length() > 0) { result.append(" "); } result.append("("); result.append(revision); result.append(")"); } // build time if (BUILD_TIME != null) { if (result.length() > 0) { result.append(" "); } result.append(BUILD_TIME); } if (result.length() == 0) { // The following need not be localizable since this shouldn't // normally occur. result.append("[none specified]"); } result.insert(0, L10N.get("CT.Version", "Version: ")); return result.toString(); } // end getVersion() public static boolean isBuilt() { return VERSION != null || BUILD_TIME != null; } protected static String getRevisionAtRuntime() { if (OSUtils.WINDOWS) { // don't know how to do this in windows yet return null; } List<String> command = new ArrayList<String>(); command.add("bash"); command.add("-l"); command.add("-c"); command.add("svn info"); List<String> result = new ArrayList<String>(); try { Subprocess.exec(command, result, null, null, null); for (String s : result) { if (s.startsWith("Revision")) { return s; } } } catch (RuntimeException e) { // Don't let it stop anything, but do leave some spoor on stderr // so we can at least try to figure out what went wrong. e.printStackTrace(); } return null; } public static String getMemoryUsage() { Runtime rt = Runtime.getRuntime(); long max = rt.maxMemory(); long free = rt.freeMemory(); long total = rt.totalMemory(); double used = (100.0 * (total - free)) / total; NumberFormat fmt = NumberFormat.getInstance(); fmt.setMinimumIntegerDigits(1); fmt.setMinimumFractionDigits(2); fmt.setMaximumFractionDigits(2); fmt.setGroupingUsed(true); StringBuilder result = new StringBuilder(L10N.get("CT.Memory", "Memory usage: ")); // TODO this is not properly locale sensitive, as it assumes US // conventions for percent format and lists result.append(fmt.format(used)); result.append(L10N.get("CT.PercentOf", "% of ")); result.append(fmt.format(total / 1000000.0)); result.append("; "); result.append(fmt.format(max / 1000000.0)); return result.toString(); } public static String getConfigurationProperties() { StringBuilder result = new StringBuilder(L10N.get("CT.Properies", "Props: ")); result.append(System.getProperty("user.region")); result.append(", "); result.append(System.getProperty("user.language")); result.append(", "); result.append(System.getProperty("file.encoding")); result.append(", "); result.append(System.getProperty("file.encoding.pkg")); return result.toString(); } public static void enableLogging(boolean value) { if (value) { logger.setLevel(Level.ALL); for (Handler h : logger.getHandlers()) { if (h instanceof FileHandler) { return; } } FileHandler fh = null; try { fh = new FileHandler( (CogToolPref.LOG_DIRECTORY.getString() + LOG_FILE_PATTERN), LOG_FILE_MAX_SIZE, LOG_FILES_MAX_COUNT, true); } catch (IOException ex) { logger.warning("Couldn't create log file: " + ex); return; } fh.setFormatter(new Formatter() { @Override public String format(LogRecord r) { long ms = r.getMillis(); return String.format("%tF %tT.%tL\t%s\n", ms, ms, ms, r.getMessage()); } }); logger.addHandler(fh); } else { logger.setLevel(DEFAULT_LOG_LEVEL); for (Handler h : logger.getHandlers()) { if (! (h instanceof ConsoleHandler)) { logger.removeHandler(h); } } } } }