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 }