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 }