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 jyns.init(); // GO! 064 return jyns; 065 } 066 067 // validate Jynstrument path, return className 068 private static String validate(String path) { 069 if (path == null) { 070 log.error("Path is null"); 071 return null; 072 } 073 if (path.length() - 4 < 0) { 074 log.error("File name too short (should at least end with .jyn) (got {})", path); 075 return null; 076 } 077 if (path.endsWith(File.separator)) { 078 path = path.substring(0, path.length()-File.separator.length()); 079 } 080 File f = new File(path); 081 082 // Path must be a folder named xyz.jin 083 if (!f.isDirectory()) { 084 log.debug("Not a directory, trying parent"); 085 return null; 086 } 087 if (! path.toLowerCase().endsWith(".jyn")) { 088 log.debug("Not an instrument (folder name not ending with .jyn) (got {})", path); 089 return null; 090 } 091 092 // must contain a xyz.py file and construct class name from filename (xyz actually) xyz class in xyz.py file in xyz.jin folder 093 String[] children = f.list(); 094 String className = null; 095 if (children == null) { 096 log.error("Didn't find any files in {}", f); 097 return className; 098 } 099 100 String assumedClassName = f.getName().substring(0, f.getName().length() - 4); 101 // Try to find best candidate 102 for (String c : children) { 103 if ((c).compareToIgnoreCase(assumedClassName + ".py") == 0) { 104 return assumedClassName; // got exact match for folder name 105 } 106 } 107 // If not, use first python file we can find 108 log.warn("Coulnd't find best candidate ({}), reverting to first one", assumedClassName + ".py"); 109 for (String c : children) { 110 if (c.substring(c.length() - 3).compareToIgnoreCase(".py") == 0) { 111 className = c.substring(0, c.length() - 3); // else take whatever comes 112 } 113 } 114 log.warn("Using {}", className); 115 return className; 116 } 117 118 private final static Logger log = LoggerFactory.getLogger(JynstrumentFactory.class); 119}