All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.python.core.PrePy Maven / Gradle / Ivy

Go to download

Jython is an implementation of the high-level, dynamic, object-oriented language Python written in 100% Pure Java, and seamlessly integrated with the Java platform. It thus allows you to run Python on any Java platform.

There is a newer version: 2.7.4
Show newest version
package org.python.core;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.security.AccessControlException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import jnr.posix.util.Platform;

/**
 * This class is part of the Jython run-time system, and contains only "pre-Python" data and methods
 * that may safely be used before the type system is ready. The Jython type system springs into
 * existence in response to a program's first use of any {@code PyObject}, for example when creating
 * the first interpreter. When preparing an application (from the command line options, say) for
 * creation of the first interpreter, it useful to defer type system creation until pre-Python
 * configuration is complete. See PEP 432 for further rationale.
 * 

* Creation of the type system may happen as a side effect of referring using (almost) any object * from a class that statically refers to a {@code PyObject}, for example {@code Py} or * {@code PySystemState}. The present class is intended to hold utility methods and configuration * useful in the pre-Python phase. */ // Do not refer to any PyObject, Py export or PySystemState in this class. public class PrePy { // Logging functions are here so they may be used without starting the Jython runtime. /** Our name-spaced root logger is "org.python". */ protected static final Logger logger = Logger.getLogger("org.python"); protected static final Logger importLogger = Logger.getLogger("org.python.import"); /** {@link Options#verbose} level indicating an error that prevents correct results. */ public static final int ERROR = -1; /** {@link Options#verbose} level indicating an unexpected event, still working correctly. */ public static final int WARNING = 0; /** {@link Options#verbose} level for messages that confirm correct functioning. */ public static final int MESSAGE = 1; /** {@link Options#verbose} level providing detail during correct functioning. */ public static final int COMMENT = 2; /** {@link Options#verbose} level providing detail in support of debugging or tracing. */ public static final int DEBUG = 3; static final Level[] LEVELS = {// Level.OFF, // Level.SEVERE, // Legacy: ERROR Level.WARNING, // Legacy: WARNING Level.INFO, // Legacy: MESSAGE Level.CONFIG, // Legacy: COMMENT Level.FINE, // Legacy: DEBUG Level.FINER, Level.FINEST, Level.ALL}; /** * Translate from the traditional "verbosity" system to JUL Level. We allow Jython verbosity * values beyond the conventional range, treating values <{@link #ERROR} as {@code ERROR} * (that is {@code Level.SEVERE}) and values >{@link #DEBUG} (that is {@code Level.FINE}) as * {@code FINER}, {@code FINEST} and {@code ALL}. * * @param verbosity any integer verbosity, where the runtime default {@link #MESSAGE} = 1 * @return a corresponding level where the default {@link #MESSAGE} produces {@code Level.INFO}. */ public static Level levelFromVerbosity(int verbosity) { if (verbosity < ERROR) { return Level.OFF; } else if (verbosity >= LEVELS.length + (ERROR - 1)) { return Level.ALL; } else { // Bound the index to the LEVELS array. int index = verbosity - (ERROR - 1); return LEVELS[index]; } } /** * Translate from JUL Level to equivalent in the traditional "verbosity" system. We return * Jython verbosity values beyond the conventional range, enough to enumerate the Java standard * levels (e.g {@code FINER} returns 4 and {@code ALL} returns 6 ). * * @param level {@code java.util.logging.Level} to translate. * @return integer verbosity, where the runtime default {@code INFO} = 1 */ public static int verbosityFromLevel(Level level) { /* * Find the least verbose setting v such that events at the given level or above will be * logged by Jython, that is, v such that levelFromVerbosity(v) is a threshold no higher * than the given level. We allow Jython verbosity values beyond the conventional range (e.g * level==FINER), according to the range of values in the LEVELS array. */ int intLevel = level.intValue(); int index = 0, v = ERROR - 1; // = OFF while (index < LEVELS.length && LEVELS[index].intValue() > intLevel) { assert LEVELS[index] == levelFromVerbosity(v); index += 1; v += 1; } return v; } /** * Convenience function to get the effective level of a given Logger, looking up the parent * chain. */ private static Level getEffectiveLoggingLevel(Logger logger) { Level level; while ((level = logger.getLevel()) == null) { logger = logger.getParent(); } return level; } /** Convenience function to get the effective level of Logger "org.python". */ public static Level getLoggingLevel() { return getEffectiveLoggingLevel(logger); } /** * Used by {@link #maybeWrite(Level, String)}, the terminus of all verbosity-based logging * calls, to detect changes made directly to {@link Options#verbose}. */ private static int savedVerbosity = Py.MESSAGE; /** * Set the level of the Jython logger "org.python" using the standard {@code java.util.logging} * scale. For backward compatibility with the traditional "verbosity" system, make a * corresponding setting of {@link Options#verbose}. * * @param newLevel to set * @return previous logging level */ @SuppressWarnings("deprecation") public static Level setLoggingLevel(Level newLevel) { Level previousLevel = getLoggingLevel(); if (newLevel != previousLevel) { try { logger.setLevel(newLevel); } catch (SecurityException se) { logger.warning("A security manager prevented a change to the logging level."); newLevel = previousLevel; } } savedVerbosity = Options.verbose = verbosityFromLevel(newLevel); return previousLevel; } /** * Adjust the level of the Jython logger "org.python" using the traditional "verbosity" system: * the bigger the number, the lower the logging threshold. This is primarily for the * command-line Jython, where each "-v" increases the verbosity by one, on the * {@code java.util.logging} scale. * * @param n increment on the scale {@code 1=INFO, 2=CONFIG, 3=FINE, ... } */ public static void increaseLoggingLevel(int n) { int v = verbosityFromLevel(getLoggingLevel()); setLoggingLevel(levelFromVerbosity(v + n)); } /** * Ensure that the logging system threshold is adjusted to match the legacy * {@link Options#verbose} in the event that that has changed since we last looked. */ @SuppressWarnings("deprecation") private static void syncLoggingLevel() { if (Options.verbose != savedVerbosity) { Level level = levelFromVerbosity(savedVerbosity = Options.verbose); setLoggingLevel(level); } } /** Log a message at a specified level (if that level is not below the threshold). */ @SuppressWarnings("deprecation") public static void maybeWrite(String type, String msg, int verbosity) { // If the caller is using the legacy logging system they may have changed Options.verbose. syncLoggingLevel(); if (verbosity <= Options.verbose) { // Formulate the message in legacy style, then as a log message logger.log(levelFromVerbosity(verbosity), "{0}: {1}", new Object[] {type, msg}); } } /** Submit a message to logging at the severity level ERROR. */ public static void writeError(String type, String msg) { maybeWrite(type, msg, ERROR); } /** Submit a message to logging at the severity level WARNING. */ public static void writeWarning(String type, String msg) { maybeWrite(type, msg, WARNING); } /** Submit a message to logging at the severity level MESSAGE. */ public static void writeMessage(String type, String msg) { maybeWrite(type, msg, MESSAGE); } /** Submit a message to logging at the severity level COMMENT. */ public static void writeComment(String type, String msg) { maybeWrite(type, msg, COMMENT); } /** Submit a message to logging at the severity level DEBUG. */ public static void writeDebug(String type, String msg) { maybeWrite(type, msg, DEBUG); } /** * Get the System properties if we are allowed to. Configuration values set via * {@code -Dprop=value} to the java command will be found here. If a security manager prevents * access, we will return a new (empty) object instead. * * @return {@code System} properties or a new {@code Properties} object */ public static Properties getSystemProperties() { try { return System.getProperties(); } catch (AccessControlException ace) { return new Properties(); } } /** * Get a System property if it is defined, not null, and we are allowed to access it, otherwise * return the given default. * * @param key of the entry to return * @param defaultValue to return if null or disallowed * @return property value or given default */ public static String getSystemProperty(String key, String defaultValue) { try { String value = System.getProperty(key, null); return value != null ? value : defaultValue; } catch (AccessControlException ace) { return defaultValue; } } /** * Determine whether standard input is an interactive stream. If the Java system property * {@code python.launcher.tty} is defined and equal to {@code true} or {@code false}, then that * provides the result. This property is normally supplied by the launcher. In the absence of * this certainty, we use {@link #haveConsole()}. * * @return true if (we think) standard input is an interactive stream */ public static boolean isInteractive() { // python.launcher.tty is authoritative; see http://bugs.jython.org/issue2325 String tty = getSystemProperty("python.launcher.tty", ""); if (tty.equalsIgnoreCase("true")) { return true; } else if (tty.equalsIgnoreCase("false")) { return false; } else { // See if we have access to System.console() return haveConsole(); } } /** Return {@code true} iff the console is accessible through System.console(). */ public static boolean haveConsole() { try { return System.console() != null; } catch (SecurityException se) { return false; } } /** * Check whether an input stream is interactive. This emulates CPython * {@code Py_FdIsInteractive} within the constraints of pure Java. The input stream is * considered ``interactive'' if either *

    *
  1. it is {@code System.in} and {@link #isInteractive()} is {@code true}, or
  2. *
  3. the {@code -i} flag was given ({@link Options#interactive}={@code true}), and the * filename associated with it is {@code null} or {@code ""} or {@code "???"}.
  4. *
* * @param fp stream (tested only for {@code System.in}) * @param filename * @return true iff thought to be interactive */ public static boolean isInteractive(InputStream fp, String filename) { if (fp == System.in && isInteractive()) { return true; } else if (!Options.interactive) { return false; } else { return filename == null || filename.equals("") || filename.equals("???"); } } /** * Infers the usual Jython executable name from the position of the jar-file returned by * {@link #getJarFileName()} by replacing the file name with "bin/jython". This is intended as * an easy fallback for cases where {@code sys.executable} is {@code None} due to direct * launching via the java executable. *

* Note that this does not necessarily return the actual executable, but instead infers the * place where it is usually expected to be. Use {@code sys.executable} to get the actual * executable (may be {@code None}. * * @return usual Jython-executable as absolute path */ public static String getDefaultExecutableName() { return getDefaultBinDir() + File.separator + (Platform.IS_WINDOWS ? "jython.exe" : "jython"); } /** * Infers the usual Jython bin-dir from the position of the jar-file returned by * {@link #getJarFileName()} byr replacing the file name with "bin". This is intended as an easy * fallback for cases where {@code sys.executable} is {@code null} due to direct launching via * the java executable. *

* Note that this does not necessarily return the actual bin-directory, but instead infers the * place where it is usually expected to be. * * @return usual Jython bin-dir as absolute path */ public static String getDefaultBinDir() { String jar = _getJarFileName(); return jar.substring(0, jar.lastIndexOf(File.separatorChar) + 1) + "bin"; } /** * Utility-method to obtain the name (including absolute path) of the currently used * jython-jar-file. Usually this is jython.jar, but can also be jython-dev.jar or * jython-standalone.jar or something custom. * * @return the full name of the jar file containing this class, null if not * available. */ public static String getJarFileName() { String jar = _getJarFileName(); return jar; } /** * Utility-method to obtain the name (including absolute path) of the currently used * jython-jar-file. Usually this is jython.jar, but can also be jython-dev.jar or * jython-standalone.jar or something custom. * * @return the full name of the jar file containing this class, null if not * available. */ private static String _getJarFileName() { Class thisClass = PrePy.class; String fullClassName = thisClass.getName(); String className = fullClassName.substring(fullClassName.lastIndexOf(".") + 1); URL url = thisClass.getResource(className + ".class"); return getJarFileNameFromURL(url); } /** * Return the path in the file system (as a string) of a JAR located by a URL. Three protocols * are supported, Java JAR-file protocol, and two JBoss protocols "vfs" and "vfszip". *

* The JAR-file protocol URL, which must be a {@code jar:file:} reference to a contained element * (that is, it has a "!/" part) is able to identify an actual JAR in a file system that may * then be opened using {@code jarFile = new JarFile(jarFileName)}. The path to the JAR is * returned. If the JAR is accessed by another mechanism ({@code http:} say) this will fail. *

* The JBoss URL must be a reference to exactly * {@code vfs:/org/python/core/PySystemState.class}, or the same thing using the * {@code vfszip:} protocol, where <JAR> stands for the absolute path to the Jython JAR in * VFS. There is no "!/" marker: in JBoss VFS a JAR is treated just like a directory and can no * longer be opened as a JAR. The method essentially just swaps a VFS protocol for the Java * {@code file:} protocol. The path returned will be correct only if this naive swap is valid. * * @param url into the JAR * @return the file path or {@code null} in the event of a detectable error */ public static String getJarFileNameFromURL(URL url) { URI fileURI = null; try { switch (url == null ? "" : url.getProtocol()) { case "jar": // url is jar:file:/some/path/some.jar!/package/with/A.class if (Platform.IS_WINDOWS) { // ... or jar:file://host/some/path/some.jar!/package/with/A.class // ... or jar:file:////host/some/path/some.jar!/package/with/A.class url = tweakWindowsFileURL(url); } URLConnection c = url.openConnection(); fileURI = ((JarURLConnection) c).getJarFileURL().toURI(); break; case "vfs": case "vfszip": // path is /some/path/some-jython.jar/org/python/core/PySystemState.class String path = url.getPath(); final String target = ".jar/" + Py.class.getName().replace('.', '/'); int jarIndex = path.indexOf(target); if (jarIndex > 0) { // path contains the target class in a JAR, so make a file URL for it fileURI = new URL("file:" + path.substring(0, jarIndex + 4)).toURI(); } break; default: // Unknown protocol or url==null: fileURI = null break; } } catch (IOException | URISyntaxException | IllegalArgumentException e) { // Handler cannot open connection or URL is malformed some way: fileURI = null } // The JAR file is now identified in fileURI but needs decoding to a file return fileURI == null ? null : new File(fileURI).toString(); } /** * If the argument is a {@code jar:file:} or {@code file:} URL, compensate for a bug in Java's * construction of URLs affecting {@code java.io.File} and {@code java.net.URLConnection} on * Windows. This is a helper for {@link #getJarFileNameFromURL(URL)}. *

* This bug bites when a JAR file is at a (Windows) UNC location, and a {@code jar:file:} URL is * derived from {@code Class.getResource()} as it is in {@link #_getJarFileName()}. When URL is * supplied to {@link #getJarFileNameFromURL(URL)}, the bug leads to a URI that falsely treats a * server as an "authority". It subsequently causes an {@code IllegalArgumentException} with the * message "URI has an authority component" when we try to construct a File. See * {@link https://bugs.java.com/view_bug.do?bug_id=6360233} ("won't fix"). * * @param url Possibly malformed URL * @return corrected URL */ private static URL tweakWindowsFileURL(URL url) throws MalformedURLException { String urlstr = url.toString(); int fileIndex = urlstr.indexOf("file://"); // 7 chars if (fileIndex >= 0) { // Intended UNC path. If there is no slash following these two, insert "/" here: int insert = fileIndex + 7; if (urlstr.length() > insert && urlstr.charAt(insert) != '/') { url = new URL(urlstr.substring(0, insert) + "//" + urlstr.substring(insert)); } } return url; } /** * Run a command as a sub-process and return as the result the first line of output that * consists of more than white space. It returns "" on any kind of error. * * @param command as strings (as for ProcessBuilder) * @return the first line with content, or "" */ public static String getCommandResult(String... command) { String result = "", line = null; ProcessBuilder pb = new ProcessBuilder(command); try { Process p = pb.start(); java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream())); // We read to the end-of-stream in case the sub-process cannot end cleanly without. while ((line = br.readLine()) != null) { if (line.length() > 0 && result.length() == 0) { // This is the first line with content (maybe). result = line.trim(); } } br.close(); // Now we wait for the sub-process to terminate nicely. if (p.waitFor() != 0) { // Bad exit status: don't take the result. result = ""; } } catch (IOException | InterruptedException | SecurityException e) { result = ""; } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy