Coverage Report - eu.simuline.octave.OctaveEngine
 
Classes in this File Line Coverage Branch Coverage Complexity
OctaveEngine
97%
47/48
100%
4/4
1.294
OctaveEngine$1
100%
4/4
100%
2/2
1.294
OctaveEngine$2
100%
3/3
N/A
1.294
 
 1  
 /*
 2  
  * Copyright 2008, 2010 Ange Optimization ApS
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 /**
 17  
  * @author Kim Hansen
 18  
  */
 19  
 package eu.simuline.octave;
 20  
 
 21  
 import java.io.File;
 22  
 import java.io.IOException;
 23  
 import java.io.OutputStreamWriter;
 24  
 import java.io.Reader;
 25  
 import java.io.StringReader;
 26  
 import java.io.StringWriter;
 27  
 import java.io.Writer;
 28  
 
 29  
 import java.util.Collections;
 30  
 import java.util.Map;
 31  
 import java.util.Random;
 32  
 
 33  
 import eu.simuline.octave.exception.OctaveEvalException;
 34  
 import eu.simuline.octave.exception.OctaveClassCastException;
 35  
 import eu.simuline.octave.exception.OctaveIOException;
 36  
 import eu.simuline.octave.exec.OctaveExec;
 37  
 import eu.simuline.octave.exec.ReadFunctor;
 38  
 import eu.simuline.octave.exec.ReaderWriteFunctor;
 39  
 import eu.simuline.octave.exec.WriteFunctor;
 40  
 import eu.simuline.octave.exec.WriterReadFunctor;
 41  
 import eu.simuline.octave.io.OctaveIO;
 42  
 import eu.simuline.octave.type.OctaveObject;
 43  
 import eu.simuline.octave.type.OctaveString;
 44  
 import eu.simuline.octave.type.cast.Cast;
 45  
 
 46  
 /**
 47  
  * The connection to an octave process.
 48  
  *
 49  
  * This is inspired by the javax.script.ScriptEngine interface.
 50  
  */
 51  
 public final class OctaveEngine {
 52  
 
 53  
     // ER: nowhere used except in method getFactory() 
 54  
     // which is in turn nowhere used. 
 55  
     private final OctaveEngineFactory factory;
 56  
 
 57  
     /**
 58  
      * The executor of this octave engine. 
 59  
      */
 60  
     private final OctaveExec octaveExec;
 61  
 
 62  
     private final OctaveIO octaveIO;
 63  
 
 64  
     /**
 65  
      * The writer to write to stdout. 
 66  
      */
 67  106
     private Writer writer = new OutputStreamWriter(System.out, 
 68  106
                                                    OctaveUtils.getUTF8());
 69  
 
 70  
     /**
 71  
      * Describe variable <code>random</code> here.
 72  
      *
 73  
      */
 74  106
     private final Random random = new Random();
 75  
 
 76  
     /**
 77  
      * Creates an octave engine with the given parameters. 
 78  
      * The first one is nowhere used and the others are handed over to 
 79  
      * {@link OctaveExec#OctaveExec(int,Writer,Writer,String[],String[],File)}. 
 80  
      */
 81  
     OctaveEngine(final OctaveEngineFactory factory,
 82  
                  final int numThreadsReuse,
 83  
                  final Writer octaveInputLog,
 84  
                  final Writer errorWriter,
 85  
                  final String[] cmdArray,
 86  
                  final String[] environment, // always invoked with null 
 87  106
                  final File workingDir) {
 88  106
         this.factory = factory;
 89  
         // assert environment == null;
 90  
 
 91  106
         this.octaveExec = new OctaveExec(numThreadsReuse,
 92  
                                          octaveInputLog,
 93  
                                          errorWriter,
 94  
                                          cmdArray,
 95  
                                          environment,
 96  
                                          workingDir);
 97  106
         this.octaveIO = new OctaveIO(this.octaveExec);
 98  106
     }
 99  
 
 100  
     /**
 101  
      * Returns the according read functor: 
 102  
      * If {@link #writer} is non-null, 
 103  
      * wrap it into a {@link WriterReadFunctor}. 
 104  
      * Otherwise, create functor from a reader 
 105  
      * which reads empty, i.e. without action, as long as the reader is empty. 
 106  
      * 
 107  
      */
 108  
     @SuppressWarnings({"checkstyle:visibilitymodifier", 
 109  
             "checkstyle:magicnumber"})
 110  
     private ReadFunctor getReadFunctor() {
 111  850
         if (this.writer == null) {
 112  
             // If writer is null create a "do nothing" functor
 113  4
             return new ReadFunctor() {
 114  4
                 private final char[] buffer = new char[4096];
 115  
 
 116  
                 @Override
 117  
                 @SuppressWarnings("checkstyle:emptyblock")
 118  
                 public void doReads(final Reader reader) throws IOException {
 119  10
                     while (reader.read(buffer) != -1) { // NOPMD
 120  
                         // Do nothing
 121  
                     }
 122  4
                 }
 123  
             };
 124  
         } else {
 125  846
             return new WriterReadFunctor(this.writer);
 126  
         }
 127  
     }
 128  
 
 129  
     /**
 130  
      * Execute the given script. 
 131  
      *
 132  
      * @param script
 133  
      *            the script to execute
 134  
      * @throws OctaveIOException
 135  
      *             if the script fails, this will kill the engine
 136  
      */
 137  
     public void unsafeEval(final Reader script) {
 138  4
         this.octaveExec.evalRW(new ReaderWriteFunctor(script), 
 139  2
                                getReadFunctor());
 140  2
     }
 141  
 
 142  
     // ER: see also {@link #eval(final String script)}
 143  
     /**
 144  
      * Execute the given script. 
 145  
      *
 146  
      * @param script
 147  
      *            the script to execute
 148  
      * @throws OctaveIOException
 149  
      *             if the script fails, this will kill the engine
 150  
      */
 151  
     public void unsafeEval(final String script) {
 152  1696
         this.octaveExec.evalRW(new WriteFunctor() {
 153  
                 @Override
 154  
                 public void doWrites(final Writer writer2) throws IOException {
 155  848
                     writer2.write(script);
 156  848
                 }
 157  
             },
 158  848
             getReadFunctor());
 159  840
     }
 160  
 
 161  
     // ER: 
 162  
     // based on {@link #unsaveEval(final String script)} 
 163  
     // in contrast to {@link #unsaveEval(final String script)} 
 164  
     // errors are caught. 
 165  
     // Implementation is based on octave Built-in Function eval (try, catch)
 166  
     // both try and catch being strings. 
 167  
     // try is always evaluated and catch is evaluated in case of an error 
 168  
     // while evaluating try. 
 169  
     // The last error ist returned by built/in function lasterr(). 
 170  
     // 
 171  
     // evaluates 'eval(javaoctave_X_eval,"javaoctave_X_lasterr = lasterr();");'
 172  
     // where javaoctave_X_eval is a variable containing script as a string 
 173  
     // and X is some random number 
 174  
     //
 175  
     // That way, in case of an error, 
 176  
     // javaoctave_X_lasterr contains the string representtion of this error. 
 177  
     /**
 178  
      * A safe eval that will not break the engine on syntax errors 
 179  
      * or other errors. 
 180  
      *
 181  
      * @param script
 182  
      *            the script to execute
 183  
      * @throws OctaveEvalException
 184  
      *             if the script fails
 185  
      */
 186  
     @SuppressWarnings("checkstyle:magicnumber")
 187  
     public void eval(final String script) {
 188  844
         final String tag = String.format("%06x%06x",
 189  422
                                          this.random.nextInt(1 << 23),
 190  422
                                          this.random.nextInt(1 << 23));
 191  422
         put(String.format("javaoctave_%1$s_eval", tag),
 192  
             new OctaveString(script));
 193  
         // Does not use lasterror() as that returns data in a matrix struct,
 194  
         // we can not read that yet
 195  422
         unsafeEval(String.format("eval(javaoctave_%1$s_eval, " +
 196  
                                  "\"javaoctave_%1$s_lasterr = lasterr();\");",
 197  
                                  tag));
 198  420
         final OctaveString lastError =
 199  420
             get(OctaveString.class,
 200  420
                 String.format("javaoctave_%1$s_lasterr", tag));
 201  420
         unsafeEval(String.format("clear javaoctave_%1$s_eval  " +
 202  
                                  "javaoctave_%1$s_lasterr", tag));
 203  420
         if (lastError != null) {
 204  2
             throw new OctaveEvalException(lastError.getString());
 205  
         }
 206  418
     }
 207  
 
 208  
     /**
 209  
      * Sets a value in octave.
 210  
      *
 211  
      * @param key
 212  
      *            the name of the variable
 213  
      * @param value
 214  
      *            the value to set
 215  
      */
 216  
     public void put(final String key, final OctaveObject value) {
 217  500
         this.octaveIO.set(Collections.singletonMap(key, value));
 218  496
     }
 219  
 
 220  
     /**
 221  
      * Sets all the mappings in the specified map as variables in octave. 
 222  
      * These mappings replace any variable 
 223  
      * that octave had for any of the keys currently in the specified map. 
 224  
      *
 225  
      * @param vars
 226  
      *            the variables to be stored in octave
 227  
      */
 228  
     public void putAll(final Map<String, OctaveObject> vars) {
 229  4
         this.octaveIO.set(vars);
 230  4
     }
 231  
 
 232  
     /**
 233  
      * @param key
 234  
      *            the name of the variable
 235  
      * @return the value from octave or null if the variable does not exist
 236  
      */
 237  
     public OctaveObject get(final String key) {
 238  600
         return this.octaveIO.get(key);
 239  
     }
 240  
 
 241  
     /**
 242  
      * @param castClass
 243  
      *            Class to cast to
 244  
      * @param key
 245  
      *            the name of the variable
 246  
      * @param <T>
 247  
      *            the class of the return value
 248  
      * @return shallow copy of value for this key, or null if key isn't there.
 249  
      * @throws OctaveClassCastException
 250  
      *             if the object can not be cast to a castClass
 251  
      */
 252  
     public <T extends OctaveObject> T get(final Class<T> castClass,
 253  
                                           final String key) {
 254  562
         return Cast.cast(castClass, get(key));
 255  
     }
 256  
 
 257  
     // ER: nowhere used
 258  
     /**
 259  
      * @return the factory that created this object
 260  
      */
 261  
     public OctaveEngineFactory getFactory() {
 262  0
         return this.factory;
 263  
     }
 264  
 
 265  
     /**
 266  
      * Set the writer that the scripts output will be written to.
 267  
      *
 268  
      * This method is usually placed in ScriptContext.
 269  
      *
 270  
      * @param writer
 271  
      *            the writer to set
 272  
      */
 273  
     public void setWriter(final Writer writer) {
 274  18
         this.writer = writer;
 275  18
     }
 276  
 
 277  
     /**
 278  
      * Set the writer that the scripts error output will be written to.
 279  
      *
 280  
      * This method is usually placed in ScriptContext.
 281  
      *
 282  
      * @param errorWriter
 283  
      *            the errorWriter to set
 284  
      */
 285  
     public void setErrorWriter(final Writer errorWriter) {
 286  2
         this.octaveExec.setErrorWriter(errorWriter);
 287  2
     }
 288  
 
 289  
     /**
 290  
      * Close the octave process in an orderly fashion.
 291  
      */
 292  
     public void close() {
 293  108
         this.octaveExec.close();
 294  94
     }
 295  
 
 296  
     /**
 297  
      * Kill the octave process without remorse.
 298  
      */
 299  
     public void destroy() {
 300  12
         this.octaveExec.destroy();
 301  12
     }
 302  
 
 303  
     /**
 304  
      * Return the version of the octave implementation. 
 305  
      * E.g. a string like "3.0.5" or "3.2.3".
 306  
      *
 307  
      * @return Version of octave
 308  
      */
 309  
     public String getVersion() {
 310  8
         final StringWriter version = new StringWriter();
 311  8
         StringReader reader =
 312  
             new StringReader("printf(\"%s\", OCTAVE_VERSION());");
 313  8
         this.octaveExec.evalRW(new ReaderWriteFunctor(reader),
 314  
                                new WriterReadFunctor(version));
 315  8
         return version.toString();
 316  
     }
 317  
 }