/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.tools; import java.awt.Dimension; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowListener; import java.io.File; import java.io.InputStream; import java.io.Serializable; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import javax.swing.Action; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; import org.opensourcephysics.controls.ConsoleLevel; import org.opensourcephysics.controls.OSPLog; import org.opensourcephysics.controls.XML; import org.opensourcephysics.controls.XMLControl; import org.opensourcephysics.controls.XMLControlElement; import org.opensourcephysics.controls.XMLLoader; import org.opensourcephysics.display.OSPLayout; import org.opensourcephysics.display.OSPRuntime; /** * This is a tree node that can describe and launch an application. * * @author Douglas Brown * @version 1.0 */ public class LaunchNode extends DefaultMutableTreeNode { // static fields static final Level DEFAULT_LOG_LEVEL = ConsoleLevel.OUT_CONSOLE; // instance fields Object launchObj; // object providing xml data when arg 0 is "this" String classPath; // relative paths to jar files with launch/support classes String launchClassName; // name of class to be launched Class<?> launchClass; // class to be launched String[] args = new String[] {""}; // args passed to main method when launching //$NON-NLS-1$ boolean showLog = false; // shows OSPLog when in single vm boolean clearLog = false; // clears OSPLog if shown Level logLevel = DEFAULT_LOG_LEVEL; // OSPLog set to this level before log is shown boolean singleVM = false; // launches in current vm boolean singleVMOff = false; // allows child to override single vm of parent boolean hiddenWhenRoot = false; // hides node when at root position boolean buttonView = false; // displays node in button view when root boolean singleton = false; // allows single instance when in separate vm boolean singleApp = false; // allows single app when in single vm boolean singleAppOff = false; // allows child to override single app of parent boolean hiddenInLauncher = false; // hides node in Launcher (not LaunchBuilder) String name = ""; // display name of this node //$NON-NLS-1$ String description = ""; // description displayed if no url specified or found //$NON-NLS-1$ String tooltip = ""; // tooltip for this node //$NON-NLS-1$ String xsetName = ""; // name of xset to be loaded (splash) //$NON-NLS-1$ String author = ""; // author of the curricular activity defined by this node //$NON-NLS-1$ String keywords = ""; // key words associated with this node //$NON-NLS-1$ String level = ""; // complexity level for this node //$NON-NLS-1$ String languages = ""; // languages associated with this node //$NON-NLS-1$ String comment = ""; // comments about this node //$NON-NLS-1$ String appletWidth = ""; // preferred applet width //$NON-NLS-1$ String appletHeight = ""; // preferred applet height //$NON-NLS-1$ ArrayList<DisplayTab> tabData = new ArrayList<DisplayTab>(); // list of display tabs private String fileName; // xml file name relative to tabset base Collection<Process> processes = new HashSet<Process>(); // processes launched by this node Collection<Frame> frames = new HashSet<Frame>(); // frames launched by this node Collection<Action> actions = new HashSet<Action>(); // terminateActions to be performed by this node Map<Runnable, Thread> threads = new HashMap<Runnable, Thread>(); int launchCount = 0; LaunchPanel launchPanel; boolean selfContained; boolean parentSelfContained; boolean previewing; boolean saveHiddenNodes; boolean enabled = true; List<String> jars = new ArrayList<String>(); List<String> pdf = new ArrayList<String>(); // following used for undoable NavEdits int tabNumber = -1; // current tab number, or -1 if no tabs int prevTabNumber = -1; // previous tab number URL htmlURL; // current URL--may be null URL prevURL; // previous URL JScrollPane launchModelScroller; /** * Constructs a node with the specified name. * * @param name the name */ public LaunchNode(String name) { setUserObject(this); if(name!=null) { this.name = name; } } /** * Signals that a launch thread for this node is about to start or end. * * @param starting true if the thread is starting */ public void threadRunning(boolean starting) { launchCount += starting ? +1 : -1; launchCount = Math.max(0, launchCount); if(launchPanel!=null) { launchPanel.repaint(); } } /** * Launches this node. */ public void launch() { launch(null); } /** * Launches this node from the specified launch panel. * * @param tab the launch panel */ public void launch(LaunchPanel tab) { if(!isLeaf()) { return; } launchPanel = tab; OSPRuntime.launchingInSingleVM = this.isSingleVM(); Launcher.singleAppMode = this.isSingleApp(); Launcher.classPath = getClassPath(); // in node-to-root order if(isShowLog()&&isSingleVM()) { OSPLog.setLevel(getLogLevel()); OSPLog log = OSPLog.getOSPLog(); if(isClearLog()) { log.clear(); } log.setVisible(true); } setMinimumArgLength(1); // trim args if nec String arg0 = args[0]; if(getLaunchClass()!=null) { if(arg0.equals("this")) { //$NON-NLS-1$ Object launchObj = getLaunchObject(); if(launchObj!=null) { // replace with xml from launch object, if any XMLControl control = new XMLControlElement(launchObj); args[0] = control.toXML(); } else { args[0] = ""; //$NON-NLS-1$ } } if(args[0].equals("")&&(args.length==1)) { //$NON-NLS-1$ Launcher.launch(getLaunchClass(), null, this); } else { Launcher.launch(getLaunchClass(), args, this); } } args[0] = arg0; } /** * Returns the nearest ancestor with a non-null file name. * This node is returned if it has a non-null file name. * * @return the file node */ public LaunchNode getOwner() { if(fileName!=null) { return this; } if(getParent()!=null) { return((LaunchNode) getParent()).getOwner(); } return null; } /** * Returns all descendents of this node with non-null file names. * This node is not included. * * @return an array of launch nodes */ public LaunchNode[] getAllOwnedNodes() { Collection<LaunchNode> nodes = new ArrayList<LaunchNode>(); Enumeration<?> e = breadthFirstEnumeration(); while(e.hasMoreElements()) { LaunchNode next = (LaunchNode) e.nextElement(); if((next.fileName!=null)&&(next!=this)) { nodes.add(next); } } return nodes.toArray(new LaunchNode[0]); } /** * Returns the nearest descendents of this node with non-null file names. * This node is not included. * * @return an array of launch nodes */ public LaunchNode[] getChildOwnedNodes() { Collection<LaunchNode> nodes = new ArrayList<LaunchNode>(); LaunchNode[] owned = getAllOwnedNodes(); LaunchNode owner = getOwner(); for(int i = 0; i<owned.length; i++) { LaunchNode next = ((LaunchNode) owned[i].getParent()).getOwner(); if(next==owner) { nodes.add(owned[i]); } } return nodes.toArray(new LaunchNode[0]); } /** * Returns a string used as a display name for this node. * * @return the string name of this node */ public String toString() { // return name, if any if((name!=null)&&!name.equals("")) { //$NON-NLS-1$ return name; } // return launch class name, if any if(launchClassName!=null) { return XML.getExtension(launchClassName); } // return args[0] name, if any if(!args[0].equals("")) { //$NON-NLS-1$ String name = args[0]; name = XML.getName(name); return XML.stripExtension(name); } return ""; //$NON-NLS-1$ } /** * Gets the unique ID string for this node. * * @return the ID string */ public String getID() { return String.valueOf(this.hashCode()); } /** * Sets the name of this node. * * @param name the name */ public void setName(String name) { this.name = name; } /** * Gets the name of this node. * * @return the name */ public String getName() { return name; } /** * Sets the description of this node. * * @param desc the description */ public void setDescription(String desc) { description = desc; } /** * Gets the description of this node. * * @return the description */ public String getDescription() { return description; } /** * Sets the launch arguments of this node. * * @param args the arguments */ public void setArgs(String[] args) { if((args!=null)&&(args.length>0)&&(args[0]!=null)) { this.args = args; } } /** * Gets the launch arguments of this node. * * @return the launch arguments */ public String[] getArgs() { return args; } /** * Sets the tooltip of this node * * @param _tooltip the tooltip */ public void setTooltip(String _tooltip) { tooltip = _tooltip; } /** * Gets the tooltip of this node. * * @return the tooltip */ public String getTooltip() { return tooltip; } /** * Sets the author of this node * * @param _author the author */ public void setAuthor(String _author) { author = _author; } /** * Gets the author. * * @return the first non-null author of this or an ancestor */ public String getAuthor() { if(!author.equals("")) { //$NON-NLS-1$ return author; } else if(isRoot()) { return ""; //$NON-NLS-1$ } LaunchNode parent = (LaunchNode) getParent(); return parent.getAuthor(); } /** * Sets the keywords of this node * * @param _keywords the keywords */ public void setKeyword(String _keywords) { keywords = _keywords; } /** * Gets the keywords of this node or ancestor. * * @return the keywords */ public String getKeywords() { if(!keywords.equals("")) { //$NON-NLS-1$ return keywords; } else if(isRoot()) { return ""; //$NON-NLS-1$ } LaunchNode parent = (LaunchNode) getParent(); return parent.getKeywords(); } /** * Sets the comment of this node * * @param _comment the comment */ public void setComment(String _comment) { comment = _comment; } /** * Gets the comment of this node. * * @return the comment */ public String getComment() { return comment; } /** * Sets the preferred applet width of this node * * @param _width the width */ public void setPreferredAppletWidth(String _width) { this.appletWidth = _width; } /** * Gets the preferred applet width of this node. * * @return the width */ public String getPreferredAppletWidth() { return this.appletWidth; } /** * Sets the preferred applet height of this node * * @param _height the height */ public void setPreferredAppletHeight(String _height) { this.appletHeight = _height; } /** * Gets the preferred applet height of this node. * * @return the height */ public String getPreferredAppletHeight() { return this.appletHeight; } /** * Sets the course level of this node * * @param _level the level */ public void setCourseLevel(String _level) { level = _level; } /** * Gets the course level of this node or ancestor. * * @return the level */ public String getCourseLevel() { if(!level.equals("")) { //$NON-NLS-1$ return level; } else if(isRoot()) { return ""; //$NON-NLS-1$ } LaunchNode parent = (LaunchNode) getParent(); return parent.getCourseLevel(); } /** * Sets the languages for which translations exist for this node * * @param _lang the languages */ public void setLanguages(String _lang) { languages = _lang; } /** * Gets the languages for which translations exist for this node or ancestor. * * @return the languages */ public String getLanguages() { if(!languages.equals("")) { //$NON-NLS-1$ return languages; } else if(isRoot()) { return ""; //$NON-NLS-1$ } LaunchNode parent = (LaunchNode) getParent(); return parent.getLanguages(); } /** * Gets the complete class path in node-to-root order. * If Launcher is running from a jar, that jar is in every classpath. * * @return the class path */ public String getClassPath() { String path = ""; //$NON-NLS-1$ if(classPath!=null) { path += classPath; } LaunchNode node = this; while(!node.isRoot()) { node = (LaunchNode) node.getParent(); if(node.classPath!=null) { if(!path.equals("")) { //$NON-NLS-1$ path += ";"; //$NON-NLS-1$ } path += node.classPath; } } if(!path.equals("")) { //$NON-NLS-1$ // eliminate duplicate jars jars.clear(); String next = path; int i = path.indexOf(";"); //$NON-NLS-1$ if(i==-1) { i = path.indexOf(":"); //$NON-NLS-1$ } if(i!=-1) { next = path.substring(0, i); path = path.substring(i+1); } else { path = ""; //$NON-NLS-1$ } // iterate thru path and add each unique jar to jars list while(next.length()>0) { if(!jars.contains(next)) { jars.add(next); } i = path.indexOf(";"); //$NON-NLS-1$ if(i==-1) { i = path.indexOf(":"); //$NON-NLS-1$ } if(i==-1) { next = path.trim(); path = ""; //$NON-NLS-1$ } else { next = path.substring(0, i).trim(); path = path.substring(i+1).trim(); } } // reconstruct clean path using semicolon delimiters Iterator<String> it = jars.iterator(); while(it.hasNext()) { if(!path.equals("")) { //$NON-NLS-1$ path += ";"; //$NON-NLS-1$ } path += it.next(); } } // add ResourceLoader.launchJarName, if any, unless base directory path has been set if(OSPRuntime.getLaunchJarName()!=null && path.indexOf(OSPRuntime.getLaunchJarName())==-1 && LaunchClassChooser.baseDirectoryPath==null) { if(!path.equals("")) { //$NON-NLS-1$ path += ";"; //$NON-NLS-1$ } path += OSPRuntime.getLaunchJarName(); } return path; } /** * Sets the class path (relative jar or directory paths separated by colons or semicolons). * * @param jarNames the class path */ public void setClassPath(String jarNames) { if((jarNames==null)||jarNames.equals("")) { //$NON-NLS-1$ classPath = null; return; } // eliminate extra colons and semicolons while(jarNames.startsWith(":")||jarNames.startsWith(";")) { //$NON-NLS-1$ //$NON-NLS-2$ jarNames = jarNames.substring(1); } while(jarNames.endsWith(":")||jarNames.endsWith(";")) { //$NON-NLS-1$ //$NON-NLS-2$ jarNames = jarNames.substring(0, jarNames.length()-1); } String s = jarNames; int i = jarNames.indexOf(";;"); //$NON-NLS-1$ if(i==-1) { i = jarNames.indexOf("::"); //$NON-NLS-1$ } if(i==-1) { i = jarNames.indexOf(":;"); //$NON-NLS-1$ } if(i==-1) { i = jarNames.indexOf(";:"); //$NON-NLS-1$ } while(i>-1) { jarNames = jarNames.substring(0, i+1)+s.substring(i+2); s = jarNames; i = jarNames.indexOf(";;"); //$NON-NLS-1$ if(i==-1) { i = jarNames.indexOf("::"); //$NON-NLS-1$ } if(i==-1) { i = jarNames.indexOf(":;"); //$NON-NLS-1$ } if(i==-1) { i = jarNames.indexOf(";:"); //$NON-NLS-1$ } } classPath = jarNames; } /** * Returns the name of the class to launch * @return the class name */ public String getLaunchClassName() { return this.launchClassName; } /** * Sets the launch class for this node. * * @param className the name of the class * @return true if the class was successfully loaded for the first time */ public boolean setLaunchClass(String className) { if(className==null) { return false; } if((launchClassName==className)&&(launchClass!=null)) { return false; } launchModelScroller = null; launchClassName = className; launchClass = LaunchClassChooser.getClass(getClassPath(), className); OSPLog.finest("node "+getName()+": "+LaunchRes.getString("Log.Message.SetLaunchClass") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ +" "+className+(launchClass==null? " (not found!)": "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return launchClass!=null; } /** * Gets the launch class for this node. * * @return the launch class */ public Class<?> getLaunchClass() { if((launchClass==null)&&(launchClassName!=null)&&!launchClassName.equals("")) { //$NON-NLS-1$ setLaunchClass(launchClassName); } return launchClass; } /** * Gets the launch object. May be null. * * @return the launch object */ public Object getLaunchObject() { if(launchObj!=null) { return launchObj; } else if(isRoot()) { return null; } LaunchNode node = (LaunchNode) getParent(); return node.getLaunchObject(); } /** * Sets the launch object. * * @param obj the launch object */ public void setLaunchObject(Object obj) { launchObj = obj; } /** * Sets the specified display tab title and url path. * Specifying a tab number greater than the current tab count adds a new tab. * Setting a path to null removes the tab. * * @param n the tab number * @param title the tab title * @param path the path. * @param args model arguments. May be null. * @return the DisplayTab, or null if unsuccessful */ public DisplayTab setDisplayTab(int n, String title, String path, String[] args) { if(n>=tabData.size()) { // add a new tab return addDisplayTab(title, path, args); } else if((path==null)||path.equals("")) { //$NON-NLS-1$ // remove the tab return removeDisplayTab(n); } else { // change the tab DisplayTab tab = tabData.get(n); tab.title = title; tab.setPath(path); tab.setModelArgs(args); OSPLog.finest("tab "+n+" changed: [\""+title+"\", \""+path+"\"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ return tab; } } /** * Adds a display tab with the specified title and relative path. * No tab is added if path is null or "". * * @param title the tab title. May be null. * @param path the path. * @param args model arguments. May be null. * @return the newly created DisplayTab, if any */ public DisplayTab addDisplayTab(String title, String path, String[] args) { if((path==null)||path.equals("")) { //$NON-NLS-1$ return null; } // assemble DisplayTab and add to tabData DisplayTab tab = new DisplayTab(title, path); tab.setModelArgs(args); tabData.add(tab); OSPLog.finest("tab added: [\""+title+"\", \""+path+"\"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return tab; } /** * Inserts a display tab with the specified title and relative path. * No tab is inserted if path is null or "", or if the specified tab index * is invalid. * * @param n the insertion index * @param title the tab title. May be null. * @param path the path. * @param args model arguments. May be null. * @return the newly created DisplayTab, if any */ public DisplayTab insertDisplayTab(int n, String title, String path, String[] args) { if((path==null)||path.equals("")||(n>=tabData.size())) { //$NON-NLS-1$ return null; } // assemble tab and insert into tabData DisplayTab tab = new DisplayTab(title, path); tab.setModelArgs(args); tabData.add(n, tab); OSPLog.finest("tab inserted: [\""+title+"\", \""+path+"\"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return tab; } /** * Removes a display tab. * * @param n the tab number * @return the DisplayTab removed, or null if none removed */ public DisplayTab removeDisplayTab(int n) { DisplayTab tab = getDisplayTab(n); if(tab!=null) { tabData.remove(tab); OSPLog.finest("tab "+n+" removed: [\""+ //$NON-NLS-1$ //$NON-NLS-2$ tab.title+"\", \""+tab.path+"\"]"); //$NON-NLS-1$ //$NON-NLS-2$ } return tab; } /** * Gets a display tab. * * @param n the tab number * @return the DisplayTab, or null if none found */ public DisplayTab getDisplayTab(int n) { // n<0 added by W. Christian if(n<0 || n>=tabData.size()) { return null; } return tabData.get(n); } /** * Returns the number of display tabs for this node. * @return the tab count */ public int getDisplayTabCount() { return tabData.size(); } /** * Gets the list of pdf documents associated with this node. * @return a list of PDF documents (may be empty) */ public List<String> getPDFPaths() { int n = getDisplayTabCount(); pdf.clear(); for (int i = 0; i<n; i++) { String path = getDisplayTab(i).getPath(); if (path.toLowerCase().endsWith(".pdf")) { //$NON-NLS-1$ pdf.add(path); } } return pdf; } /** * Gets the fileName. * * @return the fileName, assumed to be a relative path */ public String getFileName() { return fileName; } /** * Gets the string path to this node, starting from the root. * This returns the names of the nodes in the path, separated by "/". * * @return the path */ public String getPathString() { TreeNode[] nodes = getPath(); LaunchNode next = (LaunchNode) nodes[0]; StringBuffer path = new StringBuffer(next.name); for(int i = 1; i<nodes.length; i++) { next = (LaunchNode) nodes[i]; path.append("/"+next.name); //$NON-NLS-1$ } return path.toString(); } /** * Sets the fileName. Accepts relative paths or * will convert absolute paths to relative. * * @param path the path to the file * @return the file name */ public String setFileName(String path) { if(path==null) { fileName = null; } else { fileName = XML.getPathRelativeTo(path, Launcher.tabSetBasePath); } return fileName; } /** * Gets the parentSelfContained flag. * * @return true if parentSelfContained is true for this or an ancestor */ public boolean isParentSelfContained() { if(parentSelfContained) { return true; } else if(isRoot()) { return false; } LaunchNode parent = (LaunchNode) getParent(); return parent.isSelfContained(); } /** * Gets the selfContained flag. * * @return true if selfContained is true for this or an ancestor */ public boolean isSelfContained() { if(selfContained||isParentSelfContained()) { return true; } return false; } /** * Gets the previewing flag. * * @return true if previewing is true for this or an ancestor */ public boolean isPreviewing() { if(previewing) { return true; } else if(isRoot()) { return false; } LaunchNode node = (LaunchNode) getParent(); return node.isPreviewing(); } /** * Gets the saveHiddenNodes flag. * * @return true if saveHiddenNodes is true for this or an ancestor */ public boolean isSavingHiddenNodes() { if(saveHiddenNodes) { return true; } else if(isRoot()) { return false; } LaunchNode node = (LaunchNode) getParent(); return node.isSavingHiddenNodes(); } /** * Sets the selfContained flag. * * @param selfContained true if self contained */ public void setSelfContained(boolean selfContained) { this.selfContained = selfContained; } /** * Gets the singleVM flag. * * @return true if singleVM is true for this or an ancestor */ public boolean isSingleVM() { if(singleVM||org.opensourcephysics.display.OSPRuntime.isWebStart()) { return true; } else if(isRoot()) { return false; } LaunchNode parent = (LaunchNode) getParent(); return singleVMOff ? false : parent.isSingleVM(); } /** * Sets the single VM flag. * * @param singleVM true if single vm */ public void setSingleVM(boolean singleVM) { this.singleVM = singleVM; } /** * Gets the showLog value. * * @return true if showLog is true for this or an ancestor */ public boolean isShowLog() { if(showLog) { return true; } else if(isRoot()) { return false; } LaunchNode parent = (LaunchNode) getParent(); return parent.isShowLog(); } /** * Sets the showLog flag. * * @param show true to show the OSPLog (single vm only) */ public void setShowLog(boolean show) { showLog = show; } /** * Gets the clearLog value. * * @return true if clearLog is true for this or an ancestor */ public boolean isClearLog() { if(clearLog) { return true; } else if(isRoot()) { return false; } LaunchNode parent = (LaunchNode) getParent(); return parent.isClearLog(); } /** * Sets the showLog flag. * * @param clear true to clear the OSPLog (single vm only) */ public void setClearLog(boolean clear) { clearLog = clear; } /** * Gets the log level. * * @return the level */ public Level getLogLevel() { if(isRoot()) { return logLevel; } LaunchNode parent = (LaunchNode) getParent(); if(parent.isShowLog()) { Level parentLevel = parent.getLogLevel(); if(parentLevel.intValue()<logLevel.intValue()) { return parentLevel; } } return logLevel; } /** * Sets the log level. * * @param level the level */ public void setLogLevel(Level level) { if(level!=null) { logLevel = level; } } /** * Gets the singleApp value. * * @return true if singleApp is true for this or an ancestor */ public boolean isSingleApp() { if(singleApp) { return true; } else if(isRoot()) { return false; } LaunchNode parent = (LaunchNode) getParent(); return singleAppOff ? false : parent.isSingleApp(); } /** * Sets the singleApp flag. * * @param singleApp true to close other apps when launching new app (single vm only) */ public void setSingleApp(boolean singleApp) { this.singleApp = singleApp; } /** * Sets the hiddenWhenRoot flag. * * @param hide true to hide node when at root */ public void setHiddenWhenRoot(boolean hide) { hiddenWhenRoot = hide; } /** * Gets the buttonView value. * * @return true if buttonView is true for this or an ancestor */ public boolean isButtonView() { if(buttonView) { return true; } else if(isRoot()) { return false; } LaunchNode parent = (LaunchNode) getParent(); return parent.isButtonView(); } /** * Sets the buttonView flag. * * @param buttonView true to display in buttonView */ public void setButtonView(boolean buttonView) { LaunchNode root = (LaunchNode) getRoot(); root.buttonView = buttonView; } /** * Gets the singleton value. * * @return true if singleApp is true for this or an ancestor */ public boolean isSingleton() { if(singleton) { return true; } else if(isRoot()) { return false; } LaunchNode parent = (LaunchNode) getParent(); return parent.isSingleton(); } /** * Sets the singleton flag. * * @param singleton true to allow single instance when in separate vm */ public void setSingleton(boolean singleton) { this.singleton = singleton; } /** * Gets the hiddenInLauncher value. * * @return true if hiddenInLauncher is true for this or an ancestor */ public boolean isHiddenInLauncher() { if(hiddenInLauncher) { return true; } else if(isRoot()) { return false; } LaunchNode parent = (LaunchNode) getParent(); return parent.isHiddenInLauncher(); } /** * Sets the hiddenInLauncher flag. * * @param hide true to hide this node in Launcher */ public void setHiddenInLauncher(boolean hide) { hiddenInLauncher = hide; } /** * Gets the resource, if any, for this node * * @return the resource */ public Resource getResource() { if(fileName==null) { return null; } String path = XML.getResolvedPath(fileName, Launcher.tabSetBasePath); return ResourceLoader.getResource(path); } /** * Determines whether a resource exists for this node. * * @return true if a resource exists */ public boolean exists() { return(getResource()!=null); } /** * Return an existing file with current file name and specified base. * May return null. * * @return existing file, or null */ public File getFile() { if(exists()) { return getResource().getFile(); } return null; } /** * Determines if this node matches another node. * * @param node the node to match * @return true if the nodes match */ public boolean matches(LaunchNode node) { if(node==null) { return false; } boolean match = (showLog==node.showLog)&&(clearLog==node.clearLog)&&(singleton==node.singleton)&&(singleVM==node.singleVM)&&(hiddenWhenRoot==node.hiddenWhenRoot)&&name.equals(node.name)&&description.equals(node.description)&&args[0].equals(node.args[0])&&(((fileName==null)&&(node.fileName==null))||((fileName!=null)&&fileName.equals(node.fileName)))&&(((getLaunchClass()==null)&&(node.getLaunchClass()==null))||((getLaunchClass()!=null)&&getLaunchClass().equals(node.getLaunchClass())))&&(((classPath==null)&&(node.classPath==null))||((classPath!=null)&&classPath.equals(node.classPath))); return match; } /** * Gets a child node specified by fileName. * * @param childFileName the file name of the child * @return the first child found, or null */ public LaunchNode getChildNode(String childFileName) { Enumeration<?> e = breadthFirstEnumeration(); while(e.hasMoreElements()) { LaunchNode next = (LaunchNode) e.nextElement(); if(childFileName.equals(next.fileName)) { return next; } } return null; } /** * Adds menu item to a JPopupMenu or JMenu. * * @param menu the menu */ public void addMenuItemsTo(final JComponent menu) { Enumeration<?> e = children(); while(e.hasMoreElements()) { LaunchNode child = (LaunchNode) e.nextElement(); if(child.isLeaf()) { JMenuItem item = new JMenuItem(child.toString()); menu.add(item); item.setToolTipText(child.tooltip); item.setActionCommand(child.getID()); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String id = e.getActionCommand(); LaunchNode root = (LaunchNode) LaunchNode.this.getRoot(); Enumeration<?> e2 = root.postorderEnumeration(); while(e2.hasMoreElements()) { LaunchNode node = (LaunchNode) e2.nextElement(); if(node.getID().equals(id)) { node.launch(); break; } } } }); } else { JMenu item = new JMenu(child.toString()); menu.add(item); child.addMenuItemsTo(item); } } } /** * Adds an action to this node's actions collection. * * @param action the action to add */ public void addTerminateAction(Action action) { actions.add(action); launchCount++; } /** * Removes an action from this node's actions collection. * * @param action the action to remove */ public void removeTerminateAction(Action action) { actions.remove(action); launchCount = Math.max(0, --launchCount); } /** * Removes an action from this node's actions collection. * * @param action the action to remove */ public void terminate(Action action) { if(actions.contains(action)) { removeTerminateAction(action); if(launchPanel!=null) { launchPanel.repaint(); } } } /** * Terminates all apps launched by this node. */ public void terminateAll() { for(Iterator<Process> it = processes.iterator(); it.hasNext(); ) { Process proc = it.next(); proc.destroy(); } for(Iterator<Frame> it = frames.iterator(); it.hasNext(); ) { Frame frame = it.next(); WindowListener[] listeners = frame.getWindowListeners(); for(int j = 0; j<listeners.length; j++) { if(listeners[j] instanceof Launcher.FrameCloser) { frame.removeWindowListener(listeners[j]); } } frame.dispose(); } for(Iterator<Thread> it = threads.values().iterator(); it.hasNext(); ) { Thread thread = it.next(); if(thread!=null) { thread.interrupt(); } } Collection<Action> allActions = new HashSet<Action>(actions); for(Iterator<Action> it = allActions.iterator(); it.hasNext(); ) { Action action = it.next(); if(action!=null) { action.actionPerformed(null); } } processes.clear(); frames.clear(); threads.clear(); actions.clear(); launchCount = 0; } /** * Gets the launchModelScroller. May return null. * * @return the scroller containing the modelPane from the launch class */ protected JScrollPane getLaunchModelScroller() { if(launchModelScroller==null) { final JComponent content = Launcher.getModelPane(getLaunchClass(), getArgs()); if(content==null) { return null; } JPanel panel = new JPanel(new OSPLayout()) { public Dimension getPreferredSize() { Dimension dim = content.getPreferredSize(); dim.width += 8; dim.height += 8; return dim; } }; panel.setBackground(java.awt.Color.white); panel.add(content, OSPLayout.CENTERED); launchModelScroller = new JScrollPane(panel); } return launchModelScroller; } /** * Returns the XML.ObjectLoader for this class. * * @return the object loader */ public static XML.ObjectLoader getLoader() { return new Loader(); } /** * A class to save and load LaunchNode data in an XMLControl. */ private static class Loader extends XMLLoader { public void saveObject(XMLControl control, Object obj) { LaunchNode node = (LaunchNode) obj; node.setMinimumArgLength(1); // trim args if nec if(!node.name.equals("")) { //$NON-NLS-1$ control.setValue("name", node.name); //$NON-NLS-1$ } if(!node.description.equals("")) { //$NON-NLS-1$ control.setValue("description", node.description); //$NON-NLS-1$ } if(!node.tooltip.equals("")) { //$NON-NLS-1$ control.setValue("tooltip", node.tooltip); //$NON-NLS-1$ } if(!node.xsetName.equals("")) { //$NON-NLS-1$ control.setValue("launchset", node.xsetName); //$NON-NLS-1$ } control.setValue("display_tabs", node.getDisplayData()); //$NON-NLS-1$ control.setValue("display_args", node.getDisplayArgs()); //$NON-NLS-1$ if(node.getLaunchClass()!=null) { control.setValue("launch_class", node.getLaunchClass().getName()); //$NON-NLS-1$ } else if(node.launchClassName!=null) { control.setValue("launch_class", node.launchClassName); //$NON-NLS-1$ } if(!node.args[0].equals("")||(node.args.length>1)) { //$NON-NLS-1$ control.setValue("launch_args", node.args); //$NON-NLS-1$ } if((node.classPath!=null)&&!node.classPath.equals("")) { //$NON-NLS-1$ control.setValue("classpath", node.classPath); //$NON-NLS-1$ } if(node.hiddenWhenRoot) { control.setValue("root_hidden", true); //$NON-NLS-1$ } if(node.buttonView) { control.setValue("button_view", true); //$NON-NLS-1$ } if(node.singleton) { control.setValue("singleton", true); //$NON-NLS-1$ } if(node.singleVM) { control.setValue("single_vm", true); //$NON-NLS-1$ } if(node.singleVMOff) { control.setValue("single_vm_off", true); //$NON-NLS-1$ } if(node.showLog) { control.setValue("show_log", true); //$NON-NLS-1$ } if(node.logLevel!=DEFAULT_LOG_LEVEL) { control.setValue("log_level", node.logLevel.getName()); //$NON-NLS-1$ } if(node.clearLog) { control.setValue("clear_log", true); //$NON-NLS-1$ } if(node.singleApp) { control.setValue("single_app", true); //$NON-NLS-1$ } if(node.singleAppOff) { control.setValue("single_app_off", true); //$NON-NLS-1$ } if(node.hiddenInLauncher) { control.setValue("hidden_in_launcher", true); //$NON-NLS-1$ } if(!node.author.equals("")) { //$NON-NLS-1$ control.setValue("author", node.author); //$NON-NLS-1$ } if(!node.keywords.equals("")) { //$NON-NLS-1$ control.setValue("keywords", node.keywords); //$NON-NLS-1$ } if(!node.level.equals("")) { //$NON-NLS-1$ control.setValue("level", node.level); //$NON-NLS-1$ } if(!node.languages.equals("")) { //$NON-NLS-1$ control.setValue("languages", node.languages); //$NON-NLS-1$ } if(!node.comment.equals("")) { //$NON-NLS-1$ control.setValue("comment", node.comment); //$NON-NLS-1$ } if(!node.appletWidth.equals("")) { //$NON-NLS-1$ control.setValue("applet_width", node.appletWidth); //$NON-NLS-1$ } if(!node.appletHeight.equals("")) { //$NON-NLS-1$ control.setValue("applet_height", node.appletHeight); //$NON-NLS-1$ } // Dimension dim = EjsTool.getEjsAppletDimension(node.getLaunchClass()); // if(dim!=null) { // control.setValue("applet_width", dim.width); //$NON-NLS-1$ // control.setValue("applet_height", dim.height); //$NON-NLS-1$ // } if(node.children!=null) { // list the children and save the list ArrayList<Serializable> children = new ArrayList<Serializable>(); Enumeration<?> e = node.children(); boolean saveAll = node.isSavingHiddenNodes(); while(e.hasMoreElements()) { LaunchNode child = (LaunchNode) e.nextElement(); if(!saveAll&&child.isHiddenInLauncher()) { continue; } if(node.isPreviewing()) { children.add(child); } else if((child.fileName!=null)&&!node.isSelfContained()) { children.add(child.fileName); } else { child.fileName = null; child.setSelfContained(false); children.add(child); } } if(children.size()>0) { control.setValue("child_nodes", children); //$NON-NLS-1$ } } } public Object createObject(XMLControl control) { String name = control.getString("name"); //$NON-NLS-1$ if(name==null) { name = LaunchRes.getString("NewNode.Name"); //$NON-NLS-1$ } return new LaunchNode(name); } public Object loadObject(XMLControl control, Object obj) { LaunchNode node = (LaunchNode) obj; String name = control.getString("name"); //$NON-NLS-1$ if(name!=null) { node.name = name; } String description = control.getString("description"); //$NON-NLS-1$ if(description!=null) { node.description = description; } String tooltip = control.getString("tooltip"); //$NON-NLS-1$ if(tooltip!=null) { node.tooltip = tooltip; } String xsetName = control.getString("launchset"); //$NON-NLS-1$ if(xsetName!=null) { node.xsetName = xsetName; } // read legacy xml files with "url" properties String url = control.getString("url"); //$NON-NLS-1$ if(url!=null) { node.setDisplayData(new String[][] { {null, url} }); } else { if(control.getPropertyNames().contains("html")) { //$NON-NLS-1$ pre-September 2009 files node.setDisplayData((String[][]) control.getObject("html")); //$NON-NLS-1$ } else { // post-September 2009 files node.setDisplayData((String[][]) control.getObject("display_tabs")); //$NON-NLS-1$ } node.setDisplayArgs((String[][]) control.getObject("display_args")); //$NON-NLS-1$ } node.setClassPath(control.getString("classpath")); //$NON-NLS-1$ String className = control.getString("launch_class"); //$NON-NLS-1$ if(className!=null) { node.launchClassName = className; node.launchModelScroller = null; } String[] args = (String[]) control.getObject("launch_args"); //$NON-NLS-1$ if(args!=null) { node.setArgs(args); } node.hiddenWhenRoot = control.getBoolean("root_hidden"); //$NON-NLS-1$ node.buttonView = control.getBoolean("button_view"); //$NON-NLS-1$ node.singleton = control.getBoolean("singleton"); //$NON-NLS-1$ node.singleVM = control.getBoolean("single_vm"); //$NON-NLS-1$ node.singleVMOff = control.getBoolean("single_vm_off"); //$NON-NLS-1$ node.showLog = control.getBoolean("show_log"); //$NON-NLS-1$ node.clearLog = control.getBoolean("clear_log"); //$NON-NLS-1$ node.singleApp = control.getBoolean("single_app"); //$NON-NLS-1$ node.singleAppOff = control.getBoolean("single_app_off"); //$NON-NLS-1$ node.hiddenInLauncher = control.getBoolean("hidden_in_launcher"); //$NON-NLS-1$ Level logLevel = OSPLog.parseLevel(control.getString("log_level")); //$NON-NLS-1$ if(logLevel!=null) { node.logLevel = logLevel; } // meta items should not be set to null String author = control.getString("author"); //$NON-NLS-1$ if(author!=null) { node.author = author; } String keywords = control.getString("keywords"); //$NON-NLS-1$ if(keywords!=null) { node.keywords = keywords; } String level = control.getString("level"); //$NON-NLS-1$ if(level!=null) { node.level = level; } String lang = control.getString("languages"); //$NON-NLS-1$ if(lang!=null) { node.languages = lang; } String comment = control.getString("comment"); //$NON-NLS-1$ if(comment!=null) { node.comment = comment; } String width = control.getString("applet_width"); //$NON-NLS-1$ if(width!=null) { node.appletWidth = width; } String height = control.getString("applet_height"); //$NON-NLS-1$ if(height!=null) { node.appletHeight = height; } name = control.getString("filename"); //$NON-NLS-1$ if(name!=null) { node.setFileName(name); } Collection<?> children = (ArrayList<?>) control.getObject("child_nodes"); //$NON-NLS-1$ if(children!=null) { node.removeAllChildren(); Iterator<?> it = children.iterator(); while(it.hasNext()) { Object next = it.next(); if(next instanceof LaunchNode) { // child node // add the child node LaunchNode child = (LaunchNode) next; node.add(child); child.setLaunchClass(child.launchClassName); } else if(next instanceof String) { // file name String fileName = (String) next; String path = XML.getResolvedPath(fileName, Launcher.tabSetBasePath); // last path added is first path searched ResourceLoader.addSearchPath(Launcher.resourcesPath); ResourceLoader.addSearchPath(Launcher.tabSetBasePath); // open the file in an xml control XMLControlElement childControl = new XMLControlElement(); String absolutePath = childControl.read(path); if(childControl.failedToRead()) { JOptionPane.showMessageDialog(null, LaunchRes.getString("Dialog.InvalidXML.Message")+" \""+fileName+"\"", LaunchRes.getString("Dialog.InvalidXML.Title"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } // check root to prevent circular references LaunchNode root = (LaunchNode) node.getRoot(); if((root.getChildNode(fileName)!=null)||fileName.equals(root.fileName)) { continue; } Class<?> type = childControl.getObjectClass(); if(LaunchNode.class.isAssignableFrom(type)) { // first add the child with just the fileName // this allows descendants to get the root LaunchNode child = new LaunchNode(LaunchRes.getString("NewNode.Name")); //$NON-NLS-1$ child.setFileName(fileName); OSPLog.finest(LaunchRes.getString("Log.Message.Loading")+": "+absolutePath); //$NON-NLS-1$ //$NON-NLS-2$ node.add(child); // then load the child with data childControl.loadObject(child); } } } } return node; } } /** * Gets the display tab data. * * @return an array of String[2] {title, path}, one for each tab */ private String[][] getDisplayData() { if(tabData.isEmpty()) { return null; } String[][] data = new String[tabData.size()][2]; for(int i = 0; i<tabData.size(); i++) { DisplayTab tab = tabData.get(i); data[i] = new String[] {tab.title, tab.path}; } return data; } /** * Sets the display tab data. * * @param data an array of String[2] {title, path}, one for each tab */ private void setDisplayData(String[][] data) { if(data==null) { return; } tabData.clear(); for(int i = 0; i<data.length; i++) { DisplayTab tab = new DisplayTab(data[i][0], data[i][1]); tabData.add(tab); } } /** * Gets the arguments for display tab models. * * @return an array of String[] arguments, one for each tab */ private String[][] getDisplayArgs() { if(tabData.isEmpty()) { return null; } String[][] data = new String[tabData.size()][]; for(int i = 0; i<tabData.size(); i++) { DisplayTab tab = tabData.get(i); String[] args = tab.getModelArgs(); data[i] = (args.length==0) ? null : args; } return data; } /** * Sets the arguments for display tab models. * * @param data an array of String[] arguments, one for each tab */ private void setDisplayArgs(String[][] data) { if(data==null) { return; } int len = Math.min(data.length, tabData.size()); for(int i = 0; i<len; i++) { DisplayTab tab = tabData.get(i); tab.setModelArgs(data[i]); } } protected void setMinimumArgLength(int n) { n = Math.max(n, 1); // never shorter than 1 element if(n==args.length) { return; } if(n>args.length) { // increase the size of the args array String[] newArgs = new String[n]; for(int i = 0; i<n; i++) { if(i<args.length) { newArgs[i] = args[i]; } else { newArgs[i] = ""; //$NON-NLS-1$ } } setArgs(newArgs); } else { while((args.length>n)&&args[args.length-1].equals("")) { //$NON-NLS-1$ String[] newArgs = new String[args.length-1]; for(int i = 0; i<newArgs.length; i++) { newArgs[i] = args[i]; } setArgs(newArgs); } } } protected void removeThread(Runnable runner) { threads.remove(runner); } /** * A class to hold display tab data. */ public class DisplayTab { String title; boolean hyperlinksEnabled = true; String path; // url path or model class name URL url; Class<?> modelClass; JComponent modelPane; JScrollPane modelScroller; String[] modelArgs = new String[0]; /** * Constructor. * * @param title the tab title (may be null) * @param path the path */ DisplayTab(String title, String path) { setTitle(title); setPath(path); } /** * Gets the path. * * @return the path */ public String getPath() { return path; } /** * Sets the path. * * @param path the path */ public void setPath(String path) { this.path = path; if(!setURL(path)) { setModelClass(path); } } /** * Gets the title. * * @return the title */ public String getTitle() { return title; } /** * Sets the title. * * @param title the title */ public void setTitle(String title) { this.title = title; } /** * Gets the URL. May return null. * * @return the URL */ public URL getURL() { if((url==null)&&(modelClass==null)&&(path!=null)&&!"".equals(path)) { //$NON-NLS-1$ setURL(path); } return url; } /** * Gets the model class. May return null. * * @return the model class */ public Class<?> getModelClass() { if((url==null)&&(modelClass==null)&&(path!=null)&&!path.equals("")) { //$NON-NLS-1$ setModelClass(path); } return modelClass; } /** * Gets the modelPane. May return null. * * @return the model pane */ public JComponent getModelPane() { if((modelPane==null)&&(getModelClass()!=null)) { if(JComponent.class.isAssignableFrom(modelClass)) { try { modelPane = (JComponent) modelClass.newInstance(); } catch(Exception ex) {} } else { modelPane = Launcher.getModelPane(modelClass, getModelArgs()); } } return modelPane; } /** * Gets the model arguments. May return null. * * @return the model arguments */ public String[] getModelArgs() { return modelArgs; } /** * Sets the model arguments. * * @param args the model arguments */ public void setModelArgs(String[] args) { if(args!=null) { modelArgs = args; modelPane = null; modelScroller = null; } } /** * Gets the modelScroller. May return null. * * @return the model scroller */ protected JScrollPane getModelScroller() { final JComponent content = getModelPane(); if((modelScroller==null)&&(content!=null)) { JPanel panel = new JPanel(new OSPLayout()) { public Dimension getPreferredSize() { Dimension dim = content.getPreferredSize(); dim.width += 8; dim.height += 8; return dim; } }; panel.setBackground(java.awt.Color.white); panel.add(content, OSPLayout.CENTERED); modelScroller = new JScrollPane(panel); } return modelScroller; } protected void setMinimumModelArgLength(int n) { n = Math.max(n, 0); if(n==modelArgs.length) { return; } if(n>modelArgs.length) { // increase the size of the modelArgs array String[] newArgs = new String[n]; for(int i = 0; i<n; i++) { if(i<modelArgs.length) { newArgs[i] = modelArgs[i]; } else { newArgs[i] = null; } } setModelArgs(newArgs); } else { while((modelArgs.length>n)&&(modelArgs[modelArgs.length-1]==null)) { String[] newArgs = new String[modelArgs.length-1]; for(int i = 0; i<newArgs.length; i++) { newArgs[i] = modelArgs[i]; } setModelArgs(newArgs); } } } /** * Sets the URL. * * @param path the url path * @return true if a url resource was found */ private boolean setURL(String path) { url = null; Resource res = ResourceLoader.getResource(path); if((res!=null)&&(res.getURL()!=null)) { url = res.getURL(); try { InputStream in = url.openStream(); in.close(); OSPLog.finer(LaunchRes.getString("Log.Message.URL")+" "+url); //$NON-NLS-1$ //$NON-NLS-2$ } catch(Exception ex) { url = null; } } return url!=null; } /** * Sets the model class. The model class must either descend * from JPanel or define a static getModelPane() method. * * @param className the name of the class * @return true if the class was successfully loaded for the first time */ private boolean setModelClass(String className) { path = className; if(className==null) { return false; } if((modelClass!=null)&&className.equals(modelClass.getName())) { // no change return false; } modelPane = null; modelScroller = null; modelClass = LaunchClassChooser.getModelClass(getClassPath(), className); return modelClass!=null; } } //___________________________ deprecated methods _________________________ /** * Adds an HTML tab with the specified title and relative path. * @deprecated replaced by addDisplayTab */ @SuppressWarnings("javadoc") public DisplayTab addHTML(String title, String path) { return addDisplayTab(title, path, null); } /** * Inserts an HTML tab with the specified title and relative path. * @deprecated replaced by insertDisplayTab */ @SuppressWarnings("javadoc") public DisplayTab insertHTML(int n, String title, String path) { return insertDisplayTab(n, title, path, null); } /** * Removes an HTML tab. * @deprecated replaced by removeDisplayTab */ @SuppressWarnings("javadoc") public DisplayTab removeHTML(int n) { return removeDisplayTab(n); } /** * Gets an HTML tab. * @deprecated replaced by getDisplayTab */ @SuppressWarnings("javadoc") public DisplayTab getHTML(int n) { return getDisplayTab(n); } /** * Returns the number of html objects in the list. * @deprecated replaced by getDisplayTabCount */ @SuppressWarnings("javadoc") public int getHTMLCount() { return getDisplayTabCount(); } /** * Sets the specified HTML tab title and url path. * @deprecated replaced by setDisplayTab */ @SuppressWarnings("javadoc") public DisplayTab setHTML(int n, String title, String path) { return setDisplayTab(n, title, path, null); } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */