Package nodebox.function

Source Code of nodebox.function.PythonLibrary

package nodebox.function;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import nodebox.client.PythonUtils;
import nodebox.util.FileUtils;
import nodebox.util.LoadException;
import org.python.core.*;
import org.python.util.PythonInterpreter;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkArgument;

public class PythonLibrary extends FunctionLibrary {

    private static final Pattern FILE_NAME_PATTERN = Pattern.compile("[a-z0-9_]+\\.py");

    /**
     * Given a file name, determines the namespace.
     *
     * @param fileName The file name. Should end in ".py".
     * @return The namespace.
     */
    public static String namespaceForFile(String fileName) {
        checkArgument(fileName.endsWith(".py"), "The file name of a Python library needs to end in .py (not %s)", fileName);
        checkArgument(fileName.trim().length() >= 4, "The file name can not be empty (was %s).", fileName);
        File f = new File(fileName);
        String baseName = f.getName();
        checkArgument(FILE_NAME_PATTERN.matcher(baseName).matches(), "The file name can only contain lowercase letters, numbers and underscore (was %s).", fileName);
        return baseName.substring(0, baseName.length() - 3);
    }

    /**
     * Load the Python module.
     * <p/>
     * The namespace is determined automatically by using the file name.
     *
     * @param baseFile The file to which the path of this library is relative to.
     * @param fileName The file name.
     * @return The new Python library.
     * @throws LoadException If the script could not be loaded.
     * @see #namespaceForFile(String)
     */
    public static PythonLibrary loadScript(File baseFile, String fileName) throws LoadException {
        return loadScript(namespaceForFile(fileName), baseFile, fileName);
    }

    /**
     * Load the Python module.
     *
     * @param namespace The name space in which the library resides.
     * @param fileName  The file name.
     * @return The new Python library.
     * @throws LoadException If the script could not be loaded.
     */
    public static PythonLibrary loadScript(String namespace, String fileName) throws LoadException {
        return loadScript(namespace, null, fileName);
    }

    /**
     * Load the Python module.
     *
     * @param namespace The name space in which the library resides.
     * @param baseFile  The file to which the path of this library is relative to.
     * @param fileName  The file name.
     * @return The new Python library.
     * @throws LoadException If the script could not be loaded.
     */
    public static PythonLibrary loadScript(String namespace, File baseFile, String fileName) throws LoadException {
        File file;
        if (baseFile != null) {
            file = new File(baseFile, fileName);
        } else {
            file = new File(fileName);
        }
        if (!file.exists()) {
            throw new LoadException(file, "Library does not exist.");
        }
        return new PythonLibrary(namespace, file, loadScript(file));
    }

    private static Future<ImmutableMap<String, Function>> loadScript(final File file) {
        FutureTask<ImmutableMap<String, Function>> task = new FutureTask<ImmutableMap<String, Function>>(new Callable<ImmutableMap<String, Function>>() {
            public ImmutableMap<String, Function> call() throws Exception {
                // This creates a dependency between function and the client.
                // However, we need to know the load paths before we can do anything, so this is necessary.
                PythonUtils.initializePython();
                Py.getSystemState().path.append(new PyString(file.getParentFile().getCanonicalPath()));
                PythonInterpreter interpreter = new PythonInterpreter();
                try {
                    interpreter.execfile(file.getCanonicalPath());
                } catch (IOException e) {
                    throw new LoadException(file, e);
                } catch (PyException e) {
                    throw new LoadException(file, e);
                }
                PyStringMap map = (PyStringMap) interpreter.getLocals();

                ImmutableMap.Builder<String, Function> builder = ImmutableMap.builder();

                for (Object key : map.keys()) {
                    Object o = map.get(Py.java2py(key));
                    if (o instanceof PyFunction) {
                        String name = (String) key;
                        Function f = new PythonFunction(name, (PyFunction) o);
                        builder.put(name, f);
                    }
                }
                return builder.build();
            }
        });
        Thread t = new Thread(task);
        t.start();
        return task;
    }

    private final String namespace;
    private final File file;
    private Future<ImmutableMap<String, Function>> functionMap;

    private PythonLibrary(String namespace, File file, Future<ImmutableMap<String, Function>> functionMap) {
        this.namespace = namespace;
        this.file = file;
        this.functionMap = functionMap;
    }

    @Override
    public String getLink(File baseFile) {
        File parentFile = baseFile != null ? baseFile.getParentFile() : null;
        return "python:" + FileUtils.getRelativeLink(file, parentFile);
    }

    public String getSimpleIdentifier() {
        return file.getName();
    }

    public String getNamespace() {
        return namespace;
    }

    public String getLanguage() {
        return "python";
    }

    public File getFile() {
        return file;
    }

    public ImmutableMap<String, Function> getFunctionMap() {
        try {
            return functionMap.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Function getFunction(String name) {
        return getFunctionMap().get(name);
    }

    public boolean hasFunction(String name) {
        return getFunctionMap().containsKey(name);
    }

    /**
     * Reloads the python module.
     */
    @Override
    public void reload() {
        this.functionMap = loadScript(this.file);
        // Because we don't want this to happen asynchronously, get the results of the functionMap immediately.
        try {
            this.functionMap.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static final class PythonFunction implements Function {

        private final String name;
        private final PyFunction fn;

        public PythonFunction(String name, PyFunction fn) {
            this.name = name;
            this.fn = fn;
        }

        public String getName() {
            return name;
        }

        public Object invoke(Object... args) throws Exception {
            PyObject[] pyArgs = new PyObject[args.length];
            for (int i = 0; i < args.length; i++)
                pyArgs[i] = Py.java2py(args[i]);

            PyObject pyResult = fn.__call__(pyArgs);
            if (pyResult == null)
                return null;
            // todo: number conversions should be handled higher up in the code, and not at the Jython level.
            if (pyResult instanceof PyLong || pyResult instanceof PyInteger)
                return pyResult.__tojava__(Long.class);

            Object result = pyResult.__tojava__(Object.class);
            if (result == Py.NoConversion)
                throw new RuntimeException("Cannot convert Python object " + pyResult + " to java.");
            return result;
        }

        public ImmutableList<Argument> getArguments() {
            // todo: check if keeping a list of arguments makes sense in a python environment.
            return ImmutableList.of();
        }
    }
}
TOP

Related Classes of nodebox.function.PythonLibrary

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.