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}