001/* An actor that executes a Python script.
002
003 Copyright (c) 2003-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026 */
027package ptolemy.actor.lib.python;
028
029import java.io.File;
030import java.util.HashMap;
031import java.util.Iterator;
032import java.util.Locale;
033import java.util.Properties;
034
035import org.python.core.PyClass;
036import org.python.core.PyException;
037import org.python.core.PyJavaType;
038import org.python.core.PyMethod;
039import org.python.core.PyModule;
040import org.python.core.PyObject;
041import org.python.core.PyString;
042import org.python.core.PySystemState;
043import org.python.util.PythonInterpreter;
044
045import ptolemy.actor.TypedAtomicActor;
046import ptolemy.actor.process.TerminateProcessException;
047import ptolemy.kernel.CompositeEntity;
048import ptolemy.kernel.Port;
049import ptolemy.kernel.util.Attribute;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.NameDuplicationException;
052import ptolemy.kernel.util.Settable;
053import ptolemy.kernel.util.StringAttribute;
054import ptolemy.kernel.util.Workspace;
055import ptolemy.util.ClassUtilities;
056import ptolemy.util.StringUtilities;
057
058///////////////////////////////////////////////////////////////////
059//// PythonScript
060
061/**
062 An actor of this class executes a Python script.  There are two versions
063 of this actor provided in the Vergil libraries.  The one called
064 "PythonActor" has an input port and an output port; to view or edit
065 its Python script, look inside the actor.  The second version is
066 called "PythonScript" and has no ports; to view or edit its Python
067 script, select Configure (or double click on the icon).
068
069 <p> Upon creation, this actor has no ports, and no parameters other than
070 {@link #script script}; The <i>script</i> parameter has visibility
071 EXPERT, and therefore does not normally show up in a configure dialog
072 for the actor.  To make the script visible and editable, you have two
073 options. Including an instance of an attribute of class
074 TextEditorConfigureFactory (with its <i>attributeName</i> parameter
075 set to <i>script</i>) results in behavior like that of the Vergil
076 "PythonScript." That is, to edit the script, you Configure the actor.
077 If instead you include an instance of TextEditorTableauFactory,
078 then to edit the script you look inside the actor.  Use the latter
079 if you wish to add additional attributes to the actor and hide the
080 script from the users.  Use the former if the script is the main
081 means by which users interact with the actor.</p>
082
083 <p> The functionality of an actor of this type is given by a Python script.
084 As an example, a simplified version of the
085 {@link ptolemy.actor.lib.Scale Scale}
086 actor can be implemented by the following script:</p>
087 <pre>
088 1.  class Main :
089 2.    "scale"
090 3.    def fire(self) :
091 4.      if not self.input.hasToken(0) :
092 5.        return
093 6.      s = self.scale.getToken()
094 7.      t = self.input.get(0)
095 8.      self.output.broadcast(s.multiply(t))
096 </pre>
097
098 <p>Line 1 defines a Python class Main, which matches the value of the
099 <i>jythonClassName</i> parameter. An instance of this class is created when the
100 actor is initialized. Line 2 is a description of the purpose of the
101 script. Lines 3-8 define the fire() method, which is called by the
102 {@link #fire() fire()} method of this actor. In the method body,
103 <i>input</i> and <i>output</i> are ports that have to have been added
104 to the actor, and <i>scale</i> is a parameter that has to have been
105 added to the actor (these can be added in the XML that defines the
106 actor instance in an actor library). The Main class can provide other
107 methods in the {@link ptolemy.actor.Executable Executable} interface
108 as needed.</p>
109
110 <p>In the script, use <code>self.actor</code> to access the actor. For example,
111 <code>self.actor.getDirector()</code> returns the current director of the
112 actor. For debugging, use <code>self.actor.debug(someMessage)</code>. The
113 final message sent to the debug listeners of the actor will have the string
114 "From script: " inserted at the beginning. To avoid generating the debug
115 message when there are no listeners, use:</p>
116 <pre>
117 if self.actor.isDebugging() :
118 self.actor.debug(someMessage)
119 </pre>
120
121 <p>To use a Jython module, it is necessary to create a .py file
122 located in a location where Jython can find it.  The Jython
123 <code>sys.path</code> variable contains the Jython path.  One way to
124 get the value of the sys.path variable is to enable debugging on the
125 actor by right clicking and selecting "Listen to Actor", which will
126 cause the preinitialize() method to print the contents of sys.path to
127 standard out.  Another way to get the value of <code>sys.path</code>
128 is to run the Ptolemy model at
129 <code>ptolemy/actor/lib/python/test/PythonSysPath</code>.  For
130 example, under Mac OS X for the ptII user, sys.path includes
131 <code>/Users/ptII/lib/Lib</code>.  So, create that directory if
132 necessary and place the .py file in that directory, for example
133 <code>/Users/ptII/lib/Lib/PtPythonSquare.py</code></p>
134
135 <pre>
136class Main :
137  "Read the input and send the square to the output"
138  def fire(self) :
139    token = self.input.get(0)
140    self.output.broadcast(token.multiply(token))
141    return
142 </pre>
143
144 <p>Then set <i>jythonClassName</i> to the name of the <b>Jython</b>
145 class, for example <code>PtPythonSquare.Main</code>.  (Note that the
146 <i>jythonClassName</i> parameter should be set to the value of the
147 Jython class name before changing the <i>script</i> parameter to
148 import a Jython module.)</p>
149
150 <p>Then set <i>script</i> to  to:</p>
151 <pre>
152 import PtPythonSquare
153 PtPythonSquare = reload(PtPythonSquare)
154 </pre>
155
156
157 <p>This class relies on <a href="http://jython.org">Jython</a>, which
158 is a Java implementation of Python.
159
160 <p>As of November, 2011 $PTII/lib/jython.jar was based on Jython 2.5.2.</p>
161
162 <p>See <a href="https://kepler-project.org/developers/reference/python-and-kepler#in_browser">Python and Kepler notes</a>.</p>
163
164
165 @author Xiaojun Liu
166 @version $Id$
167 @since Ptolemy II 2.3
168 @Pt.ProposedRating Yellow (liuxj)
169 @Pt.AcceptedRating Red (reviewmoderator)
170 */
171public class PythonScript extends TypedAtomicActor {
172    /** Construct an actor with the given container and name.
173     *  In addition to invoking the base class constructor,
174     *  create the <i>script</i> parameter, and initialize
175     *  the script to provide an empty template.
176     *  @param container The container.
177     *  @param name The name of this actor.
178     *  @exception NameDuplicationException If the container already
179     *   has an actor with this name.
180     *  @exception IllegalActionException If the actor cannot be contained
181     *   by the proposed container.
182     */
183    public PythonScript(CompositeEntity container, String name)
184            throws NameDuplicationException, IllegalActionException {
185        super(container, name);
186
187        jythonClassName = new StringAttribute(this, "jythonClassName");
188        jythonClassName.setExpression("Main");
189
190        script = new StringAttribute(this, "script");
191
192        // Set the visibility to expert, as casual users should
193        // not see the script.  This is particularly true if one
194        // installs an actor that is an instance of this with a
195        // particular script in the library.
196        script.setVisibility(Settable.EXPERT);
197
198        // initialize the script to provide an empty template:
199        //
200        // # This is a template.
201        // class Main :
202        //   "description here"
203        //   def fire(self) :
204        //     # read input, compute, send output
205        //     return
206        //
207        script.setExpression("# This is a template.\n" + "class Main :\n"
208                + "  \"description here\"\n" + "  def fire(self) :\n"
209                + "    # Create ports, e.g. input and output.\n"
210                + "    # Read input, for example using\n"
211                + "    # token = self.input.get(0)\n"
212                + "    # compute, and send an output using\n"
213                + "    # self.output.broadcast(token)\n" + "    return\n\n");
214        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-30\" y=\"-15\" "
215                + "width=\"60\" height=\"30\" " + "style=\"fill:black\"/>\n"
216                + "<text x=\"-22\" y=\"4\""
217                + "style=\"font-size:14; fill:white; font-family:SansSerif\">"
218                + "Python</text>\n" + "</svg>\n");
219    }
220
221    ///////////////////////////////////////////////////////////////////
222    ////                     ports and parameters                  ////
223
224    /** The Jython class name to be invoked.  The default value is
225     *  "Main", which indicates that the <i>script</i> parameter
226     *  should define a class named "Main".  If the <i>script</i>
227     *  parameter imports a Jython module, for example: "import Foo",
228     *  then Foo.py should define a class named "Main" and this
229     *  parameter should have the value "Foo.Main".  If the value of
230     *  this parameter is anything other than "Main", then
231     *  preinitialize() will reread the script.  This is how Jython
232     *  modules can be used.  Note that the <i>jythonClassName</i>
233     *  parameter should be set to the value of the Jython class name
234     *  before changing the <i>script</i> parameter to import a Jython
235     *  module.
236     */
237    public StringAttribute jythonClassName;
238
239    /** The script that specifies the function of this actor.
240     *  The default value is a script that copies the input to the output.
241     */
242    public StringAttribute script;
243
244    ///////////////////////////////////////////////////////////////////
245    ////                         public methods                    ////
246
247    /** If <i>script</i> is changed, invoke the python interpreter to
248     *  evaluate the script.
249     *  @param attribute The attribute that changed.
250     *  @exception IllegalActionException If there is any error in evaluating
251     *   the script.
252     */
253    @Override
254    public void attributeChanged(Attribute attribute)
255            throws IllegalActionException {
256        if (attribute == script) {
257            _evaluateScript();
258        } else {
259            super.attributeChanged(attribute);
260        }
261    }
262
263    /** Clone the actor into the specified workspace. This calls the
264     *  base class and then properly sets private variables.
265     *  @param workspace The workspace for the new object.
266     *  @return A new actor.
267     *  @exception CloneNotSupportedException If a derived class contains
268     *   an attribute that cannot be cloned.
269     */
270    @Override
271    public Object clone(Workspace workspace) throws CloneNotSupportedException {
272        PythonScript newObject = (PythonScript) super.clone(workspace);
273
274        newObject._class = null;
275        newObject._methodMap = new HashMap();
276        newObject._object = null;
277        return newObject;
278    }
279
280    /** Send the message to all registered debug listeners. In the script,
281     *  use <code>self.actor.debug()</code> to call this method.
282     *  @param message The debug message.
283     */
284    public void debug(String message) {
285        if (_debugging) {
286            _debug("From script: ", message);
287        }
288    }
289
290    /** Invoke the fire() method if defined in the script.
291     *  @exception IllegalActionException If there is any error in calling the
292     *   fire() method defined by the script.
293     */
294    @Override
295    public void fire() throws IllegalActionException {
296        super.fire();
297        _invokeMethod("fire", null);
298    }
299
300    /** Invoke the initialize() method if defined in the script.
301     *  @exception IllegalActionException If there is any error in calling the
302     *   initialize() method defined by the script.
303     */
304    @Override
305    public void initialize() throws IllegalActionException {
306        super.initialize();
307        _invokeMethod("initialize", null);
308    }
309
310    /** Return true if this actor has at least one debug listener.
311     *  @return True if this actor has at least one debug listener.
312     */
313    public boolean isDebugging() {
314        return _debugging;
315    }
316
317    /** Invoke the postfire() method if defined in the script. Return true
318     *  when the method return value is not zero, or the method does not
319     *  return a value, or the method is not defined in the script.
320     *  @return False if postfire() is defined in the script and returns 0,
321     *   true otherwise.
322     *  @exception IllegalActionException If there is any error in calling the
323     *   postfire() method defined by the script.
324     */
325    @Override
326    public boolean postfire() throws IllegalActionException {
327        boolean defaultResult = super.postfire();
328        PyObject postfireResult = _invokeMethod("postfire", null);
329
330        if (postfireResult != null) {
331            return postfireResult.__nonzero__();
332        } else {
333            return defaultResult;
334        }
335    }
336
337    /** Invoke the prefire() method if defined in the script. Return true
338     *  when the method return value is not zero, or the method does not
339     *  return a value, or the method is not defined in the script.
340     *  @return False if prefire() is defined in the script and returns 0,
341     *   true otherwise.
342     *  @exception IllegalActionException If there is any error in calling the
343     *   prefire() method.
344     */
345    @Override
346    public boolean prefire() throws IllegalActionException {
347        boolean defaultResult = super.prefire();
348        PyObject prefireResult = _invokeMethod("prefire", null);
349
350        if (prefireResult != null) {
351            return prefireResult.__nonzero__();
352        } else {
353            return defaultResult;
354        }
355    }
356
357    /** Create an instance of the parameter named by the
358     *  jythonClassName parameter that is defined in the script.  Add
359     *  all parameters and ports of this actor as attributes of the
360     *  object, so that they become accessible to the methods defined
361     *  in the script.
362     *  @exception IllegalActionException If there is any error in
363     *   creating an instance of the class named by the
364     *   jythonClassName class defined in the script.
365     */
366    @Override
367    public void preinitialize() throws IllegalActionException {
368        super.preinitialize();
369        if (_debugging) {
370            _interpreter.exec("print sys.path");
371        }
372
373        _object = _createObject();
374        _invokeMethod("preinitialize", null);
375    }
376
377    /** Invoke the stop() method if defined in the script. Ignore any error
378     *  in calling the method.
379     */
380    @Override
381    public void stop() {
382        super.stop();
383
384        try {
385            _invokeMethod("stop", null);
386        } catch (IllegalActionException e) {
387            if (_debugging) {
388                _debug(e.getMessage());
389            }
390        }
391    }
392
393    /** Invoke the stopFire() method if defined in the script. Ignore any error
394     *  in calling the method.
395     */
396    @Override
397    public void stopFire() {
398        super.stopFire();
399
400        try {
401            _invokeMethod("stopFire", null);
402        } catch (IllegalActionException e) {
403            if (_debugging) {
404                _debug(e.getMessage());
405            }
406        }
407    }
408
409    /** Invoke the terminate() method if defined in the script. Ignore any
410     *  error in calling the method.
411     */
412    @Override
413    public void terminate() {
414        super.terminate();
415
416        try {
417            _invokeMethod("terminate", null);
418        } catch (IllegalActionException e) {
419            if (_debugging) {
420                _debug(e.getMessage());
421            }
422        }
423    }
424
425    /** Invoke the wrapup() method if defined in the script. Ignore any error
426     *  in calling the method.
427     *  @exception IllegalActionException If there is any error in calling the
428     *   wrapup() method defined in the script.
429     */
430    @Override
431    public void wrapup() throws IllegalActionException {
432        super.wrapup();
433        _invokeMethod("wrapup", null);
434    }
435
436    ///////////////////////////////////////////////////////////////////
437    ////                         private methods                   ////
438
439    /**  Create an instance of the class named by the jythonClassName
440     *  parameter which is defined in the script.  Add all parameters
441     *  and ports of this actor as attributes of the object, so that
442     *  they become accessible to the methods defined in the
443     *  script.
444     *  @exception IllegalActionException If there is any error in
445     *  creating an instance of the class named by the jythonClassName
446     *  parameter defined in the script.
447     */
448    private PyObject _createObject() throws IllegalActionException {
449        // Create an instance by using the __call__ method
450        // of the class object
451        if (_class == null || !jythonClassName.getExpression().equals("Main")) {
452            // Since _class is null, we could have been cloned.
453            // Evaluate the script so that we do not use a different
454            // script of another python actor. (This will set _class).
455            _evaluateScript();
456        }
457        PyObject object = _class.__call__();
458
459        if (object == null) {
460            throw new IllegalActionException(this,
461                    "Error in creating an instance of the "
462                            + jythonClassName.getExpression()
463                            + "defined in the script.");
464        }
465
466        // set up access to this actor
467        // first create an attribute "actor" on the object
468        // the PyObject class does not allow adding a new attribute to the
469        // object
470        object.__setattr__("actor", PyJavaType.wrapJavaObject(this));
471
472        // give the object access to attributes and ports of this actor
473        Iterator attributes = attributeList().iterator();
474
475        while (attributes.hasNext()) {
476            Attribute attribute = (Attribute) attributes.next();
477            String mangledName = _mangleName(attribute.getName());
478
479            if (_debugging) {
480                _debug("set up reference to attribute \"" + attribute.getName()
481                        + "\" as \"" + mangledName + "\"");
482            }
483
484            object.__setattr__(new PyString(mangledName),
485                    PyJavaType.wrapJavaObject(attribute));
486        }
487
488        Iterator ports = portList().iterator();
489
490        while (ports.hasNext()) {
491            Port port = (Port) ports.next();
492            String mangledName = _mangleName(port.getName());
493
494            if (_debugging) {
495                _debug("set up reference to port \"" + port.getName()
496                        + "\" as \"" + mangledName + "\"");
497            }
498
499            object.__setattr__(new PyString(mangledName),
500                    PyJavaType.wrapJavaObject(port));
501        }
502
503        // populate the method map
504        for (int i = 0; i < _METHOD_NAMES.length; ++i) {
505            String methodName = _METHOD_NAMES[i];
506            PyMethod method = null;
507
508            try {
509                method = (PyMethod) object.__findattr__(methodName);
510            } catch (ClassCastException ex) {
511                // the object has an attribute with the methodName but
512                // is not a method, ignore
513            }
514
515            _methodMap.put(methodName, method);
516        }
517
518        return object;
519    }
520
521    /**  Evaluate the script by invoking the Jython interpreter.
522     *  @exception IllegalActionException If the script does
523     *  not define a class with the name of the value of the
524     *  jythonClassName parameter, or the python interpreter cannot be
525     *  initialized.
526     */
527    private void _evaluateScript() throws IllegalActionException {
528        synchronized (_interpreter) {
529            String pythonScript = script.getExpression();
530
531            try {
532                if (_debugging) {
533                    _debug("PythonScript: evaluating " + pythonScript);
534                }
535                _interpreter.exec(pythonScript);
536            } catch (Exception ex) {
537                if (ex instanceof PyException) {
538                    _reportScriptError((PyException) ex,
539                            "Error in evaluating script:\n");
540                } else {
541                    throw new IllegalActionException(this, ex,
542                            "Error in evaluating script:\n");
543                }
544            }
545
546            // Get the class defined by the script.
547            try {
548                _class = (PyClass) _interpreter
549                        .get(jythonClassName.getExpression());
550            } catch (ClassCastException ex) {
551                try {
552                    PyModule module = (PyModule) _interpreter
553                            .get(jythonClassName.getExpression());
554                    _class = (PyClass) module.__findattr_ex__("Main");
555                } catch (ClassCastException ex2) {
556                    throw new IllegalActionException(this, ex,
557                            "Failed to cast _interpreter.get(jythonClassName.getExpression()) "
558                                    + " which is of type "
559                                    + _interpreter
560                                            .get(jythonClassName
561                                                    .getExpression())
562                                            .getClass().getName()
563                                    + " to PyClass.");
564                }
565            }
566
567            if (_class == null) {
568                throw new IllegalActionException(this,
569                        "The script does not define a \""
570                                + jythonClassName.getExpression()
571                                + " \" class, try setting the jythonClassName parameter "
572                                + "or have the script start with \"class "
573                                + jythonClassName.getExpression() + "\".");
574            }
575        }
576    }
577
578    /**  Invoke the specified method on the instance of the class
579     *  named by the jythonClassName parameter.  Any argument that is
580     *  not an instance of PyObject is wrapped in an instance of
581     *  PyJavaType. The result of invoking the method is returned.
582     *  @exception IllegalActionException If there is any
583     *  error in calling the method.
584     */
585    private PyObject _invokeMethod(String methodName, Object[] args)
586            throws IllegalActionException {
587        PyMethod method = (PyMethod) _methodMap.get(methodName);
588        PyObject returnValue = null;
589
590        if (method != null) {
591            try {
592                if (args == null || args.length == 0) {
593                    try {
594                        returnValue = method.__call__();
595                    } catch (Exception ex) {
596                        // If the inner exception is TerminateProcessException,
597                        // then get the exception and rethrow it.
598                        if (ex instanceof PyException) {
599                            PyException pyException = (PyException) ex;
600                            Object exceptionValue = pyException.value
601                                    .__tojava__(Exception.class);
602                            if (exceptionValue instanceof Exception) {
603                                Exception innerException = (Exception) exceptionValue;
604                                if (innerException instanceof TerminateProcessException) {
605                                    // Work around bug reported by
606                                    // Norbert Podhorszki
607                                    // See python/test/auto/PythonScalePN.xml
608                                    throw (TerminateProcessException) innerException;
609                                } else {
610                                    throw ex;
611                                }
612                            } else {
613                                // Test PythonScript-2.5 illustrates
614                                // why we need this.
615                                throw ex;
616                            }
617                        } else {
618                            throw ex;
619                        }
620                    }
621                } else {
622                    PyObject[] convertedArgs = new PyObject[args.length];
623
624                    for (int i = 0; i < args.length; ++i) {
625                        if (!(args[i] instanceof PyObject)) {
626                            convertedArgs[i] = PyJavaType
627                                    .wrapJavaObject(args[i]);
628                        } else {
629                            convertedArgs[i] = (PyObject) args[i];
630                        }
631                    }
632
633                    returnValue = _object.__call__(convertedArgs);
634                }
635            } catch (TerminateProcessException terminate) {
636                // Rethrow the terminate exception.
637                // See python/test/auto/PythonScalePN.xml
638                throw terminate;
639            } catch (Exception ex) {
640                String messagePrefix = "Error in invoking the " + methodName
641                        + " method:\n";
642                if (ex instanceof PyException) {
643                    _reportScriptError((PyException) ex, messagePrefix);
644                } else {
645                    throw new IllegalActionException(this, ex, messagePrefix);
646                }
647            }
648        }
649
650        return returnValue;
651    }
652
653    /*  Mangle the given name (usually the name of an entity, or a parameter,
654     *  or a port). Any character that is not legal in Java identifiers is
655     *  changed to the underscore character.
656     */
657    private String _mangleName(String name) {
658        char[] nameChars = name.toCharArray();
659        boolean mangled = false;
660
661        for (int i = 0; i < nameChars.length; ++i) {
662            if (!Character.isJavaIdentifierPart(nameChars[i])) {
663                nameChars[i] = '_';
664                mangled = true;
665            }
666        }
667
668        if (mangled) {
669            return new String(nameChars);
670        }
671
672        return name;
673    }
674
675    /*  Report an error in evaluating the script or calling a method defined
676     *  in the script.
677     */
678    private void _reportScriptError(PyException ex, String messagePrefix)
679            throws IllegalActionException {
680        String message = ex.toString();
681        int i = message.indexOf("line");
682
683        if (i >= 0) {
684            message = message.substring(i);
685        }
686
687        throw new IllegalActionException(this, ex, messagePrefix + message);
688    }
689
690    ///////////////////////////////////////////////////////////////////
691    ////                         private variables                 ////
692    // The class defined in the script.
693    private PyClass _class;
694
695    // The python interpreter.
696    //private static PythonInterpreter _interpreter = new PythonInterpreter();
697    private static PythonInterpreter _interpreter;
698
699    static {
700        try {
701            // If the python.home property is not set, then set it
702            // so that we can figure out where to write the jython cache.
703            //
704            // Under Webstart python/core/PySystemState.findRoot() first
705            // looks for the python.home property, so if it is
706            // not set we set it.
707            if (System.getProperty("python.home") == null) {
708                // Look for jython.jar in the classpath
709                // Start of code based on python/core/PySystemState.findRoot()
710                String classpath = StringUtilities
711                        .getProperty("java.class.path");
712
713                int jythonIndex = -1;
714                if (classpath == null) {
715                    System.setProperty("python.home",
716                            StringUtilities.getProperty("user.home"));
717                } else {
718                    jythonIndex = classpath.toLowerCase(Locale.getDefault())
719                            .indexOf("jython.jar");
720                }
721
722                if (jythonIndex == -1) {
723                    // We did not find jython.jar, so set it to user.home.
724                    // WebStart will end up here.
725                    System.setProperty("python.home",
726                            StringUtilities.getProperty("user.home"));
727                } else {
728                    // We found jython.jar, return the parent directory.
729                    // Under WebStart, jython.jar will not be in the classpath
730                    int start = classpath.lastIndexOf(
731                            java.io.File.pathSeparator, jythonIndex) + 1;
732                    System.setProperty("python.home",
733                            classpath.substring(start, jythonIndex));
734                }
735
736                // End of code based on python/core/PySystemState.findRoot()
737            }
738        } catch (Exception ex) {
739            // Ignore, we are probably under an an applet
740            System.err.println(
741                    "Warning: PythonScript threw an exception.  Perhaps we are under an applet?");
742            ex.printStackTrace();
743        }
744
745        try {
746            _interpreter = new PythonInterpreter();
747        } catch (java.security.AccessControlException ex) {
748            // In an applet, instantiating a PythonInterpreter
749            // causes PySystemState.initialize() to call
750            // System.getProperties(), which throws an exception
751            // The solution is to pass our own custom Properties
752            // Properties that are accessible via an applet.
753            String[] propertyNames = { "file.separator", "line.separator",
754                    "path.separator", "java.class.version", "java.vendor",
755                    "java.vendor.url", "java.version", "os.name", "os.arch",
756                    "os.version" };
757            Properties preProperties = new Properties();
758
759            for (String propertyName : propertyNames) {
760                preProperties.setProperty(propertyName,
761                        System.getProperty(propertyName));
762            }
763
764            PySystemState.initialize(preProperties, null, new String[] { "" });
765            _interpreter = new PythonInterpreter();
766        }
767
768        try {
769            //String ptIIDir = StringUtilities.getProperty("ptolemy.ptII.dir");
770            _interpreter.exec("import sys\n");
771            //_interpreter.exec("sys.path.append('" + ptIIDir
772            //        + "/ptolemy/actor/lib/python/test/')");
773
774        } catch (Exception ex) {
775            ExceptionInInitializerError error = new ExceptionInInitializerError(
776                    "The python command \"import sys\" failed.");
777            error.initCause(ex);
778            throw error;
779        }
780
781        String className = "ptolemy.kernel.util.NamedObj";
782        String classResource = ClassUtilities.lookupClassAsResource(className);
783
784        if (classResource != null) {
785            //System.out.println("PythonScript: className: " + classResource);
786            File classFile = new File(classResource);
787
788            if (classFile.isDirectory()) {
789                PySystemState.add_extdir(classResource);
790            } else {
791                PySystemState.add_classdir(classResource);
792            }
793        }
794    }
795
796    // Map from method name to PyMethod objects.
797    private HashMap _methodMap = new HashMap();
798
799    // The instance of the jythonClassName class defined in the script.
800    private PyObject _object;
801
802    // Invocation of methods named in this list is delegated to the instance
803    // of the jythonClassName class defined in the script.
804    // Listed here are all methods of the Executable interface, except
805    // iterate().
806    private static final String[] _METHOD_NAMES = { "fire", "initialize",
807            "postfire", "prefire", "preinitialize", "stop", "stopFire",
808            "terminate", "wrapup" };
809}