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 <name></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 }