001/* Utilities for accessing the Matlab engine.
002
003 Copyright (c) 2003-2015 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 OR RESEARCH IN MOTION
012 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
013 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
014 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
015 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED
019 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
021 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
022 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION
023 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
024 ENHANCEMENTS, OR MODIFICATIONS.
025
026
027 */
028package ptolemy.data.expr;
029
030import java.lang.reflect.InvocationTargetException;
031import java.lang.reflect.Method;
032import java.util.Iterator;
033import java.util.Set;
034import java.util.StringTokenizer;
035
036import ptolemy.data.StringToken;
037import ptolemy.kernel.util.IllegalActionException;
038
039///////////////////////////////////////////////////////////////////
040//// MatlabUtilities
041
042/** This class provides access to the Ptolemy Matlab interface
043 in ptolemy.matlab by using reflection.
044
045 @author Christopher Hylands, Steve Neuendorffer
046 @author Zoltan Kemenczy, Research in Motion Ltd.
047 @version $Id$
048 @since Ptolemy II 2.1
049 @Pt.ProposedRating Green (neuendor)
050 @Pt.AcceptedRating Yellow (neuendor)
051 @see ptolemy.data.expr.ParseTreeEvaluator
052 */
053public class MatlabUtilities {
054    /** Evaluate a Matlab expression within a scope.
055     *        The expression argument is of the form
056     *  <em>matlab("expression", arg1, arg2, ...)</em>, where
057     *  <em>arg1, arg2, ... </em>is a list of Variables appearing in
058     *  <em>"expression"</em>. Note that this form of invoking matlab
059     *  is limited to returning only the first return value of a
060     *  matlab function. If you need multiple return values, use the
061     *  matlab  actor.<p>
062     *
063     *  Note that having an instance of {@link
064     *  ptolemy.matlab.Expression} in the model will keep the matlab
065     *  engine open from model preinitialize() to wrapup() and hence
066     *  opening/closing of additional Engine instances done by this
067     *  matlab expression evaluator becomes fast. Most users should
068     *  prefer to use {@link ptolemy.matlab.Expression} and resort to
069     *  this mechanism of invoking matlab only where necessary, e.g. in
070     *  FSM transition action expressions (which was the reason for
071     *  introducing this form of matlab engine access).<p>
072     *
073     *  If a "packageDirectories" Parameter is in the scope of this
074     *  expression, its value is added to the matlab path while the
075     *  expression is being executed (like {@link
076     *  ptolemy.matlab.Expression}).
077     *  @param expression The Matlab expression to be evaluated
078     *  @param variableNames The Matlab variables required for evaluating
079     *  the expression.   Each element of the Set names a Token that
080     *  is found in the scope.
081     *  @param scope The scope to evaluate the expression within.
082     *  @return The results of the evaluation
083     *  @exception IllegalActionException If there is a problem initializing
084     *  the Matlab interface, invoking the Matlab engine or accessing a
085     *  a Token.
086     */
087    public static ptolemy.data.Token evaluate(String expression,
088            Set variableNames, ParserScope scope)
089            throws IllegalActionException {
090        try {
091            if (_engineClass == null) {
092                _initialize();
093            }
094
095            ptolemy.data.Token result = null;
096
097            //MatlabEngineInterface matlabEngine =
098            //    MatlabEngineFactory.createEngine();
099            // Engine matlabEngine = new Engine();
100            Object matlabEngine = null;
101
102            try {
103                matlabEngine = _engineClass.newInstance();
104            } catch (InstantiationException ex) {
105                throw new IllegalActionException(null, ex,
106                        "Failed to instantiate ptolemy.matlab.Engine");
107            }
108
109            //long[] engine = matlabEngine.open();
110            // Opening the matlab engine each time is very slow
111            _engine = (long[]) _engineOpen.invoke(matlabEngine, new Object[0]);
112
113            try {
114                synchronized (
115                //matlabEngine.getSemaphore();
116                _engine) {
117                    // matlabEngine is not very good since it is
118                    // "local".
119                    // (zk:) I would recommend removing the static,
120                    // synchronized engine instance and open/close a new
121                    // Engine every time (see updated javadoc for this
122                    // function).
123                    String addPathCommand = null; // Assume none
124                    ptolemy.data.Token previousPath = null;
125                    ptolemy.data.Token packageDirectories = null;
126
127                    if (scope != null) {
128                        packageDirectories = scope.get("packageDirectories");
129                    }
130
131                    if (packageDirectories != null
132                            && packageDirectories instanceof StringToken) {
133                        StringTokenizer dirs = new StringTokenizer(
134                                ((StringToken) packageDirectories)
135                                        .stringValue(),
136                                ",");
137                        StringBuffer cellFormat = new StringBuffer(512);
138                        cellFormat.append("{");
139
140                        if (dirs.hasMoreTokens()) {
141                            cellFormat.append("'" + UtilityFunctions
142                                    .findFile(dirs.nextToken()) + "'");
143                        }
144
145                        while (dirs.hasMoreTokens()) {
146                            cellFormat.append(",'" + UtilityFunctions
147                                    .findFile(dirs.nextToken()) + "'");
148                        }
149
150                        cellFormat.append("}");
151
152                        if (cellFormat.length() > 2) {
153                            addPathCommand = "addedPath_="
154                                    + cellFormat.toString()
155                                    + ";addpath(addedPath_{:});";
156
157                            //matlabEngine.evalString
158                            //    (engine, "previousPath_=path");
159                            _engineEvalString.invoke(matlabEngine,
160                                    new Object[] { _engine,
161                                            "previousPath_=path" });
162
163                            //previousPath = matlabEngine.get
164                            //    (engine, "previousPath_");
165                            previousPath = (ptolemy.data.Token) _engineGet
166                                    .invoke(matlabEngine, new Object[] {
167                                            _engine, "previousPath_" });
168                        }
169                    }
170
171                    //matlabEngine.evalString
172                    //    (engine, "clear variables;clear globals");
173                    _engineEvalString.invoke(matlabEngine, new Object[] {
174                            _engine, "clear variables;clear globals" });
175
176                    if (addPathCommand != null) {
177                        // matlabEngine.evalString(engine, addPathCommand);
178                        _engineEvalString.invoke(matlabEngine,
179                                new Object[] { _engine, addPathCommand });
180                    }
181
182                    // Set matlab variables required for evaluating the
183                    // expression
184                    if (!variableNames.isEmpty()) {
185                        Iterator names = variableNames.iterator();
186
187                        while (names.hasNext()) {
188                            String name = (String) names.next();
189                            ptolemy.data.Token token = scope.get(name);
190
191                            if (token != null) {
192                                //matlabEngine.put
193                                //    (engine, name, token);
194                                _enginePut.invoke(matlabEngine,
195                                        new Object[] { _engine, name, token });
196                            }
197                        }
198                    }
199
200                    //matlabEngine.evalString(engine,
201                    //        "result__=" + expression);
202                    _engineEvalString.invoke(matlabEngine,
203                            new Object[] { _engine, "result__=" + expression });
204
205                    //result = matlabEngine.get(engine, "result__");
206                    result = (ptolemy.data.Token) _engineGet.invoke(
207                            matlabEngine, new Object[] { _engine, "result__" });
208
209                    if (previousPath != null) {
210                        // Restore the original engine path
211                        //matlabEngine.put
212                        //    (engine, name, token);
213                        _enginePut.invoke(matlabEngine, new Object[] { _engine,
214                                "previousPath_", previousPath });
215
216                        // matlabEngine.evalString(engine, "path(previousPath_)");
217                        _engineEvalString.invoke(matlabEngine, new Object[] {
218                                _engine, "path(previousPath_)" });
219                    }
220                }
221            } finally {
222                //matlabEngine.close(engine);
223                _engineClose.invoke(matlabEngine, new Object[] { _engine });
224            }
225
226            return result;
227        } catch (IllegalAccessException ex) {
228            throw new IllegalActionException(null, ex,
229                    "Problem invoking a method on " + "ptolemy.matlab.Engine");
230        } catch (InvocationTargetException ex) {
231            throw new IllegalActionException(null, ex,
232                    "Problem invoking a method of " + "ptolemy.matlab.Engine");
233        }
234    }
235
236    ///////////////////////////////////////////////////////////////////
237    ////                         private methods                   ////
238    // Initialize private variables.
239    private static void _initialize() throws IllegalActionException {
240        // These could be in the constructor, but since the evaluate()
241        // method is a static method, we break out the functionality into
242        // a separate method.
243        // Use reflection so that we can compile without
244        // ptolemy.matlab and we check to see if is present at runtime.
245        try {
246            _engineClass = Class.forName("ptolemy.matlab.Engine");
247        } catch (Throwable throwable) {
248            // UnsatsifiedLinkError is an Error, not an Exception, so
249            // we catch Throwable
250            throw new IllegalActionException(null, throwable,
251                    "Failed to load ptolemy.matlab.Engine class");
252        }
253
254        try {
255            // Methods of ptolemy.matlab.Engine, in alphabetical order.
256            _engineClose = _engineClass.getMethod("close",
257                    new Class[] { long[].class });
258
259            _engineEvalString = _engineClass.getMethod("evalString",
260                    new Class[] { long[].class, String.class });
261
262            _engineGet = _engineClass.getMethod("get",
263                    new Class[] { long[].class, String.class });
264
265            _engineOpen = _engineClass.getMethod("open", new Class[0]);
266
267            _enginePut = _engineClass.getMethod("put", new Class[] {
268                    long[].class, String.class, ptolemy.data.Token.class });
269        } catch (NoSuchMethodException ex) {
270            throw new IllegalActionException(null, ex,
271                    "Problem finding a method of " + "ptolemy.matlab.Engine");
272        }
273    }
274
275    ///////////////////////////////////////////////////////////////////
276    ////                         private variables                 ////
277    // The matlab engine pointer that is returned by matlab.Engine.open
278    // We cache this value so that each time we evaluate a Matlab
279    // expression, we need not necessarily reopen the Engine.
280    private static long[] _engine;
281
282    // The class of ptolemy.matlab.Engine
283    private static Class _engineClass;
284
285    // Methods of ptolemy.matlab.Engine, in alphabetical order.
286    // ptolemy.matlab.Engine.close();
287    private static Method _engineClose;
288
289    // ptolemy.matlab.Engine.evalString();
290    private static Method _engineEvalString;
291
292    // ptolemy.matlab.Engine.get();
293    private static Method _engineGet;
294
295    // ptolemy.matlab.Engine.open();
296    private static Method _engineOpen;
297
298    // ptolemy.matlab.Engine.put();
299    private static Method _enginePut;
300}