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  import java.net.URL;
29  import java.nio.charset.Charset;
30  
31  import java.util.Arrays;
32  import java.util.Collection;
33  import java.util.Collections;
34  import java.util.Map;
35  import java.util.HashMap;
36  import java.util.Set;
37  import java.util.HashSet;
38  import java.util.Random;
39  
40  import java.util.jar.Attributes;
41  import java.util.jar.Manifest;
42  import java.util.regex.Matcher;
43  import java.util.regex.Pattern;
44  
45  import eu.simuline.octave.exception.OctaveEvalException;
46  import eu.simuline.octave.exception.OctaveClassCastException;
47  import eu.simuline.octave.exception.OctaveIOException;
48  import eu.simuline.octave.exec.OctaveExec;
49  import eu.simuline.octave.exec.ReadFunctor;
50  import eu.simuline.octave.exec.ReaderWriteFunctor;
51  import eu.simuline.octave.exec.WriteFunctor;
52  import eu.simuline.octave.exec.WriterReadFunctor;
53  import eu.simuline.octave.io.OctaveIO;
54  import eu.simuline.octave.type.OctaveBoolean;
55  import eu.simuline.octave.type.OctaveCell;
56  import eu.simuline.octave.type.OctaveObject;
57  import eu.simuline.octave.type.OctaveString;
58  import eu.simuline.octave.type.OctaveStruct;
59  import eu.simuline.octave.type.cast.Cast;
60  
61  /**
62   * The connection to an octave process.
63   *
64   * This is inspired by the javax.script.ScriptEngine interface.
65   */
66  public final class OctaveEngine {
67      
68      // TBD: clarify whether these versions are the correct ones. 
69      // In the repo for octave, typing `hg tags` I found the following release tags
70      // release-5-2-0, release-5-1-0,
71      // release-4-4-1, release-4-4-0,
72      // release-4-2-2, release-4-2-1, release-4-2-0,
73      // release-4-0-3, release-4-0-2, release-4-0-1, release-4-0-0,
74      // release-3-8-2, release-3-8-1, release-3-8-0,
75      // release-3-6-4, release-3-6-3, release-3-6-2, release-3-6-1, 
76      // release-3-6-0 and release-3.6.0(!),
77      // release-3-4-3, release-3-4-2, release-3-4-1, release-3-4-0,
78      // release-3-2-4,
79      // release-3-0-0
80      // What about the gaps? 
81      // What is prior to 3-0-0?
82      // What is the difference between 3-0-0 and 3.0.0?
83      // Note that the versions "3.0.5", "3.2.3" in javaoctave 
84      // have no counterpart. 
85  
86  
87      /**
88       * The set of known versions of octave, i.e. those for which javaoctave shall work. 
89       */
90      private final static Set<String> KNOWN_OCTAVE_VERSIONS = new HashSet<String>
91  	    (Arrays.asList("3.0.5", "3.2.3", "3.2.4",
92  		    "3.6.2", "3.8.2",
93  		    // added by E.R.
94  		    "4.3.0+",
95  		    "4.4.0", "5.0.0", "5.2.0", 
96  		    "6.1.0", "6.2.0"));
97  
98  
99      // ER: nowhere used except in method getFactory() 
100     // which is in turn nowhere used. 
101     /**
102     * @deprecated
103     */
104     private final OctaveEngineFactory factory;
105 
106     /**
107      * The executor of this octave engine. 
108      */
109     private final OctaveExec octaveExec;
110 
111     private final OctaveIO octaveIO;
112 
113     // TBD: it seems that the writer is not needed but the functor. 
114     // maybe reuse that and set this field to the functor 
115     // wrapping the writer instead of the writer. 
116     // This is a way to avoid a null-value. 
117     /**
118      * The writer to write output from evaluation of a script.  
119      * Initially this wraps {@link System#out}. 
120      * This may also be <code>null</code> which indicates a 'do nothing functor'
121      */
122     private Writer writer;
123 
124     /**
125      * Describe variable <code>random</code> here.
126      *
127      */
128     private final Random random = new Random();
129 
130 
131     // TBC: in which version does this occur in listVars? 
132     // seemingly not in 5.2.0. 
133     /**
134      * A variable name not to be listed by {@link OctaveUtils#listVars(OctaveEngine)}. 
135      */
136     private static final String ANS    = "ans";
137 
138     // TBC: in which version does this occur? 
139     // seemingly not in 5.2.0. 
140     // Just nargin and that only within functions. 
141     // but in that context also nargout would be needed. 
142     // Note also that it is nargin not __nargin__. 
143     // In particular this variable is not returned by whos. 
144     /**
145      * A variable name not to be listed by {@link OctaveUtils#listVars(OctaveEngine)}. 
146      */
147     private static final String NARGIN = "__nargin__";
148 
149     /**
150      * Creates an octave engine with the given parameters. 
151      * The first one is nowhere used and the others are handed over to 
152      * {@link OctaveExec#OctaveExec(int,Writer,Writer,Charset,String[],String[],File)}. 
153      * 
154      * @param factory
155      *    the factory used to create this engine. 
156      * @param numThreadsReuse
157      *    TBC
158      * @param octaveInputLog
159      *    a writer to log octave's standard output to, if not <code>null</code>. 
160      * @param errorWriter
161      *     a writer to log octave's error output to ,if not <code>null</code>. 
162      * @param charset
163      *    the charset used for communication with the octave process. 
164      * @param cmdArray
165      *    an array with 0th entry the command 
166      *    and the rest (optional) command line parameters. 
167      *    
168      */
169     OctaveEngine(final OctaveEngineFactory factory,
170 		 final int numThreadsReuse,
171 		 final Writer octaveInputLog, // may be null 
172 		 final Writer errorWriter,// may be null 
173 		 final Charset charset,
174 		 final String[] cmdArray,
175 		 final String[] environment, // may be null 
176 		 final File workingDir) {
177         this.factory = factory;
178 	// assert environment == null;
179 
180         this.writer = new OutputStreamWriter(System.out);
181 
182         this.octaveExec = new OctaveExec(numThreadsReuse,
183 					 octaveInputLog, // may be null
184 					 errorWriter,
185 					 charset,
186 					 cmdArray,
187 					 environment,
188 					 workingDir);
189         this.octaveIO = new OctaveIO(this.octaveExec);
190     }
191 
192     /**
193      * Returns the according read functor: 
194      * If {@link #writer} is non-null, 
195      * wrap it into a {@link WriterReadFunctor}. 
196      * Otherwise, create functor from a reader 
197      * which reads empty, i.e. without action, as long as the reader is empty. 
198      * 
199      */
200     @SuppressWarnings({"checkstyle:visibilitymodifier", 
201 	    "checkstyle:magicnumber"})
202     private ReadFunctor getReadFunctor() {
203         if (this.writer == null) {
204             // If writer is null create a "do nothing" functor
205             return new ReadFunctor() {
206                 private final char[] buffer = new char[4096];
207 
208 		@Override
209 		@SuppressWarnings("checkstyle:emptyblock")
210 		public void doReads(final Reader reader) throws IOException {
211                     while (reader.read(buffer) != -1) { // NOPMD
212                         // Do nothing
213                     }
214                 }
215             };
216         } else {
217             return new WriterReadFunctor(this.writer);
218         }
219     }
220 
221     /**
222      * Execute the given script. 
223      *
224      * @param script
225      *            the script to execute
226      * @throws OctaveIOException
227      *             if the script fails, this will kill the engine
228      */
229     public void unsafeEval(final Reader script) {
230         this.octaveExec.evalRW(new ReaderWriteFunctor(script), 
231 			       getReadFunctor());
232     }
233 
234     // ER: see also {@link #eval(final String script)}
235     /**
236      * Execute the given script. 
237      *
238      * @param script
239      *            the script to execute
240      * @throws OctaveIOException
241      *             if the script fails, this will kill the engine
242      */
243     public void unsafeEval(final String script) {
244         this.octaveExec.evalRW(new WriteFunctor() {
245 		@Override
246 		public void doWrites(final Writer writer2) throws IOException {
247 		    writer2.write(script);
248 		}
249 	    },
250 	    getReadFunctor());
251     }
252 
253     // ER: 
254     // based on {@link #unsaveEval(final String script)} 
255     // in contrast to {@link #unsaveEval(final String script)} 
256     // errors are caught. 
257     // Implementation is based on octave Built-in Function eval (try, catch)
258     // both try and catch being strings. 
259     // try is always evaluated and catch is evaluated in case of an error 
260     // while evaluating try. 
261     // The last error ist returned by built/in function lasterr(). 
262     // 
263     // evaluates 'eval(javaoctave_X_eval,"javaoctave_X_lasterr = lasterr();");'
264     // where javaoctave_X_eval is a variable containing script as a string 
265     // and X is some random number 
266     //
267     // That way, in case of an error, 
268     // javaoctave_X_lasterr contains the string representtion of this error. 
269     /**
270      * A safe eval that will not break the engine on syntax errors 
271      * or other errors. 
272      *
273      * @param script
274      *            the script to execute
275      * @throws OctaveEvalException
276      *             if the script fails
277      */
278     @SuppressWarnings("checkstyle:magicnumber")
279     public void eval(final String script) {
280         final String tag = String.format("%06x%06x",
281 					 this.random.nextInt(1 << 23),
282 					 this.random.nextInt(1 << 23));
283         put(String.format("javaoctave_%1$s_eval", tag),
284 	    new OctaveString(script));
285         // Does not use lasterror() as that returns data in a matrix struct,
286 	// we can not read that yet
287         unsafeEval(String.format("eval(javaoctave_%1$s_eval, " +
288 				 "\"javaoctave_%1$s_lasterr = lasterr();\");",
289 				 tag));
290         final OctaveString lastError =
291 	    get(OctaveString.class,
292 		String.format("javaoctave_%1$s_lasterr", tag));
293         unsafeEval(String.format("clear javaoctave_%1$s_eval  " +
294 				 "javaoctave_%1$s_lasterr", tag));
295         if (lastError != null) {
296             throw new OctaveEvalException(lastError.getString());
297         }
298     }
299 
300     /**
301      * Sets a value in octave.
302      *
303      * @param key
304      *     the name of the variable to be set to value <code>value</code>. 
305      * @param value
306      *     the value to set for the variable <code>key</code>
307      */
308     public void put(final String key, final OctaveObject value) {
309         this.octaveIO.set(Collections.singletonMap(key, value));
310     }
311 
312     /**
313      * Sets all the mappings in the specified map as variables in octave. 
314      * These mappings replace any variable 
315      * that octave had for any of the keys currently in the specified map. 
316      *
317      * @param vars
318      *    a map from variable names to according values 
319      *    o be stored in the according variables in octave. 
320      */
321     public void putAll(final Map<String, OctaveObject> vars) {
322         this.octaveIO.set(vars);
323     }
324 
325     /**
326      * @param key
327      *            the name of the variable
328      * @return the value from octave or null if the variable does not exist
329      */
330     public OctaveObject get(final String key) {
331         return this.octaveIO.get(key);
332     }
333 
334     /**
335      * @param castClass
336      *            Class to cast to
337      * @param key
338      *            the name of the variable
339      * @param <T>
340      *            the class of the return value
341      * @return shallow copy of value for this key, or null if key isn't there.
342      * @throws OctaveClassCastException
343      *             if the object can not be cast to a castClass
344      */
345     public <T extends OctaveObject> T get(final Class<T> castClass,
346 					  final String key) {
347         return Cast.cast(castClass, get(key));
348     }
349 
350     // ER: nowhere used
351     /**
352      * @return the factory that created this object
353      * @deprecated
354      */
355     public OctaveEngineFactory getFactory() {
356         return this.factory;
357     }
358 
359     /**
360      * Set the writer that the scripts output will be written to.
361      *
362      * This method is usually placed in ScriptContext.
363      * It is used also for tests. 
364      *
365      * @param writer
366      *    the writer to set
367      *    This may be null which means that no writer is used. 
368      */
369     public void setWriter(final Writer writer) {
370         this.writer = writer;
371     }
372 
373     /**
374      * Set the writer that the scripts error output will be written to.
375      *
376      * This method is usually placed in ScriptContext.
377      *
378      * @param errorWriter
379      *    the errorWriter to set
380      */
381     public void setErrorWriter(final Writer errorWriter) {
382         this.octaveExec.setErrorWriter(errorWriter);
383     }
384 
385     /**
386      * Close the octave process in an orderly fashion.
387      */
388     public void close() {
389         this.octaveExec.close();
390     }
391 
392     /**
393      * Kill the octave process without remorse.
394      */
395     public void destroy() {
396         this.octaveExec.destroy();
397     }
398     
399     
400     /**
401      * Return the version of the octave implementation. 
402      * E.g. a string like "3.0.5" or "3.2.3".
403      * @return
404      *    the version of the underlying octave program as a string. 
405      * @deprecated
406      *    use {@link #getOctaveVersion()} instead.     
407      */
408     public String getVersion() {
409 	return getOctaveVersion();
410     }
411     
412     // TBD: synchronize with according class in maven-latex-plugin 
413     // and extract into separate git repository. 
414     static class ManifestInfo {
415 	private final static String META_FOLDER = "META-INF/";
416 	   
417 
418 	private final static String MANIFEST_FILE = "MANIFEST.MF";
419 	
420 	// TBD: decide whether this is sensible 
421 	private final Manifest manifest;
422 	
423 	/**
424 	 * The main attributes of the manifest. 
425 	 */
426 	private final Attributes mAtts;
427 	
428 
429 	ManifestInfo() throws IllegalStateException {
430 	    try {
431 		this.manifest = new Manifest
432 			(this.getClass().getClassLoader()
433 				.getResource(META_FOLDER + MANIFEST_FILE)
434 				.openStream());
435 	    } catch (IOException e) {
436 		throw new IllegalStateException("could not read properties" + e);
437 	    }
438 	    this.mAtts = this.manifest.getMainAttributes();
439 
440 	}
441 	
442 	private String getAttrValue(Object name) {
443 	    // is in fact a string always but this is to detect null pointer exceptions 
444 	    return (String)this.mAtts.get(name);//.toString();
445 	}
446 
447 	/**
448 	 * Returns the version of the implementation. 
449 	 * This is the version given by the maven coordinates.
450 	 * 
451 	 *  @return
452 	 */
453 	String getImplVersion() {
454 	    return getAttrValue(Attributes.Name.IMPLEMENTATION_VERSION);
455 	}
456 	
457 	String getImplVendor() {
458 	    return getAttrValue(Attributes.Name.IMPLEMENTATION_VENDOR);
459 	}
460 
461     } // class ManifestInfo
462     
463     private final static ManifestInfo MANIFEST_INFO = new ManifestInfo();
464 
465     // TBD: workaround.
466     // This does not work only in context of junit tests (classloader!)
467     // It does work if run standalone. 
468     /**
469      * Returns the vendor of this octave bridge as a string. 
470      * @return
471      *    The vendor of this octave bridge as a string. 
472      */
473     public String getVendor() {
474 	System.out.println("MANIFEST_INFO: "+MANIFEST_INFO);
475 	return MANIFEST_INFO.getImplVendor();
476 	//return this.getClass().getPackage().getImplementationVendor();
477     }
478 
479     // TBD: workaround.
480     // This does not work only in context of junit tests (classloader!)
481     // It does work if run standalone. 
482     /**
483      * Returns the version of this octave bridge as a string. 
484      * @return
485      *    The version of this octave bridge as a string. 
486      * @see #getOctaveVersion()
487      */
488     public String getOctaveInJavaVersion() {
489 	//System.out.println("MANIFEST_INFO: "+MANIFEST_INFO);
490 	//return MANIFEST_INFO.getImplVersion();
491 	return this.getClass().getPackage().getImplementationVersion();
492     }
493 
494     /**
495      * Return the version of the octave implementation invoked by this bridge. 
496      * E.g. a string like "3.0.5" or "3.2.3".
497      *
498      * @return 
499      *    The version of octave as a string. 
500      * @see #getOctaveInJavaVersion()
501      */
502     public String getOctaveVersion() {
503 	eval("OCTAVE_VERSION();");
504 	return get(OctaveString.class, ANS).getString();
505     }
506 
507     /**
508      * Returns whether the version of the current octave installation 
509      * given by {@link #getOctaveVersion()}
510      * is supported by this octavejava bridge. 
511      *
512      * @return
513      *    whether the version of the current octave installation 
514      *    is supported by this octavejava bridge. 
515      * @see #KNOWN_OCTAVE_VERSIONS
516      */
517     public boolean isOctaveVersionAllowed() {
518 	return KNOWN_OCTAVE_VERSIONS.contains(getOctaveVersion());
519     }
520 
521     /**
522      * Returns the file separator of this os given by the expression <code>filesep()</code>.
523      * 
524      * @return
525      *    the file-separator for this os.
526      */
527     public String getFilesep() {
528 	eval("filesep();");
529 	return get(OctaveString.class, ANS).getString();
530     }
531 
532     /**
533      * Returns value in variable {@link #ANS} 
534      * which is expected to be a cell array of strings, 
535      * as a collection of strings. 
536      * 
537      * @return
538      *    {@link #ANS} as a collection of strings. 
539      */
540     private Collection<String> getStringCellFromAns() {
541 	OctaveCell cell = get(OctaveCell.class, ANS);
542 	// it is known that cell contains strings only. 
543 	int len = cell.dataSize();
544 	Collection<String> collection = new HashSet<String>();
545 	for (int idx = 1; idx <= len; idx++) {
546 	    collection.add(cell.get(OctaveString.class, 1, idx).getString());
547 	}
548 	return collection;
549     }
550 
551     // This is a little strange: pkg('list') returns a cell array 
552     // but the entries have uniform type. 
553     // TBD: we need more than the mere names. 
554     // TBC: maybe this shall be reimplemented in terms of getPackagesInstalled()
555     /**
556      * Returns a collection of names of installed packages. 
557      * 
558      * @return
559      *    a collection of names of installed packages. 
560      * @see #getPackagesInstalled()
561      */
562     public Collection<String> getNamesOfPackagesInstalled() {
563 	eval("cellfun(@(x) x.name, pkg('list'), 'UniformOutput', false);");
564 	return getStringCellFromAns();
565     }
566 
567 //    // TBC: dependency only with name okg possible. 
568 //    public static class Dependency {
569 //	public final String pkg;
570 //	public final String operator;// TBC: maybe enum. 
571 //	public final String version;
572 //	
573 //    } // class Dependency
574 
575     // TBD: complete 
576     // interesting is package struct
577     // in particular, depends and autoload
578     /**
579      * Representation of a package as returned by the octave command
580      * <code>pkg('list')</code> which returns a cell array
581      * of structs with fields reflected by the fields of this class. 
582      * Thus the constructor has a struct as parameter 
583      * and is the only place to initialize the fields 
584      * which are all public and final.
585      * <p>
586      * Most of the fields are defined by the DESCRIPTION file in the package 
587      * as described in the manual 5.2.0, Section 37.4.1.
588      * There are mandatory fields, 
589      * optional fields and there may be fields in the struct
590      * which are not documented. 
591      * Currently, these are not reflected in this class.
592      */
593     public static class PackageDesc {
594 	/**
595 	 * The name of the package in lower case, 
596 	 * no matter how it is written in the DESCRIPTION FILE. 
597 	 */
598 	public final String name;
599 
600 	/**
601 	 * A version string which typically consists of numbers separated by dots 
602 	 * but may also contain +, - and ~. 
603 	 * Documentation of function <code>compare_versions</code> 
604 	 * shows that the form is more restricted. 
605 	 * TBD: bugreport that versions shall be comparable. 
606 	 */
607 	public final String version;// TBC: add comparator 
608 
609 	/**
610 	 * The date in iso form yyyy-mm-dd.
611 	 * TBD: add this to manual: make comparable.
612 	 * Also: seems reasonable, to allow time also. 
613 	 * TBD: entry in manual
614 	 */
615 	public final String date;
616 
617 	// name and email in form 'Alexander Barth <barth.alexander@gmail.com>'
618 	/**
619 	 * The name of the original author, 
620 	 * convention (TBC) name <email>, 
621 	 * if more than one, separated by comma. 
622 	 */
623 	public final String author;
624 //	public final String maintainer;// may be list 
625 //	public final String title;
626 //	public final String description;
627 //	public final String categories;// TBD: optional 
628 //	public final String problems;// TBD: optional 
629 //	// shall be a list of url's
630 //	public final Set<URL> url;// sometimes url2, optional
631 //	// TBC: may also take octave into account 
632 //	// add also in arithintoctave
633 //	public final Set<Dependency> depends;
634 //	public final String license;// maybe comma separated, is optional
635 //	public final String systemRequirements;
636 //	public final String buildrequires;// TBC: similar:suggested, package io
637 
638 	//These are the additional fields nowhere added 
639 	// they come from according entries in the DESCRPTION file 
640 	//public final Map<String, String> name2addArg;
641 
642 	// from here on, these are states of the package 
643 	// which are not constant accross lifetime of a package 
644 	// and have thus nothing to do with the DESCRIPTION FILE 
645 	// TBD: these shall be documented in the manual. 
646 //	public final String autoload;// TBC: maybe enum 
647 	public final File dir;
648 	public final File archprefix;
649 
650 	/**
651 	 * Whether the package is loaded. 
652 	 * This has nothing to do with the DESCRIPTION file. 
653 	 */
654 	public final boolean isLoaded;
655 
656 
657 	/**
658 	 * Creates a new package description from the given struct. 
659 	 * @param pkg
660 	 *    a struct representing a package 
661 	 *    which has a predefined set of mandatory fields, 
662 	 *    a predefined set of optional fields 
663 	 *    and which may have also additional fields 
664 	 *    going beyond what is documented. 
665 	 */
666 	PackageDesc(OctaveStruct pkg) {
667 	    this.name       = pkg.get(OctaveString .class, "name"   ).getString();
668 	    this.version    = pkg.get(OctaveString .class, "version").getString();
669 	    this.date       = pkg.get(OctaveString .class, "date"   ).getString();
670 	    this.author     = pkg.get(OctaveString .class, "author" ).getString();
671 	    this.dir        = new File(pkg.get(OctaveString.class, "dir"       ).getString());
672 	    this.archprefix = new File(pkg.get(OctaveString.class, "archprefix").getString());
673 	    this.isLoaded   = pkg.get(OctaveBoolean.class, "loaded" ).get(1, 1);
674 	}
675 
676 	@Override public String toString() {
677 	    StringBuilder res = new StringBuilder();
678 	    res.append("<package>\n");
679 	    res.append("name=      "+this.name+"\n");
680 	    res.append("version=   "+this.version+"\n");
681 	    res.append("date=      "+this.date+"\n");
682 	    res.append("author=    "+this.author+"\n");
683 	    res.append("dir=       "+this.dir+"\n");
684 	    res.append("archprefix="+this.archprefix+"\n");
685 	    res.append("isLoaded=  "+this.isLoaded+"\n");
686 	    res.append("</package>\n");
687 	    return res.toString();
688 	}
689     } // class PackageDesc 
690 
691     /**
692      * Returns a map mapping the names of the installed packages 
693      * to the description of the according package. 
694      * 
695      * @return
696      *    a map from the names to the description of the packages installed. 
697      * @see #getNamesOfPackagesInstalled()
698      */
699     public Map<String, PackageDesc> getPackagesInstalled() {
700 	eval(ANS + "=pkg('list');");
701 	// TBC: why ans=necessary??? without like pkg list .. bug? 
702 	OctaveCell cell = get(OctaveCell.class, ANS);
703 	int len = cell.dataSize();
704 	Map<String, PackageDesc> res = new HashMap<String, PackageDesc>();
705 	PackageDesc pkg;
706 	for (int idx = 1; idx <= len; idx++) {
707 	    pkg = new PackageDesc(cell.get(OctaveStruct.class, 1, idx));
708 	    res.put(pkg.name, pkg);
709 	}
710 	return res;
711     }
712 
713     /**
714      * Returns a collection of variables defined 
715      * excluding variables like {@link #NARGIN} and {@link#ANS} 
716      * but also those that are most likely to be created by this software. TBD: clarification 
717      * 
718      * @return collection of variables
719      */
720     public Collection<String> getVarNames() {
721 	// Justification: 3.0 are the earliest versions. 
722 	// all later ones don't use '-v' any more 
723 	String script = getOctaveVersion().startsWith("3.0.") ? "ans=whos -v()" : "ans=whos()";
724 	eval(script);
725 	// TBD: clarify: if we use who instead of whos, this can be simplified.  
726 	eval("{ans.name}");
727 	Collection<String> collection = getStringCellFromAns();
728 	collection.removeIf(p -> NARGIN.equals(p));
729 	collection.removeIf(p -> ANS.equals(p));
730 	// TBD: eliminate magic literal 
731 	Pattern pattern = Pattern.compile("javaoctave_[0-9a-f]{12}_eval");
732 	collection.removeIf(p -> pattern.matcher(p).matches());
733 	return collection;
734     }
735 
736     /**
737      * Describes the meaning of a name, if any. That name is replicated in {@link #name}. 
738      * The category is given in {@link #category}. 
739      * If it is {@link Category#Unknown}, both {@link #type} and {@link #file} are <code>null</code>.
740      * From now on assume it is not unknown.
741      * <p>
742      * If {@link #name} is a {@link Category#Variable}, 
743      * of course no file is attached and also no type. 
744      * TBD: change that. 
745      * The type can be found out using <code>typeinfo(name)</code>. 
746      * From now on assume {@link #name} is not a variable. 
747      * <p>
748      * If {@link #name} points to an existing file {@link #file}, the category is {@link Category#FileEx}, 
749      * no matter whether a directory or a proper file and {@link #type} is <code>null</code>. 
750      * <p>
751      * If the name points to an object with a type 
752      * defined by a file (seemingly function types only), 
753      * then the category is {@link Category#TypedDefInFile}. 
754      * Then of course, {@link #type} determines the type and {@link #file} the file, 
755      * both not <code>null</code>. 
756      * CAUTION: {@link #file} may not exist (for built-in functions). 
757      * <p>
758      * There is a last case {@link #name} has a type {@link #type} but is not defined by a file. 
759      * TBD: clarify when this occurs. 
760      * In that case the category is {@link Category#TypedWithoutFile}. 
761      */
762     public static class NameDesc {
763 
764 	public enum Category {
765 	    Variable,
766 	    FileEx {
767 		boolean hasFile() {
768 		    return true;
769 		}
770 	    },
771 	    TypedWithoutFile {
772 		boolean isTyped() {
773 		    return true;
774 		}
775 	    },
776 	    TypedDefInFile {
777 		boolean isTyped() {
778 		    return true;
779 		}
780 		boolean hasFile() {
781 		    return true;
782 		}
783 	    },
784 	    Unknown;
785 	    boolean isTyped() {
786 		return false;
787 	    }
788 	    boolean hasFile() {
789 		return false;
790 	    }
791 	} // enum Category
792 
793 	/**
794 	 * The name which is described by this {@link NameDesc}.
795 	 */
796 	public final String name;
797 
798 	/**
799 	 * The category of the {@link #name} described by this object. 
800 	 */
801 	public final Category category;
802 
803 
804 	// TBD: avoid null
805 	/**
806 	 * The type of the object tied to {@link #name}.
807 	 * This may be null, depending on {@link #category}.
808 	 * Seemingly, "function" represents the type "user-defined function". 
809 	 * Seemingly, else this is a type as returned by 'typeinfo'.
810 	 */
811 	public final String type;
812 
813 	// TBD: clarify
814 	// TBD: avoid null
815 	/**
816 	 * The file of the object tied to {@link #name}.
817 	 * This may be null, depending on {@link #category}.
818 	 * If this is a function defined by an m-file, this is the location of that m-file. 
819 	 * Then the type is 'function', meaning 'user defined function'.
820 	 * For built-in functions this seems to be something rooted in "libinterp" 
821 	 * but without leading file separator if returned by which. 
822 	 * Thus for built-in functions, this 'file' does not exist. 
823 	 * Nevertheless, these functions seem to be all defined in liboctinterp.so (linux). 
824 	 */
825 	public final File file;
826 
827 	NameDesc(Matcher matcher, String name) {
828 	    boolean found = matcher.find();
829 	    if (!found) {
830 		throw new IllegalStateException("Output of command 'which' does not fit expectation.");
831 	    }
832 	    this.name = name;
833 	    if (matcher.group("name") == null) {
834 		assert     matcher.group("type")  == null
835 			&& matcher.group("var")   == null
836 			&& matcher.group("sfile") == null
837 		        && matcher.group("tfile") == null;
838 		this.category = Category.Unknown;
839 		this.type = null;
840 		this.file = null;
841 		return;
842 	    }
843 	    assert matcher.group("name").equals(name);
844 
845 	    if (matcher.group("var") != null) {
846 		assert     matcher.group("type")  == null
847 			&& matcher.group("sfile") == null
848 		        && matcher.group("tfile") == null;
849 		this.category = Category.Variable;
850 		this.type = null;
851 		this.file = null;
852 		return;
853 	    }
854 
855 	    this.type = matcher.group("type");
856 	    if (this.type != null) {
857 		assert matcher.group("sfile") == null;
858 		this.file = new File(matcher.group("tfile"));
859 		this.category = this.file != null
860 			? Category.TypedDefInFile : Category.TypedWithoutFile;
861 		return;
862 	    }
863 
864    	    assert matcher.group("sfile") != null;
865    	    this.category = Category.FileEx;
866    	    this.file = new File(matcher.group("sfile"));
867    	    assert this.file.exists();
868 	}
869 
870 	@Override
871 	public String toString() {
872 	    return "NameDesc [name=" + name + ", category=" + category +
873 		    ", type=" + type + ", file=" + file + "]";
874 	}
875     } // class NameDesc 
876 
877 
878     /**
879      * The pattern for the answer to the command <code>which &lt;name&gt;</code>
880      * which is implemented by {@link #getDescForName(String)}. 
881      */
882     private final static Pattern PATTERN_NAME_TYPE_FILE =
883 	    Pattern.compile(String.format("(^'(?<name>.+)' is " +
884                             // presupposes that type is not 'variable' and contains no file separator
885                             "(a ((?<var>variable)|(?<type>[^%s]+)( from the file (?<tfile>.+))?)" +
886 		            "|the (file|directory) (?<sfile>.+))$)|^$", File.separator));
887 
888     /**
889      * Returns the description tied to the name <code>name</code>
890      * in a way, the command 'which' does in octave.
891      * 
892      * @param name
893      *    the name which may, e.g. be a variable or a file or a function or even nothing known.  
894      * @return
895      *    The description for the given name <code>name</code>,
896      *    indicating its category in {@link NameDesc#category}
897      *    and providing many interesting pieces of information.
898      */
899     public NameDesc getDescForName(String name) {
900 	StringReader checkCmd = new StringReader(String.format("which %s", name));
901         final StringWriter fileResultWr = new StringWriter();
902         this.octaveExec.evalRW(new ReaderWriteFunctor(checkCmd),
903         	new WriterReadFunctor(fileResultWr));
904         return new NameDesc(PATTERN_NAME_TYPE_FILE.matcher(fileResultWr.toString()), name);
905     }
906 
907     /**
908      * A command in the package 'java'. 
909      * This is used to determine both the installation home directory and the java home directory, 
910      * which is the location of the m-file for {@link #JAVA_FUN}.
911      */
912     private final static String JAVA_FUN = "javaaddpath";
913 
914     /**
915      * A pattern of the m-file for command {@link #JAVA_FUN} in package java, 
916      * defining the installation home directory as returned by {@link #getInstHomeDir()} 
917      * as its group with number two and the java home directory as its group number one. 
918      * The pattern is made independent of the octave version and of the specific command. 
919      */
920     private final static String PATTERN_HOMEDIR = String
921 	    .format("(?<javaHome>(?<octHome>/.+)/share/octave/(?<octVrs>[^/]+)/m/java/)%s.m", JAVA_FUN)
922 	    .replace("/", File.separator);
923 
924     private File getHomeDir(String nameGrp) {
925 	Matcher matcher = Pattern.compile(PATTERN_HOMEDIR)
926 		.matcher(getDescForName(JAVA_FUN).file.toString());
927 	boolean found = matcher.find();
928 	assert found;
929 	assert matcher.group("octVrs").equals(getOctaveVersion());
930 	return new File(matcher.group(nameGrp));
931     }
932 
933     // TBD: suggestion: replace OCTAVE_HOME by octaveHome() 
934     /**
935      * Returns the installation home directory, 
936      * in the manual sometimes called octave-home. 
937      * CAUTION: Initially, this is OCTAVE_HOME, but the latter can be overwritten. 
938      * 
939      * @return
940      *    octave's installation home directory. 
941      */
942     public File getInstHomeDir() {
943  	return this.getHomeDir("octHome");
944      }
945 
946     // TBD: suggestion: replace OCTAVE_JAVA_DIR by octaveJavaDir() 
947     /**
948      * Returns the java home directory, which contains the m files of the java interface
949      * like {@link #JAVA_FUN} but is also a search directory for files 
950      * <code>javaclasspath.txt</code> (deprecated <code>classpath.txt</code>) 
951      * but also the configuration file <code>java.opt</code>. 
952      * CAUTION: Initially, this is OCTAVE_JAVA_DIR, but the latter can be overwritten. 
953      * 
954      * @return
955      *    octave's java home directory. 
956      */
957     public File getJavaHomeDir() {
958  	return this.getHomeDir("javaHome");
959      }
960 
961 }