View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.ui;
39  
40  import ffx.algorithms.AlgorithmFunctions;
41  import ffx.algorithms.AlgorithmListener;
42  import ffx.algorithms.Terminatable;
43  import ffx.algorithms.cli.AlgorithmsCommand;
44  import ffx.algorithms.dynamics.MolecularDynamics;
45  import ffx.algorithms.dynamics.integrators.IntegratorEnum;
46  import ffx.algorithms.dynamics.thermostats.ThermostatEnum;
47  import ffx.algorithms.optimize.Minimize;
48  import ffx.numerics.Potential;
49  import ffx.potential.ForceFieldEnergy;
50  import ffx.potential.MolecularAssembly;
51  import ffx.potential.bonded.MSNode;
52  import ffx.potential.bonded.RendererCache.ColorModel;
53  import ffx.potential.bonded.RendererCache.ViewModel;
54  import ffx.potential.cli.PotentialCommand;
55  import ffx.potential.utils.PotentialsFunctions;
56  import ffx.utilities.Console;
57  import ffx.utilities.FFXCommand;
58  import ffx.utilities.FFXBinding;
59  import ffx.utilities.GroovyFileFilter;
60  import org.codehaus.groovy.runtime.MethodClosure;
61  import org.graalvm.polyglot.Context;
62  import org.graalvm.polyglot.Engine;
63  import org.graalvm.polyglot.Language;
64  import org.graalvm.polyglot.Source;
65  import org.graalvm.polyglot.Value;
66  import org.graalvm.polyglot.proxy.ProxyArray;
67  
68  import javax.swing.ImageIcon;
69  import javax.swing.JFrame;
70  import javax.swing.JOptionPane;
71  import javax.swing.JScrollPane;
72  import javax.swing.JSplitPane;
73  import javax.swing.JTextPane;
74  import javax.swing.JViewport;
75  import javax.swing.SwingUtilities;
76  import javax.swing.text.Style;
77  import javax.swing.text.StyleConstants;
78  import javax.swing.text.StyleContext;
79  import javax.swing.text.StyledDocument;
80  import java.awt.Color;
81  import java.awt.Component;
82  import java.awt.Dimension;
83  import java.awt.EventQueue;
84  import java.awt.Menu;
85  import java.awt.MenuBar;
86  import java.awt.Point;
87  import java.awt.Rectangle;
88  import java.io.File;
89  import java.net.URL;
90  import java.nio.file.Path;
91  import java.nio.file.Paths;
92  import java.util.ArrayList;
93  import java.util.EventObject;
94  import java.util.List;
95  import java.util.Map;
96  import java.util.logging.Level;
97  import java.util.logging.Logger;
98  
99  /**
100  * The ModelingShell is used to script Multiscale Modeling Routines via the Groovy scripting
101  * language. Functionality available through the modeling shell includes the Force Field X API, Java
102  * API and Groovy extensions.
103  *
104  * @author Michael J. Schnieders
105  */
106 public class ModelingShell extends Console implements AlgorithmListener {
107 
108   /**
109    * The logger for this class.
110    */
111   private static final Logger logger = Logger.getLogger(ModelingShell.class.getName());
112   private static final double toSeconds = 1.0e-9;
113 
114   /**
115    * A reference to the main application container.
116    */
117   private final MainPanel mainPanel;
118   /**
119    * The flag headless is true for the CLI and false for the GUI.
120    */
121   private final boolean headless;
122   /**
123    * The flag interrupted is true if a script is running and the user requests it be canceled.
124    */
125   private boolean interrupted;
126   /**
127    * An algorithm that implements the Terminatable interface can be cleanly terminated before
128    * completion. For example, after completion of an optimization step or MD step.
129    */
130   private Terminatable terminatableAlgorithm = null;
131   /**
132    * Flag to indicate if a script is running.
133    */
134   private boolean scriptRunning;
135   /**
136    * Timing.
137    */
138   private long time;
139 
140   private long subTime;
141   private List<String> args;
142 
143   /**
144    * Constructor for ModelingShell.
145    *
146    * @param mainPanel a reference to the MainPanel.
147    */
148   public ModelingShell(MainPanel mainPanel) {
149     super();
150     this.mainPanel = mainPanel;
151     headless = java.awt.GraphicsEnvironment.isHeadless();
152     FFXBinding binding = new FFXBinding();
153     initContext(binding);
154   }
155 
156   /**
157    * after
158    */
159   public void after() {
160     time = System.nanoTime() - time;
161     scriptRunning = false;
162     if (!interrupted) {
163       appendOutput(String.format("\n Script wall clock time: %6.3f (sec)", time * toSeconds),
164           getPromptStyle());
165     } else {
166       appendOutput(String.format("\n Script interrupted after: %6.3f (sec)", time * toSeconds),
167           getPromptStyle());
168     }
169     mainPanel.getModelingPanel().enableLaunch(true);
170   }
171 
172   /**
173    * {@inheritDoc}
174    */
175   @Override
176   public boolean algorithmUpdate(MolecularAssembly active) {
177     if (interrupted) {
178       return false;
179     }
180 
181     GraphicsCanvas graphics = mainPanel.getGraphics3D();
182     if (graphics != null) {
183       /*
184        Use the blocking graphics update method so that only
185        self-consistent coordinate sets are displayed.
186       */
187       // if (SwingUtilities.isEventDispatchThread()) {
188       graphics.updateSceneWait(active, true, false, null, false, null);
189       // }
190     }
191 
192     // The algorithm could have been interrupted during the graphics update.
193     return !interrupted;
194   }
195 
196   /**
197    * If at exit time, a script is running, the user is given an option to interrupt it first
198    *
199    * <p>{@inheritDoc}
200    */
201   @Override
202   public Object askToInterruptScript() {
203     if (!scriptRunning) {
204       return true;
205     }
206     int rc = JOptionPane.showConfirmDialog(getScrollArea(),
207         "Script executing. Press 'OK' to attempt to interrupt it before exiting.",
208         "Force Field X Shell", JOptionPane.OK_CANCEL_OPTION);
209     if (rc == JOptionPane.OK_OPTION) {
210       doInterrupt();
211       return true;
212     } else {
213       return false;
214     }
215   }
216 
217   /**
218    * {@inheritDoc}
219    *
220    * <p>Return false if user elects to cancel.
221    */
222   @Override
223   public boolean askToSaveFile() {
224     File file = (File) getScriptFile();
225     if (file == null || !getDirty()) {
226       return true;
227     }
228     return switch (JOptionPane.showConfirmDialog((Component) getFrame(),
229         "Save changes to " + file.getName() + "?", "Force Field X Shell",
230         JOptionPane.YES_NO_CANCEL_OPTION)) {
231       case JOptionPane.YES_OPTION -> fileSave();
232       case JOptionPane.NO_OPTION -> true;
233       default -> false;
234     };
235   }
236 
237   /**
238    * before
239    */
240   public void before() {
241     interrupted = false;
242     terminatableAlgorithm = null;
243     time = System.nanoTime();
244     subTime = time;
245     mainPanel.getModelingPanel().enableLaunch(false);
246     scriptRunning = true;
247   }
248 
249   @Override
250   public void clearContext() {
251     super.clearContext();
252     FFXBinding binding = new FFXBinding();
253     initContext(binding);
254   }
255 
256   @Override
257   public void clearContext(EventObject evt) {
258     super.clearContext(evt);
259     FFXBinding binding = new FFXBinding();
260     initContext(binding);
261   }
262 
263   /**
264    * {@inheritDoc}
265    *
266    * <p>Print out the Force Field X promo.
267    */
268   @Override
269   public void clearOutput() {
270     if (!java.awt.GraphicsEnvironment.isHeadless()) {
271       JTextPane output = getOutputArea();
272       output.setText("");
273       appendOutput(
274           MainPanel.border + "\n" + MainPanel.title + MainPanel.aboutString + "\n" + MainPanel.border
275               + "\n", getCommandStyle());
276     }
277   }
278 
279   @Override
280   public void clearOutput(EventObject evt) {
281     clearOutput();
282   }
283 
284   /**
285    * energy
286    *
287    * @return a {@link ffx.potential.ForceFieldEnergy} object.
288    */
289   public ForceFieldEnergy energy() {
290     if (interrupted) {
291       logger.info(" Algorithm interrupted - skipping energy.");
292       return null;
293     }
294     if (terminatableAlgorithm != null) {
295       logger.info(" Algorithm already running - skipping energy.");
296       return null;
297     }
298 
299     MolecularAssembly active = mainPanel.getHierarchy().getActive();
300     if (active != null) {
301       ForceFieldEnergy energy = active.getPotentialEnergy();
302       if (energy == null) {
303         energy = ForceFieldEnergy.energyFactory(active);
304         active.setPotential(energy);
305       }
306       energy.energy(false, true);
307       return energy;
308     }
309     return null;
310   }
311 
312   @Override
313   public void fileNewWindow() {
314     mainPanel.resetShell();
315   }
316 
317   @Override
318   public void fileNewWindow(EventObject evt) {
319     fileNewWindow();
320   }
321 
322   public AlgorithmFunctions getUIAlgorithmUtils() {
323     return new UIUtils(this, mainPanel);
324   }
325 
326   public PotentialsFunctions getUIPotentialsUtils() {
327     return new UIUtils(this, mainPanel);
328   }
329 
330   /**
331    * md
332    *
333    * @param nStep          The number of MD steps.
334    * @param timeStep       a double.
335    * @param printInterval  a double.
336    * @param saveInterval   a double.
337    * @param temperature    a double.
338    * @param initVelocities a boolean.
339    * @param dyn            a {@link java.io.File} object.
340    */
341   public void md(int nStep, double timeStep, double printInterval, double saveInterval,
342                  double temperature, boolean initVelocities, File dyn) {
343     if (interrupted || terminatableAlgorithm != null) {
344       return;
345     }
346     FFXSystem active = mainPanel.getHierarchy().getActive();
347     if (active != null) {
348       MolecularDynamics molecularDynamics = new MolecularDynamics(active,
349           active.getPotentialEnergy(), this, ThermostatEnum.BUSSI, IntegratorEnum.BEEMAN);
350       terminatableAlgorithm = molecularDynamics;
351       molecularDynamics.dynamic(nStep, timeStep, printInterval, saveInterval, temperature,
352           initVelocities, dyn);
353       terminatableAlgorithm = null;
354     }
355   }
356 
357   /**
358    * Configure the Swing GUI for the shell.
359    */
360   @Override
361   public void run() {
362     if (!headless) {
363       if (SwingUtilities.isEventDispatchThread()) {
364         init();
365       } else {
366         try {
367           SwingUtilities.invokeAndWait(this::init);
368         } catch (Exception e) {
369           //
370         }
371       }
372     }
373   }
374 
375   /**
376    * select
377    *
378    * @param node a {@link ffx.potential.bonded.MSNode} object.
379    */
380   public void select(MSNode node) {
381     if (node != null) {
382       mainPanel.getHierarchy().onlySelection(node);
383       sync();
384       logger.info(String.format(" Selected: %s.", node));
385     }
386   }
387 
388   /**
389    * setArgList
390    *
391    * @param argList a {@link java.util.List} object.
392    */
393   public void setArgList(List<String> argList) {
394     args = new ArrayList<>(argList);
395     setVariable("args", argList);
396   }
397 
398   @Override
399   public void showAbout() {
400     mainPanel.about();
401   }
402 
403   @Override
404   public void showAbout(EventObject evt) {
405     showAbout();
406   }
407 
408   /**
409    * time
410    *
411    * @return a {@link java.lang.Double} object.
412    */
413   public Double time() {
414     long current = System.nanoTime();
415     double timer = (current - subTime) * toSeconds;
416     subTime = current;
417     appendOutput(String.format("\n Intermediate time: %8.3f (sec)\n", timer), getPromptStyle());
418     return timer;
419   }
420 
421   /**
422    * {@inheritDoc}
423    */
424   @Override
425   public String toString() {
426     return "Force Field X Shell";
427   }
428 
429   @Override
430   public void updateTitle() {
431     JFrame frame = (JFrame) this.getFrame();
432     File file = (File) getScriptFile();
433     if (file != null) {
434       String name = file.getName();
435       frame.setTitle(name + " - Force Field X Shell");
436     } else {
437       frame.setTitle("Force Field X Shell");
438     }
439   }
440 
441   /**
442    * Initialize access to Force Field X variables and methods from with the Shell.
443    */
444   private void initContext(FFXBinding binding) {
445     binding.setVariable("dat", mainPanel.getHierarchy());
446     binding.setVariable("cmd", mainPanel);
447     binding.setVariable("vis", mainPanel.getGraphics3D());
448     binding.setVariable("sh", this);
449     binding.setVariable("active", mainPanel.getHierarchy().getActive());
450     binding.setVariable("logger", logger);
451 
452     // Timer
453     binding.setVariable("time", new MethodClosure(this, "time"));
454 
455     // File
456     binding.setVariable("open", new MethodClosure(mainPanel, "openWait"));
457     binding.setVariable("convertWait", new MethodClosure(mainPanel, "convertWait"));
458     binding.setVariable("save", new MethodClosure(mainPanel, "saveAsXYZ"));
459     binding.setVariable("saveAsXYZ", new MethodClosure(mainPanel, "saveAsXYZ"));
460     binding.setVariable("saveAsP1", new MethodClosure(mainPanel, "saveAsP1"));
461     binding.setVariable("saveAsPDB", new MethodClosure(mainPanel, "saveAsPDB"));
462     binding.setVariable("close", new MethodClosure(mainPanel, "closeWait"));
463     binding.setVariable("closeAll", new MethodClosure(mainPanel, "closeAll"));
464 
465     // Select
466     binding.setVariable("select", new MethodClosure(this, "select"));
467 
468     // Display and View menus.
469     if (!headless) {
470       GraphicsCanvas graphics = mainPanel.getGraphics3D();
471 
472       // Display
473       int index = 0;
474       for (ViewModel view : ViewModel.values()) {
475         binding.setVariable(view.name(), view);
476         index++;
477         if (index > 8) {
478           break;
479         }
480       }
481       binding.setVariable("view", new MethodClosure(graphics, "viewWait"));
482 
483       // Color
484       index = 0;
485       for (ColorModel color : ColorModel.values()) {
486         binding.setVariable(color.name(), color);
487         index++;
488         if (index > 6) {
489           break;
490         }
491       }
492       binding.setVariable("color", new MethodClosure(graphics, "colorWait"));
493     }
494 
495     // Algorithms
496     binding.setVariable("returnEnergy", new MethodClosure(this, "returnEnergy"));
497     binding.setVariable("energy", new MethodClosure(this, "energy"));
498     binding.setVariable("analyze", new MethodClosure(this, "analyze"));
499     binding.setVariable("minimize", new MethodClosure(this, "minimize"));
500     binding.setVariable("minimize_2", new MethodClosure(this, "minimize_2"));
501     binding.setVariable("md", new MethodClosure(this, "md"));
502     binding.setVariable("potential", new MethodClosure(this, "potential"));
503     binding.setVariable("poledit", new MethodClosure(this, "poledit"));
504     binding.setVariable("superpose", new MethodClosure(this, "superpose"));
505 
506     // Obtain UIUtils object
507     binding.setVariable("functions", new UIUtils(this, mainPanel));
508 
509     // Define a listener variable to send updates back to the GUI.
510     binding.setVariable("listener", this);
511   }
512 
513   /**
514    * Update the shell menu items.
515    */
516   private void initMenus() {
517     JFrame frame = (JFrame) this.getFrame();
518     MenuBar menuBar = frame.getMenuBar();
519     // Remove "Capture Std. Out", "Capture Std. Error" & "Detached Output" from the View menu.
520     Menu menu = menuBar.getMenu(2);
521     menu.remove(5);
522     menu.remove(5);
523     menu.remove(9);
524 
525     // Edit the Script menu.
526     menu = menuBar.getMenu(4);
527     menu.remove(4);
528     menu.remove(4);
529     menu.remove(4);
530     menu.remove(5);
531     menu.remove(7);
532   }
533 
534   /**
535    * Initialize the Shell.
536    */
537   private void init() {
538     try {
539       super.run();
540       // Output JTextPane
541       JTextPane output = getOutputArea();
542       output.setBackground(Color.BLACK);
543       output.setForeground(Color.WHITE);
544       // Input JTextPane
545       JTextPane input = getInputArea();
546       input.setBackground(Color.WHITE);
547       input.setForeground(Color.BLACK);
548       // Output StyledDocument Styles
549       StyledDocument doc = output.getStyledDocument();
550       Style defStyle = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
551       Style regular = doc.addStyle("regular", defStyle);
552       Style prompt = doc.addStyle("prompt", regular);
553       Style command = doc.addStyle("command", regular);
554       Style result = doc.addStyle("result", regular);
555       StyleConstants.setFontFamily(regular, "Monospaced");
556       setPromptStyle(prompt);
557       setCommandStyle(command);
558       setResultStyle(result);
559       StyleConstants.setForeground(prompt, Color.ORANGE);
560       StyleConstants.setForeground(command, Color.GREEN);
561       StyleConstants.setForeground(result, Color.GREEN);
562       StyleConstants.setBackground(result, Color.BLACK);
563       clearOutput();
564       // initMenus();
565 
566       // Set labels and icon for Force Field X.
567       getStatusLabel().setText("Welcome to the Force Field X Shell.");
568       JFrame frame = (JFrame) this.getFrame();
569       frame.setTitle("Force Field X Shell");
570       URL iconURL = getClass().getClassLoader().getResource("ffx/ui/icons/icon64.png");
571       ImageIcon icon = new ImageIcon(iconURL);
572       frame.setIconImage(icon.getImage());
573       frame.setSize(600, 600);
574     } catch (Exception e) {
575       logger.warning(" Exception starting up the FFX console.\n" + e);
576     }
577   }
578 
579   List<String> getArgs() {
580     return new ArrayList<>(args);
581   }
582 
583   /**
584    * runFFXScript - Execute a FFX script.
585    *
586    * @param file    a {@link java.io.File} object.
587    * @param argList List of String inputs to the script.
588    * @return Returns a reference to the executed script.
589    */
590   public FFXCommand runFFXScript(File file, List<String> argList) {
591     GroovyFileFilter groovyFileFilter = new GroovyFileFilter();
592     // Check that the file is a Groovy script.
593     if (!groovyFileFilter.accept(file)) {
594       // If not, assume its a Python script.
595       runNonGroovyScript(file, argList);
596       return null;
597     }
598 
599     logger.info(" Executing external Groovy script: " + file.getAbsolutePath() + "\n");
600     try {
601       before();
602       FFXCommand script = null;
603       try {
604         // Run the file using the current Shell and its Binding.
605         Object o = getShell().run(file, argList);
606 
607         if (o instanceof FFXCommand) {
608           script = (FFXCommand) o;
609         }
610 
611         // Do not destroy the system when using the GUI.
612         if (headless) {
613           if (o instanceof PotentialCommand) {
614             ((PotentialCommand) o).destroyPotentials();
615           } else if (o instanceof AlgorithmsCommand) {
616             ((AlgorithmsCommand) o).destroyPotentials();
617           }
618         }
619       } catch (Exception ex) {
620         logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
621       }
622       after();
623       return script;
624     } catch (Exception e) {
625       // Replacing this with a "Multi-Catch" leads to specific Exceptions not present in
626       // some versions of Groovy.
627       String message = "Error evaluating script.";
628       logger.log(Level.WARNING, message, e);
629     }
630 
631     return null;
632   }
633 
634   /**
635    * Run a script that is not Groovy (e.g., a Python script).
636    *
637    * @param file    a {@link java.io.File} object.
638    * @param argList List of String inputs to the script.
639    */
640   public void runNonGroovyScript(File file, List<String> argList) {
641     logger.info(" Attempting to execute Polyglot script:\n  " + file.getAbsolutePath() + "\n");
642 
643     if (logger.isLoggable(Level.FINE)) {
644       logger.fine(" Available languages: ");
645       try (Engine engine = Engine.create()) {
646         Map<String, Language> map = engine.getLanguages();
647         for (Map.Entry<String, Language> entry : map.entrySet()) {
648           logger.fine("  " + entry.getKey());
649         }
650       } catch (Exception ex) {
651         logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
652       }
653     }
654 
655     try {
656       before();
657       String language = Source.findLanguage(file);
658       logger.info(" Detected script language: " + language);
659       Source source = Source.newBuilder(language, file).build();
660       try (Context context = getContext(language)) {
661 
662         // Get the bindings for the language.
663         Value bindings = context.getBindings(language);
664         // Use the Polyglot ProxyArray to pass the command line arguments to the script.
665         List<Object> objList = new ArrayList<>(argList);
666         ProxyArray argArray = ProxyArray.fromList(objList);
667         bindings.putMember("args", argArray);
668         // Run the file using the current Shell and its Binding.
669         Value result = context.eval(source);
670         logger.info(" Execution of Polyglot script completed.");
671       } catch (Exception ex) {
672         logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
673       }
674       after();
675     } catch (Exception e) {
676       // Replacing this with a "Multi-Catch" leads to specific Exceptions not present in some versions of Groovy.
677       String message = "Error evaluating script.";
678       logger.log(Level.WARNING, message, e);
679     }
680   }
681 
682   /**
683    * Create a Polyglot Context for the specified language.
684    *
685    * @param language a String specifying the language.
686    * @return a Polyglot Context.
687    */
688   private Context getContext(String language) {
689     if (language.equalsIgnoreCase("python")) {
690       // For Python, try to locate the Graal Python executable.
691 
692       // The default location is $FFX_HOME/ffx_venv/bin/graalpy.
693       String FFX_HOME = System.getProperty("basedir");
694       Path graalpy = Paths.get(FFX_HOME, "ffx_venv", "bin", "graalpy");
695 
696       // Override the default location with the -Dgraalpy=path.to.graalpy option.
697       String graalpyString = System.getProperty("graalpy", graalpy.toString());
698       graalpy = Paths.get(graalpyString);
699       if (graalpy.toFile().exists()) {
700         logger.info(" graalpy (-Dgraalpy=path.to.graalpy):             " + graalpy);
701         return Context.newBuilder(language).allowAllAccess(true).
702             option("python.Executable", graalpyString).
703             option("python.PythonPath", ".").build();
704       }
705     }
706     // Fall through to default.
707     return Context.newBuilder(language).allowAllAccess(true).build();
708   }
709 
710   /**
711    * runFFXScript - Execute a compiled FFX command.
712    *
713    * @param className a compiled FFX command.
714    * @param argList   List of String inputs to the command.
715    * @return Returns a reference to the executed command.
716    */
717   public FFXCommand runFFXScript(Class<? extends FFXCommand> className, List<String> argList) {
718     logger.info(" Executing internal command: " + className.getCanonicalName() + "\n");
719     try {
720       before();
721       FFXCommand command = null;
722       try {
723         // Create a Binding for command line arguments and FFX User Interface variables.
724         FFXBinding binding = new FFXBinding();
725         binding.setVariable("args", argList);
726         initContext(binding);
727 
728         // Create a new instance of the script and run it.
729         command = className.getDeclaredConstructor().newInstance();
730         command.setBinding(binding);
731         command.run();
732 
733         // Do not destroy the system when using the GUI.
734         if (headless) {
735           if (command instanceof PotentialCommand) {
736             ((PotentialCommand) command).destroyPotentials();
737           } else if (command instanceof AlgorithmsCommand) {
738             ((AlgorithmsCommand) command).destroyPotentials();
739           }
740         }
741       } catch (Exception ex) {
742         logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
743       }
744       after();
745 
746       return command;
747     } catch (Exception e) {
748       // Replacing this with a "Multi-Catch" leads to specific Exceptions not present in
749       // some versions of Groovy.
750       String message = "Error evaluating script.";
751       logger.log(Level.WARNING, message, e);
752     }
753     return null;
754   }
755 
756   /**
757    * returnEnergy
758    *
759    * @return Current system energy (a double).
760    */
761   double returnEnergy() {
762     if (interrupted) {
763       logger.info(" Algorithm interrupted - skipping energy.");
764       return 0.0;
765     }
766     if (terminatableAlgorithm != null) {
767       logger.info(" Algorithm already running - skipping energy.");
768       return 0.0;
769     }
770 
771     MolecularAssembly active = mainPanel.getHierarchy().getActive();
772     if (active != null) {
773       ForceFieldEnergy energy = active.getPotentialEnergy();
774       if (energy == null) {
775         energy = ForceFieldEnergy.energyFactory(active);
776         active.setPotential(energy);
777       }
778       return energy.energy(false, true);
779     }
780     logger.warning(" Energy could not be calculated");
781     return 0.0;
782   }
783 
784   /**
785    * minimize
786    *
787    * @param eps a double.
788    * @return a {@link ffx.numerics.Potential} object.
789    */
790   Potential minimize(double eps) {
791     if (interrupted) {
792       logger.info(" Algorithm interrupted - skipping minimization.");
793       return null;
794     }
795     if (terminatableAlgorithm != null) {
796       logger.info(" Algorithm already running - skipping minimization.");
797       return null;
798     }
799     MolecularAssembly active = mainPanel.getHierarchy().getActive();
800     if (active != null) {
801       Minimize minimize = new Minimize(active, this);
802       terminatableAlgorithm = minimize;
803       Potential potential = minimize.minimize(eps);
804       terminatableAlgorithm = null;
805       return potential;
806     } else {
807       logger.info(" No active system to minimize.");
808     }
809     return null;
810   }
811 
812   /**
813    * Fix up the "Result: " message, then call the original method.
814    *
815    * @param string String to ouput.
816    * @param style  Style to use.
817    */
818   void appendOutputNl(String string, Style style) {
819     if (interrupted) {
820       return;
821     }
822     if (headless) {
823       logger.info(string);
824     }
825     if (string.equals("Result: ")) {
826       string = " Script result: \n";
827     } else if (string.equals("groovy> ")) {
828       string = " ffx> ";
829     }
830 
831     super.appendOutputNl(string, style);
832 
833     if (EventQueue.isDispatchThread()) {
834       scroll();
835     } else {
836       SwingUtilities.invokeLater(this::scroll);
837     }
838   }
839 
840   /**
841    * scroll
842    */
843   private void scroll() {
844     JTextPane output = getOutputArea();
845     JSplitPane splitPane = getSplitPane();
846     JScrollPane scrollPane = (JScrollPane) splitPane.getBottomComponent();
847     JViewport viewport = scrollPane.getViewport();
848     Rectangle visibleSize = viewport.getVisibleRect();
849     Dimension totalSize = output.getSize();
850     Point point = new Point(0, totalSize.height - visibleSize.height);
851     viewport.setViewPosition(point);
852   }
853 
854   /**
855    * appendOutput
856    *
857    * @param string a {@link java.lang.String} object.
858    * @param style  a {@link javax.swing.text.Style} object.
859    */
860   private void appendOutput(String string, Style style) {
861     if (interrupted) {
862       return;
863     }
864 
865     if (headless) {
866       logger.info(string);
867       return;
868     }
869 
870     super.appendOutput(string, style);
871     if (EventQueue.isDispatchThread()) {
872       scroll();
873     } else {
874       SwingUtilities.invokeLater(this::scroll);
875     }
876   }
877 
878   /**
879    * setMeasurement
880    *
881    * @param measurement a {@link java.lang.String} object.
882    * @param d           a double.
883    */
884   void setMeasurement(String measurement, double d) {
885     try {
886       appendOutput(measurement, getOutputStyle());
887     } catch (Exception e) {
888       String message = "Exception appending measurement to Shell.\n";
889       logger.log(Level.WARNING, message, e);
890     }
891   }
892 
893   /**
894    * sync
895    */
896   void sync() {
897     try {
898       setVariable("active", mainPanel.getHierarchy().getActive());
899     } catch (Exception e) {
900       String message = " Exception syncing shell variables.\n";
901       logger.log(Level.WARNING, message, e);
902     }
903   }
904 }