View Javadoc
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      private Writer writer = new OutputStreamWriter(System.out, 
68  						   OctaveUtils.getUTF8());
69  
70      /**
71       * Describe variable <code>random</code> here.
72       *
73       */
74      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  		 final File workingDir) {
88          this.factory = factory;
89  	// assert environment == null;
90  
91          this.octaveExec = new OctaveExec(numThreadsReuse,
92  					 octaveInputLog,
93  					 errorWriter,
94  					 cmdArray,
95  					 environment,
96  					 workingDir);
97          this.octaveIO = new OctaveIO(this.octaveExec);
98      }
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         if (this.writer == null) {
112             // If writer is null create a "do nothing" functor
113             return new ReadFunctor() {
114                 private final char[] buffer = new char[4096];
115 
116 		@Override
117 		@SuppressWarnings("checkstyle:emptyblock")
118 		public void doReads(final Reader reader) throws IOException {
119                     while (reader.read(buffer) != -1) { // NOPMD
120                         // Do nothing
121                     }
122                 }
123             };
124         } else {
125             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         this.octaveExec.evalRW(new ReaderWriteFunctor(script), 
139 			       getReadFunctor());
140     }
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         this.octaveExec.evalRW(new WriteFunctor() {
153 		@Override
154 		public void doWrites(final Writer writer2) throws IOException {
155 		    writer2.write(script);
156 		}
157 	    },
158 	    getReadFunctor());
159     }
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         final String tag = String.format("%06x%06x",
189 					 this.random.nextInt(1 << 23),
190 					 this.random.nextInt(1 << 23));
191         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         unsafeEval(String.format("eval(javaoctave_%1$s_eval, " +
196 				 "\"javaoctave_%1$s_lasterr = lasterr();\");",
197 				 tag));
198         final OctaveString lastError =
199 	    get(OctaveString.class,
200 		String.format("javaoctave_%1$s_lasterr", tag));
201         unsafeEval(String.format("clear javaoctave_%1$s_eval  " +
202 				 "javaoctave_%1$s_lasterr", tag));
203         if (lastError != null) {
204             throw new OctaveEvalException(lastError.getString());
205         }
206     }
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         this.octaveIO.set(Collections.singletonMap(key, value));
218     }
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         this.octaveIO.set(vars);
230     }
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         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         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         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         this.writer = writer;
275     }
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         this.octaveExec.setErrorWriter(errorWriter);
287     }
288 
289     /**
290      * Close the octave process in an orderly fashion.
291      */
292     public void close() {
293         this.octaveExec.close();
294     }
295 
296     /**
297      * Kill the octave process without remorse.
298      */
299     public void destroy() {
300         this.octaveExec.destroy();
301     }
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         final StringWriter version = new StringWriter();
311 	StringReader reader =
312 	    new StringReader("printf(\"%s\", OCTAVE_VERSION());");
313         this.octaveExec.evalRW(new ReaderWriteFunctor(reader),
314 			       new WriterReadFunctor(version));
315         return version.toString();
316     }
317 }