001package jmri.jmrit.jython; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.io.File; 005import java.io.FileReader; 006import javax.script.ScriptEngine; 007import jmri.script.JmriScriptEngineManager; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * A JynstrumentFactory handles instantiation and connection of 013 * {@link Jynstrument} instances. 014 * 015 * @see Jynstrument 016 * @author Lionel Jeanson Copyright 2009 017 * @since 2.7.8 018 */ 019public class JynstrumentFactory { 020 021 private static final String instanceName = "jynstrumentObjectInstance"; 022 023 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Should crash if missing ScriptEngine dependencies are not present") 024 public static Jynstrument createInstrument(String path, Object context) { 025 String className = validate(path); 026 if (className == null) { 027 // Try containing directory 028 File f = new File(path); 029 String parentPath = f.getParent(); 030 className = validate(parentPath); 031 if (className == null) { 032 log.error("Invalid Jynstrument, neither {} or {} are usable", path, parentPath); 033 return null; 034 } 035 path = parentPath; 036 } 037 String jyFile = path + File.separator + className + ".py"; 038 ScriptEngine engine = JmriScriptEngineManager.getDefault().getEngine(JmriScriptEngineManager.JYTHON); 039 Jynstrument jyns; 040 try { 041 FileReader fr = new FileReader(jyFile); 042 try { 043 engine.eval(fr); 044 engine.eval(instanceName + " = " + className + "()"); 045 jyns = (Jynstrument) engine.get(instanceName); 046 engine.eval("del " + instanceName); 047 } finally { 048 fr.close(); 049 } 050 } catch (java.io.IOException | javax.script.ScriptException ex) { 051 log.error("Exception while creating Jynstrument", ex); 052 return null; 053 } 054 jyns.setClassName(className); 055 jyns.setContext(context); 056 if (!jyns.validateContext()) { // check validity of this Jynstrument for that extended context 057 log.error("Invalid context for Jynstrument, host is {} and {} kind of host is expected", context.getClass(), jyns.getExpectedContextClassName()); 058 return null; 059 } 060 jyns.setJythonFile(jyFile); 061 jyns.setFolder(path); 062 jyns.setPopUpMenu(new JynstrumentPopupMenu(jyns)); 063 try { 064 jyns.init(); // GO! 065 } catch (RuntimeException e) { 066 // catch, log, and continue, we don't want to break the ongoing workflow 067 log.error("Error starting Jynstrument.",e); 068 return null; 069 } 070 return jyns; 071 } 072 073 // validate Jynstrument path, return className 074 private static String validate(String path) { 075 if (path == null) { 076 log.error("Path is null"); 077 return null; 078 } 079 if (path.length() - 4 < 0) { 080 log.error("File name too short (should at least end with .jyn) (got {})", path); 081 return null; 082 } 083 if (path.endsWith(File.separator)) { 084 path = path.substring(0, path.length()-File.separator.length()); 085 } 086 File f = new File(path); 087 088 // Path must be a folder named xyz.jin 089 if (!f.isDirectory()) { 090 log.debug("Not a directory, trying parent"); 091 return null; 092 } 093 if (! path.toLowerCase().endsWith(".jyn")) { 094 log.debug("Not an instrument (folder name not ending with .jyn) (got {})", path); 095 return null; 096 } 097 098 // must contain a xyz.py file and construct class name from filename (xyz actually) xyz class in xyz.py file in xyz.jin folder 099 String[] children = f.list(); 100 String className = null; 101 if (children == null) { 102 log.error("Didn't find any files in {}", f); 103 return className; 104 } 105 106 String assumedClassName = f.getName().substring(0, f.getName().length() - 4); 107 // Try to find best candidate 108 for (String c : children) { 109 if ((c).compareToIgnoreCase(assumedClassName + ".py") == 0) { 110 return assumedClassName; // got exact match for folder name 111 } 112 } 113 // If not, use first python file we can find 114 log.warn("Coulnd't find best candidate ({}), reverting to first one", assumedClassName + ".py"); 115 for (String c : children) { 116 if (c.substring(c.length() - 3).compareToIgnoreCase(".py") == 0) { 117 className = c.substring(0, c.length() - 3); // else take whatever comes 118 } 119 } 120 log.warn("Using {}", className); 121 return className; 122 } 123 124 private final static Logger log = LoggerFactory.getLogger(JynstrumentFactory.class); 125}