001/* Matlab Engine Interface
002
003 Copyright (c) 1998-2016 The Regents of the University of California and
004 Research in Motion Limited.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA OR RESEARCH IN MOTION
013 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
014 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
015 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
016 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF
017 SUCH DAMAGE.
018
019 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED
020 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
022 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
023 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION
024 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
025 ENHANCEMENTS, OR MODIFICATIONS.
026
027 PT_COPYRIGHT_VERSION_2
028 COPYRIGHTENDKEY
029
030 */
031package ptolemy.matlab;
032
033import ptolemy.data.ArrayToken;
034import ptolemy.data.BooleanToken;
035import ptolemy.data.ComplexMatrixToken;
036import ptolemy.data.ComplexToken;
037import ptolemy.data.DoubleMatrixToken;
038import ptolemy.data.DoubleToken;
039import ptolemy.data.IntMatrixToken;
040import ptolemy.data.IntToken;
041import ptolemy.data.MatrixToken;
042import ptolemy.data.RecordToken;
043import ptolemy.data.ScalarToken;
044import ptolemy.data.StringToken;
045import ptolemy.data.Token;
046import ptolemy.data.expr.UtilityFunctions;
047import ptolemy.kernel.util.IllegalActionException;
048import ptolemy.math.Complex;
049
050///////////////////////////////////////////////////////////////////
051//// Engine
052
053/**
054 Provides a java API to the matlab environment. It uses an intermediary
055 C++ language layer (ptmatlab) that converts between the java environment
056 using the Java Native Interface and the matlab environment using the
057 matlab engine API and associated mx-functions.<p>
058
059 The intermediary layer is built as a DLL on Windows systems
060 (ptmatlab.dll).  This shared library is placed into the $PTII/bin
061 directory (that should be in the user's path) when this package is
062 built. Ptmatlab depends on matlab's engine API shared libraries (libeng
063 and libmx) that should also be installed in the user's path (usually the
064 case when matlab is installed and matlab's bin directory is added to the
065 path).<p>
066
067 The bulk of the work done by this class is the conversion between
068 PtolemyII Tokens and matlab variables ("mxArrays").<p>
069
070 {@link #get(long[] eng, String name)} and
071 {@link ptolemy.matlab.Engine#get(long[], String,
072 Engine.ConversionParameters)} convert a matlab engine mxArray
073 (ma) variable to a Ptolemy II Token. Recursion is used if ma is a struct
074 or cell. The type of the Token returned is determined according to
075 the following table:
076
077 <table border="1">
078 <caption><em>Conversion from matlab to PtolemyII types (get())
079 </em></caption>
080 <tr><th>Matlab Type<th>PtolemyII Token
081 <tr>
082 <td>'double'
083 <td>Double, if mxArray dimension is 1x1 and
084 {@link Engine.ConversionParameters#getScalarMatrices} is true,
085 DoubleMatrix otherwise.
086 Complex, if mxArray is mxCOMPLEX, 1x1, and
087 {@link Engine.ConversionParameters#getScalarMatrices} is true,
088 ComplexMatrix otherwise.<br>
089 <em>Note:</em>
090 If {@link Engine.ConversionParameters#getIntMatrices} is true and
091 all matrix double values can be cast to integers without loss of
092 precision then an IntToken or IntTokenMatrix is returned.
093 <tr>
094 <td>'struct'
095 <td>RecordToken, if mxArray dimension 1x1, ArrayToken of ArrayTokens
096 of RecordTokens {{RecordToken,...}, {...}} ("two-dimensional" ArrayToken)
097 otherwise.
098 <tr>
099 <td>'cell'
100 <td>ArrayToken of whatever Tokens the cell elements resolve to through
101 recursion of _convertMxArrayToToken(). In the special case of a cell
102 array of doubles, an {int} is always returned if all cell double
103 values can be losslessly converted to integers.
104 Note that PtolemyII is more
105 restrictive here in that it requires all array elements to be of the
106 same type (not all matlab cell variables may be converted to PtolemyII
107 ArrayTokens).
108 <tr>
109 <td>'char'
110 <td>StringToken, if the mxArray is 1xn, ArrayToken of StringTokens
111 otherwise.
112 </table>
113 <p>
114 {@link #put(long[] eng, String name, Token t)} converts a PtolemyII
115 Token to a matlab engine mxArray. Recursion is used if t is a
116 RecordToken or ArrayToken. The type of mxArray created is determined
117 according to the following table.
118
119 <table border="1">
120 <caption><em>Conversion from PtolemyII to matlab types (put())
121 </em></caption>
122 <tr><th>PtolemyII Token<th>Matlab type
123 <tr>
124 <td>ArrayToken
125 <td>'cell', 1xn, elements are determined by recursing this method
126 on ArrayToken elements.
127 <tr>
128 <td>RecordToken
129 <td>'struct', 1x1, fields are determined by recursing this method on
130 RecordToken fields
131 <tr>
132 <td>StringToken
133 <td>'char', 1xn
134 <tr>
135 <td>ComplexMatrixToken
136 <td>'double', mxCOMPLEX, nxm
137 <tr>
138 <td>MatrixToken
139 <td>'double', mxREAL, nxm
140 <tr>
141 <td>ComplexToken
142 <td>'double', mxCOMPLEX, 1x1
143 <tr>
144 <td>ScalarToken
145 <td>'double', mxREAL, 1x1
146 </table>
147 <p>
148 Debug statements to stdout are enabled by calling
149 {@link #setDebugging} with a byte parameter &gt; 0. 1 enables basic tracing,
150 2 includes traces from the dll as well.
151
152 <p>{@link #evalString(long[], String)} send a string to the matlab
153 engine for evaluation.
154
155 {@link #open} and {@link #close} are used to open / close the
156 connection to the matlab engine.<p>
157
158 All callers share the same matlab engine and its workspace.
159 Methods of Engine synchronize on the static {@link #semaphore} to
160 prevent overlapping calls to the same method from different threads.
161 Use Engine. {@link #semaphore} to synchronize across multiple method
162 calls if needed.<p>
163
164 @author Zoltan Kemenczy and Sean Simmons, Research in Motion Limited.
165 @version $Id$
166 @since Ptolemy II 2.0
167 @Pt.ProposedRating Yellow (zkemenczy)
168 @Pt.AcceptedRating Red (cxh)
169 */
170public class Engine {
171    /** Load the "ptmatlab" native interface. Use a classpath-relative
172     * pathname without the shared library suffix (which is selected
173     * and appended by {@link UtilityFunctions#loadLibrary}) for
174     * portability. */
175    static {
176        UtilityFunctions.loadLibrary("ptolemy/matlab/ptmatlab");
177    }
178
179    /** Output buffer (allocated for each opened instance) size. */
180    static int engOutputBufferSize = 2048;
181
182    /** Used for synchronization. */
183    public static final Object semaphore = new Object();
184
185    // semaphore is public so that javadoc works.
186    // ptolemy.matlab.Expression also uses this semaphore
187
188    /** Data conversion parameters used by {@link
189     * ptolemy.matlab.Engine#get(long[], String,
190     * Engine.ConversionParameters)}. */
191    public static class ConversionParameters {
192        /** If true (default), 1x1 matrices are returned as
193         * appropriate ScalarToken.*/
194        public boolean getScalarMatrices = true;
195
196        /** If true, double matrices where all elements represent
197         * integers are returned as IntMatrixTokens (default false).*/
198        public boolean getIntMatrices = false;
199    }
200
201    /** Construct an instance of the matlab engine interface.
202     * The matlab engine is not activated at this time.
203     * <p>
204     * Ptmatlab.dll is loaded by the system library loader the
205     * first time this class is loaded.
206     * @see #open()
207     */
208    public Engine() {
209        debug = 0;
210    }
211
212    /** Enable/disable debug statements to stdout.
213     * @param d Non-zero to enable debug statements, zero to disable.
214     */
215    public void setDebugging(byte d) {
216        debug = d;
217    }
218
219    /** Open a connection to the default matlab engine installed on
220     * this host with its output buffered.
221     * @return long[2] retval engine handle; retval[0] is the real
222     * engine handle, retval[1] is a pointer to the engine output
223     * buffer; both should be preserved and passed to subsequent engine
224     * calls.
225     * @exception IllegalActionException If the matlab engine open is
226     * unsuccessful.  This will typically occur if ptmatlab (.dll)
227     * cannot be located or if the matlab bin directory is not in the
228     * path.
229     * @see #open(String, boolean)
230     */
231    public long[] open() throws IllegalActionException {
232        return open(null, true); // Use default invocation, with
233
234        // output buffering
235    }
236
237    /** Open a connection to the default matlab engine installed on
238     * this host with specified output buffering.
239     * @param needOutput selects whether the output should be buffered
240     * or not.
241     * @return long[2] retval engine handle; retval[0] is the real
242     * engine handle, retval[1] is a pointer to the engine output
243     * buffer; both should be preserved and passed to subsequent engine
244     * calls.
245     * @exception IllegalActionException If the matlab engine open is
246     * unsuccessful.  This will typically occur if ptmatlab (.dll)
247     * cannot be located or if the matlab bin directory is not in the
248     * path.
249     * @see #open(String, boolean)
250     */
251    public long[] open(boolean needOutput) throws IllegalActionException {
252        return open(null, needOutput); // Use default invocation
253
254        // output buffering
255    }
256
257    /** Open a connection to a matlab engine.<p>
258     * For more information, see the matlab engine API reference engOpen()
259     * @param startCmd hostname or command to use to start the engine.
260     * @param needOutput selects whether the output should be buffered
261     * or not.
262     * @return long[2] retval engine handle; retval[0] is the real
263     * engine handle, retval[1] is a pointer to the engine output
264     * buffer; both should be preserved and passed to subsequent engine
265     * calls.
266     * @exception IllegalActionException If the matlab engine open is
267     * unsuccessful.  This will typically occur if ptmatlab (.dll)
268     * cannot be located or if the matlab bin directory is not in the
269     * path.
270     * @see #getOutput(long[])
271     */
272    public long[] open(String startCmd, boolean needOutput)
273            throws IllegalActionException {
274        long[] retval = new long[2];
275
276        synchronized (semaphore) {
277            retval[0] = ptmatlabEngOpen(startCmd);
278
279            if (retval[0] == 0) {
280                String Path = "";
281                try {
282                    Path = System.getenv("PATH");
283                } catch (Throwable throwable) {
284                    Path = throwable.toString();
285                }
286                throw new IllegalActionException("matlabEngine.open(" + startCmd
287                        + ") : can't find Matlab engine. "
288                        + "The PATH for this process is \"" + Path
289                        + "\". Try starting "
290                        + "\"matlab\" by hand from a shell to verify that "
291                        + "Matlab is set up properly and the license is "
292                        + "correct.\n"
293                        + "Under Windows, try running \"matlab /regserver\", "
294                        + "the Matlab C API communicates with Matlab via COM, "
295                        + "and apparently the COM interface is not "
296                        + "automatically registered when Matlab is "
297                        + "installed.\n"
298                        + "Under Mac OS X, 'matlab' must be in the PATH, "
299                        + "it may be easiest to create a link from /usr/bin/matlab "
300                        + "to the location of the matlab script:\n "
301                        + "sudo ln -s /Applications/MATLAB_R2011a.app/bin/matlab /usr/bin/matlab\n"
302                        + "Under Linux and other types of UNIX, csh must be "
303                        + "installed in /bin/csh.");
304            }
305
306            if (needOutput) {
307                retval[1] = ptmatlabEngOutputBuffer(retval[0],
308                        engOutputBufferSize);
309            } // else retval[1] = 0;
310
311            if (debug > 0) {
312                System.out.println(retval[0] + " = matlabEngine.open(\""
313                        + startCmd + "\")");
314            }
315        }
316
317        return retval;
318    }
319
320    /** Close a connection to a matlab engine.
321     * This will also close the matlab engine if this instance was the last
322     * user of the matlab engine.
323     * <p>
324     * For more information, see matlab engine API reference engClose()
325     * @param eng An array of longs with length 2. eng[0] is the real
326     * engine handle, eng[1] is a pointer to the engine output
327     * buffer.
328     * @return The value returned by calling engClose() in the
329     * Matlab interface.
330     */
331    public int close(long[] eng) {
332        int retval = 0;
333
334        if (eng == null) {
335            return -1;
336        }
337
338        synchronized (semaphore) {
339            if (debug > 0) {
340                System.out.println("matlabEngine.close(" + eng[0] + ")");
341            }
342
343            retval = ptmatlabEngClose(eng[0], eng[1]);
344        }
345
346        return retval;
347    }
348
349    /** Copy of a common error message. */
350    static String errNotOpened = "matlab engine not opened.";
351
352    /** Send a string for evaluation to the matlab engine.
353     * @param eng An array of two longs; eng[0] is the real
354     * engine handle, eng[1] is a pointer to the engine output
355     * buffer.
356     * @param evalStr string to evaluate.
357     * @return The value returned by the ptmatlabEngEvalString() native method.
358     * @exception IllegalActionException If the matlab engine is not opened.
359     */
360    public int evalString(long[] eng, String evalStr)
361            throws IllegalActionException {
362        int retval;
363
364        synchronized (semaphore) {
365            if (eng == null || eng[0] == 0) {
366                throw new IllegalActionException(
367                        "matlabEngine.evalStr(): " + errNotOpened);
368            }
369
370            if (debug > 0) {
371                System.out.println(
372                        "matlabEngine.evalString(\"" + evalStr + "\")");
373            }
374
375            retval = ptmatlabEngEvalString(eng[0], evalStr);
376        }
377
378        return retval;
379    }
380
381    /** Return a Token from the matlab engine using default
382     * {@link Engine.ConversionParameters} values.
383     * @param eng An array of longs with length 2. eng[0] is the real
384     * engine handle, eng[1] is a pointer to the engine output
385     * buffer.
386     * @param name Matlab variable name used to initialize the returned Token
387     * @return PtolemyII Token.
388     * @exception IllegalActionException If the matlab engine is not opened, or
389     * if the matlab variable was not found in the engine. In this case, the
390     * matlab engine's stdout is included in the exception message.
391     * @see Expression
392     */
393    public Token get(long[] eng, String name) throws IllegalActionException {
394        return get(eng, name, new ConversionParameters());
395    }
396
397    /** Return a Token from the matlab engine using specified
398     * {@link Engine.ConversionParameters} values.
399     * @param eng An array of longs with length 2. eng[0] is the real
400     * engine handle, eng[1] is a pointer to the engine output
401     * buffer.
402     * @param name Matlab variable name used to initialize the returned Token
403     * @param par The ConversionParameter to use.
404     * @return PtolemyII Token.
405     * @exception IllegalActionException If the matlab engine is not opened, or
406     * if the matlab variable was not found in the engine. In this case, the
407     * matlab engine's stdout is included in the exception message.
408     * @see Expression
409     */
410    public Token get(long[] eng, String name, ConversionParameters par)
411            throws IllegalActionException {
412        Token retval = null;
413
414        synchronized (semaphore) {
415            if (eng == null || eng[0] == 0) {
416                throw new IllegalActionException(
417                        "matlabEngine.get(): " + errNotOpened);
418            }
419
420            long ma = ptmatlabEngGetArray(eng[0], name);
421
422            if (ma == 0) {
423                throw new IllegalActionException("matlabEngine.get(" + name
424                        + "): can't find matlab " + "variable \"" + name
425                        + "\"\n" + getOutput(eng).stringValue());
426            }
427
428            retval = _convertMxArrayToToken(ma, par);
429            ptmatlabDestroy(ma, name);
430
431            if (debug > 0) {
432                System.out.println("matlabEngine.get(" + name + ") = "
433                        + retval.toString());
434            }
435        }
436
437        return retval;
438    }
439
440    /** Get last matlab stdout.
441     * @param eng An array of longs with length 2. eng[0] is the real
442     * engine handle, eng[1] is a pointer to the engine output
443     * buffer.
444     * @return PtolemyII StringToken
445     */
446    public StringToken getOutput(long[] eng) {
447        String str = "";
448
449        synchronized (semaphore) {
450            if (eng != null && eng[1] != 0) {
451                str = ptmatlabGetOutput(eng[1], engOutputBufferSize);
452            }
453        }
454
455        return new StringToken(str);
456    }
457
458    /** Create a matlab variable using name and a Token.
459     * @param eng An array of longs with length 2. eng[0] is the real
460     * engine handle, eng[1] is a pointer to the engine output
461     * buffer.
462     * @param name matlab variable name.
463     * @param t Token to provide value.
464     * @return The result of calling engPutArray() in the Matlab
465     * C library.
466     * @exception IllegalActionException If the engine is not opened.
467     * @see Engine
468     */
469    public int put(long[] eng, String name, Token t)
470            throws IllegalActionException {
471        int retval;
472
473        synchronized (semaphore) {
474            if (eng == null || eng[0] == 0) {
475                throw new IllegalActionException(
476                        "matlabEngine.put(): " + errNotOpened);
477            }
478
479            if (debug > 0) {
480                System.out.println(
481                        "matlabEngine.put(" + name + ", " + t.toString() + ")");
482            }
483
484            long ma = _createMxArray(name, t);
485            retval = ptmatlabEngPutArray(eng[0], name, ma);
486            ptmatlabDestroy(ma, name);
487        }
488
489        return retval;
490    }
491
492    ///////////////////////////////////////////////////////////////////
493    ////                         private methods                   ////
494    // Engine functions - native methods implemented in ptmatlab.cc.
495    private native long ptmatlabEngOpen(String startCmd);
496
497    private native int ptmatlabEngClose(long e, long outputBuffer);
498
499    private native int ptmatlabEngEvalString(long e, String s);
500
501    private native long ptmatlabEngGetArray(long e, String name);
502
503    private native int ptmatlabEngPutArray(long e, String name, long mxArray);
504
505    private native long ptmatlabEngOutputBuffer(long e, int n);
506
507    // C-Mx style functions
508    private native long ptmatlabCreateCellMatrix(String name, int n, int m);
509
510    private native long ptmatlabCreateString(String name, String s, int n,
511            int m);
512
513    private native long ptmatlabCreateDoubleMatrixOneDim(String name,
514            double[] a, int length);
515
516    private native long ptmatlabCreateDoubleMatrix(String name, double[][] a,
517            int n, int m);
518
519    private native long ptmatlabCreateComplexMatrixOneDim(String name,
520            Complex[] a, int length);
521
522    private native long ptmatlabCreateComplexMatrix(String name, Complex[][] a,
523            int n, int m);
524
525    private native long ptmatlabCreateStructMatrix(String name,
526            Object[] fieldNames, int n, int m);
527
528    private native void ptmatlabDestroy(long mxArray, String name);
529
530    private native long ptmatlabGetCell(long mxArray, int n, int m);
531
532    private native String ptmatlabGetClassName(long mxArray);
533
534    private native int[] ptmatlabGetDimensions(long mxArray);
535
536    private native Complex[][] ptmatlabGetComplexMatrix(long mxArray, int n,
537            int m);
538
539    private native double[][] ptmatlabGetDoubleMatrix(long mxArray, int n,
540            int m);
541
542    private native int[][] ptmatlabGetLogicalMatrix(long mxArray, int nRows,
543            int nCols);
544
545    private native String ptmatlabGetFieldNameByNumber(long mxArray, int k);
546
547    private native long ptmatlabGetFieldByNumber(long mxArray, int k, int n,
548            int m);
549
550    private native int ptmatlabGetNumberOfFields(long mxArray);
551
552    private native String ptmatlabGetString(long mxArray, int n);
553
554    private native String ptmatlabGetOutput(long outputBuffer, int n);
555
556    private native boolean ptmatlabIsComplex(long mxArray);
557
558    private native void ptmatlabSetCell(String name, long mxArray, int n, int m,
559            long valueMxArray);
560
561    private native void ptmatlabSetString(String name, long mxArray, int n,
562            String s, int slen);
563
564    private native void ptmatlabSetStructField(String name, long mxArray,
565            String fieldName, int n, int m, long valueMxArray);
566
567    // Converts a matlab engine mxArray (ma) variable to a Ptolemy II Token.
568    // @param ma Pointer to the matlab engine variable's mxArray
569    // structure as a java long.
570    // @return Ptolemy II Token of type that corresponds to ma's type.
571    // @exception IllegalActionException If ma cannot be obtained from
572    // the matlab engine, or if the mxArray type is not one of
573    // 'double', 'struct', 'char' or 'cell', or if not all elements of
574    // an ArrayToken to be created are of the same type.
575    // @see Engine
576    private Token _convertMxArrayToToken(long ma, ConversionParameters par)
577            throws IllegalActionException {
578        String maClassStr = ptmatlabGetClassName(ma);
579        int[] dims = ptmatlabGetDimensions(ma);
580        int nRows = dims[0];
581        int nCols = dims[1];
582        boolean scalarStructs = nCols == 1 && nRows == 1;
583        boolean scalarMatrices = nCols == 1 && nRows == 1
584                && par.getScalarMatrices;
585        Token retval = null;
586
587        if (maClassStr.equals("double")) {
588            if (ptmatlabIsComplex(ma)) {
589                Complex[][] a = ptmatlabGetComplexMatrix(ma, nRows, nCols);
590
591                if (a == null) {
592                    throw new IllegalActionException(
593                            "can't get complex matrix from matlab engine.");
594                }
595
596                if (scalarMatrices) {
597                    retval = new ComplexToken(a[0][0]);
598                } else {
599                    retval = new ComplexMatrixToken(a);
600                }
601            } else {
602                double[][] a = ptmatlabGetDoubleMatrix(ma, nRows, nCols);
603
604                if (a == null) {
605                    throw new IllegalActionException(
606                            "can't get double matrix from matlab engine.");
607                }
608
609                if (scalarMatrices) {
610                    double tmp = a[0][0];
611
612                    if (_doubleIsInteger(tmp)) {
613                        retval = new IntToken((int) tmp);
614                    } else {
615                        retval = new DoubleToken(tmp);
616                    }
617                } else {
618                    boolean allIntegers = par.getIntMatrices;
619
620                    for (int i = 0; allIntegers && i < a.length; i++) {
621                        for (int j = 0; allIntegers && j < a[0].length; j++) {
622                            allIntegers &= _doubleIsInteger(a[i][j]);
623                        }
624                    }
625
626                    if (allIntegers) {
627                        int[][] tmp = new int[a.length][a[0].length];
628
629                        for (int i = 0; i < a.length; i++) {
630                            for (int j = 0; j < a[0].length; j++) {
631                                tmp[i][j] = (int) a[i][j];
632                            }
633                        }
634
635                        retval = new IntMatrixToken(tmp);
636                    } else {
637                        retval = new DoubleMatrixToken(a);
638                    }
639                }
640            }
641        } else if (maClassStr.equals("logical")) {
642            int[][] a = ptmatlabGetLogicalMatrix(ma, nRows, nCols);
643
644            if (a == null) {
645                throw new IllegalActionException(
646                        "can't get logical matrix from matlab engine.");
647            }
648
649            if (scalarMatrices) {
650                retval = new IntToken(a[0][0]);
651            } else {
652                retval = new IntMatrixToken(a);
653            }
654        } else if (maClassStr.equals("struct")) {
655            int nfields = ptmatlabGetNumberOfFields(ma);
656            Token[] ta = new Token[nCols];
657            Token[] tr = new Token[nRows];
658            String[] fieldNames = new String[nfields];
659
660            for (int k = 0; k < nfields; k++) {
661                fieldNames[k] = ptmatlabGetFieldNameByNumber(ma, k);
662            }
663
664            Token[] fieldValues = new Token[nfields];
665
666            for (int n = 0; n < nRows; n++) {
667                for (int m = 0; m < nCols; m++) {
668                    for (int k = 0; k < nfields; k++) {
669                        long fma = ptmatlabGetFieldByNumber(ma, k, n, m);
670
671                        if (fma != 0) {
672                            fieldValues[k] = _convertMxArrayToToken(fma, par);
673                        } else {
674                            throw new IllegalActionException("can't get field "
675                                    + fieldNames[k] + "from matlab " + "struct "
676                                    + nRows + "x" + nCols);
677                        }
678                    }
679
680                    ta[m] = new RecordToken(fieldNames, fieldValues);
681                }
682
683                tr[n] = new ArrayToken(ta);
684            }
685
686            if (scalarStructs) {
687                retval = ((ArrayToken) tr[0]).getElement(0);
688            } else {
689                retval = new ArrayToken(tr);
690            }
691        } else if (maClassStr.equals("cell")) {
692            Token[] ta = new Token[nCols];
693            Token[] tr = new Token[nRows];
694
695            for (int n = 0; n < nRows; n++) {
696                boolean anyIntegers = false;
697                boolean anyDoubles = false;
698
699                for (int m = 0; m < nCols; m++) {
700                    long cma = ptmatlabGetCell(ma, n, m);
701
702                    if (cma != 0) {
703                        ta[m] = _convertMxArrayToToken(cma, par);
704
705                        // Track whether we get mixed types back
706                        if (ta[m] instanceof IntToken) {
707                            anyIntegers = true;
708                        } else if (ta[m] instanceof DoubleToken) {
709                            anyDoubles = true;
710                        }
711                    } // else - throw exception?
712                }
713
714                if (anyIntegers && anyDoubles) {
715                    for (int m = 0; m < ta.length; m++) {
716                        if (ta[m] instanceof IntToken) {
717                            ta[m] = DoubleToken.convert(ta[m]);
718                        }
719                    }
720                }
721
722                tr[n] = new ArrayToken(ta);
723
724                // If not all tokens are of the same, this will throw
725                // an exception.
726            }
727
728            if (nRows == 1) {
729                retval = tr[0];
730            } else {
731                retval = new ArrayToken(tr);
732            }
733        } else if (maClassStr.equals("char")) {
734            if (nRows == 1) {
735                retval = new StringToken(ptmatlabGetString(ma, 0));
736            } else {
737                Token[] ta = new Token[nRows];
738
739                for (int n = 0; n < nRows; n++) {
740                    ta[n] = new StringToken(ptmatlabGetString(ma, n));
741                }
742
743                retval = new ArrayToken(ta);
744            }
745        } else {
746            throw new IllegalActionException("no support for mxArray class "
747                    + maClassStr + " " + dims[0] + " x " + dims[1]);
748        }
749
750        return retval;
751    }
752
753    // Creates (recursively) a matlab engine mxArray given a Ptolemy II Token.
754    // @param name Matlab variable name to be created.
755    // @param t PtolemyII Token providing the value for the variable.
756    // @return Matlab engine mxArray pointer cast to java long.
757    // @exception IllegalActionException If array creation failed, or if the
758    // Token was not one of the types supported by _createMxArray().
759    // @see Engine
760    private long _createMxArray(String name, Token t)
761            throws IllegalActionException {
762        long ma = 0;
763
764        if (t instanceof ArrayToken) {
765            Token[] ta = ((ArrayToken) t).arrayValue();
766
767            if (!(ta[0] instanceof StringToken)) {
768                ma = ptmatlabCreateCellMatrix(name, 1, ta.length);
769
770                if (ma == 0) {
771                    throw new IllegalActionException(
772                            "couldn't create cell " + "array " + name);
773                }
774
775                for (int n = 0; n < ta.length; n++) {
776                    long fma = _createMxArray("(" + n + ")", ta[n]);
777
778                    if (fma == 0) {
779                        throw new IllegalActionException(
780                                "couldn't create array for index " + n
781                                        + " in cell array " + name);
782                    }
783
784                    ptmatlabSetCell(name, ma, 0, n, fma);
785                }
786            } else {
787                String s = ((StringToken) ta[0]).stringValue();
788                ma = ptmatlabCreateString(name, s, ta.length, s.length());
789
790                for (int n = 1; n < ta.length; n++) {
791                    s = ((StringToken) ta[n]).stringValue();
792                    ptmatlabSetString(name, ma, n, s, s.length());
793                }
794            }
795        } else if (t instanceof RecordToken) {
796            Object[] fieldNames = ((RecordToken) t).labelSet().toArray();
797            ma = ptmatlabCreateStructMatrix(name, fieldNames, 1, 1);
798
799            if (ma == 0) {
800                throw new IllegalActionException(
801                        "couldn't create struct " + "array " + name);
802            }
803
804            for (Object fieldName : fieldNames) {
805                Token f = ((RecordToken) t).get((String) fieldName);
806                long fma = _createMxArray((String) fieldName, f);
807
808                if (fma == 0) {
809                    throw new IllegalActionException(
810                            "couldn't create array for field " + fieldName
811                                    + " in struct " + name);
812                }
813
814                ptmatlabSetStructField(name, ma, (String) fieldName, 0, 0, fma);
815            }
816        } else if (t instanceof StringToken) {
817            String s = ((StringToken) t).stringValue();
818            ma = ptmatlabCreateString(name, s, 1, s.length());
819        } else if (t instanceof ComplexMatrixToken) {
820            Complex[][] a = ((ComplexMatrixToken) t).complexMatrix();
821            ma = ptmatlabCreateComplexMatrix(name, a, a.length, a[0].length);
822        } else if (t instanceof MatrixToken) {
823            double[][] a = ((MatrixToken) t).doubleMatrix();
824            ma = ptmatlabCreateDoubleMatrix(name, a, a.length, a[0].length);
825        } else if (t instanceof ComplexToken) {
826            Complex[] a = { ((ComplexToken) t).complexValue() };
827            ma = ptmatlabCreateComplexMatrixOneDim(name, a, a.length);
828        } else {
829            double[] a = new double[1];
830
831            if (t instanceof BooleanToken) {
832                a[0] = ((BooleanToken) t).booleanValue() ? 1.0 : 0.0;
833            } else if (t instanceof DoubleToken) {
834                a[0] = ((ScalarToken) t).doubleValue();
835            } else if (t instanceof IntToken) {
836                a[0] = ((ScalarToken) t).intValue();
837            } else {
838                throw new IllegalActionException("Token " + t + " is of type "
839                        + t.getType() + ", it should be one of "
840                        + "ArrayToken, RecordToken, StringToken, ComplexMatrixToken, "
841                        + "MatrixToken, ComplexToken, BooleanToken, DoubleToken or IntToken.");
842            }
843
844            ma = ptmatlabCreateDoubleMatrixOneDim(name, a, 1);
845        }
846
847        if (ma == 0) {
848            throw new IllegalActionException(
849                    "couldn't create array for " + name);
850        }
851
852        return ma;
853    }
854
855    private boolean _doubleIsInteger(double d) {
856        // FindBugs reports "Test for floating point equality", which
857        // may be ignored here because we really want to know if
858        // the double is equal to the floor of the double.
859        return d == Math.floor(d) && d <= Integer.MAX_VALUE
860                && d >= Integer.MIN_VALUE;
861    }
862
863    ///////////////////////////////////////////////////////////////////
864    ////                         private variables                 ////
865    // Debug statements are sent to stdout if non-zero. If 1, only
866    // this class sends debug statements, if 2 then ptmatlab.dll also
867    // sends debug statements.
868    private byte debug = 0;
869}