diff --git a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Main.java b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Main.java index cb87deeeae..60cb08f00b 100644 --- a/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Main.java +++ b/rhino-tools/src/main/java/org/mozilla/javascript/tools/shell/Main.java @@ -32,7 +32,6 @@ import org.mozilla.javascript.JavaScriptException; import org.mozilla.javascript.Kit; import org.mozilla.javascript.NativeArray; -import org.mozilla.javascript.RhinoConfig; import org.mozilla.javascript.RhinoException; import org.mozilla.javascript.Script; import org.mozilla.javascript.Scriptable; @@ -40,6 +39,7 @@ import org.mozilla.javascript.SecurityController; import org.mozilla.javascript.commonjs.module.ModuleScope; import org.mozilla.javascript.commonjs.module.Require; +import org.mozilla.javascript.config.RhinoConfig; import org.mozilla.javascript.tools.SourceReader; import org.mozilla.javascript.tools.ToolErrorReporter; @@ -133,7 +133,7 @@ public void quit(Context cx, int exitCode) { */ public static void main(String args[]) { try { - if (RhinoConfig.DEFAULT.useJavaPolicySecurity()) { + if (RhinoConfig.get("rhino.use_java_policy_security", false)) { initJavaPolicySecuritySupport(); } } catch (SecurityException ex) { diff --git a/rhino/src/main/java/module-info.java b/rhino/src/main/java/module-info.java index 90cad354c6..c7e94dd700 100644 --- a/rhino/src/main/java/module-info.java +++ b/rhino/src/main/java/module-info.java @@ -10,6 +10,7 @@ exports org.mozilla.javascript.serialize; exports org.mozilla.javascript.typedarrays; exports org.mozilla.javascript.xml; + exports org.mozilla.javascript.config; requires java.compiler; requires jdk.dynalink; diff --git a/rhino/src/main/java/org/mozilla/javascript/RhinoConfig.java b/rhino/src/main/java/org/mozilla/javascript/RhinoConfig.java deleted file mode 100644 index 7d26780703..0000000000 --- a/rhino/src/main/java/org/mozilla/javascript/RhinoConfig.java +++ /dev/null @@ -1,237 +0,0 @@ -package org.mozilla.javascript; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; - -/** - * Utility class to read current rhino configuration. - * - *

Rhino properties typically begins with "rhino." (properties) or "RHINO_" (env) - * - *

The configuration is read from these locations: - * - *

    - *
  1. rhino.config file from current class' classpath - *
  2. rhino.config file from current threas's classpath - *
  3. rhino.config file from current directory - *
  4. System-properties starting with "rhino." - *
  5. env variables starting with "RHINO_" (underscores are replaced by '.' and string is - *
- * - *

The config files are in UTF-8 format and all keys in this configuration are case-insensitive - * and dot/underscore-insensitive. - * - *

This means, "rhino.use_java_policy_security" is equvalent to "RHINO_USE_JAVA_POLICY_SECURITY" - * - * @author Roland Praml, Foconis Analytics GmbH - */ -public class RhinoConfig { - - public static final RhinoConfig DEFAULT = - AccessController.doPrivileged((PrivilegedAction) () -> init()); - - private static RhinoConfig init() { - RhinoConfig config = new RhinoConfig(); - // we parse the following locations - config.loadFromClasspath(RhinoConfig.class.getClassLoader()); - config.loadFromClasspath(Thread.currentThread().getContextClassLoader()); - config.loadFromFile(new File("rhino.config")); - config.load(System.getProperties(), "System.properties"); - config.load(System.getenv(), "System.env"); - return config; - } - - private void loadFromFile(File config) { - if (!config.exists()) { - return; - } - try (InputStream in = new FileInputStream(config)) { - Properties props = new Properties(); - props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); - load(props, config.getAbsolutePath()); - } catch (IOException e) { - System.err.println( - "Error loading rhino.config from " - + config.getAbsolutePath() - + ": " - + e.getMessage()); - } - } - - @SuppressWarnings("unchecked") - private void loadFromClasspath(ClassLoader cl) { - if (cl == null) { - return; - } - URL resource = cl.getResource("rhino.config"); - if (resource == null) { - return; - } - try (InputStream in = resource.openStream()) { - Properties props = new Properties(); - props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); - load(props, resource.toString()); - } catch (IOException e) { - System.err.println( - "Error loading rhino.config from " + resource + ": " + e.getMessage()); - } - } - - /** Replacement for {@link System#getProperty(String)}. */ - public static String getProperty(String key) { - return null; - } - - /** Replacement for {@link System#getProperty(String, String)}. */ - public static String getProperty(String key, String defaultValue) { - String val = getProperty(key); - return (val == null) ? defaultValue : val; - } - - /** Replacement for {@link Boolean#getBoolean(String)}. */ - public static boolean getBoolean(String name) { - boolean result = false; - try { - result = Boolean.parseBoolean(getProperty(name)); - } catch (IllegalArgumentException | NullPointerException e) { - } - return result; - } - - /** Replacement for {@link Integer#getInteger(String, Integer)}. */ - public static Integer getInteger(String nm, Integer val) { - String v = null; - try { - v = getProperty(nm); - } catch (IllegalArgumentException | NullPointerException e) { - } - if (v != null) { - try { - return Integer.decode(v); - } catch (NumberFormatException e) { - } - } - return val; - } - - /** Replacement for {@link Integer#getInteger(String, int)}. */ - public static Integer getInteger(String nm, int val) { - Integer result = getInteger(nm, null); - return (result == null) ? Integer.valueOf(val) : result; - } - - /** Replacement for {@link Integer#getInteger(String)}. */ - public static Integer getInteger(String nm) { - return getInteger(nm, null); - } - - /** Returns the property as string. */ - private String get(Map map, String property, String defaultValue) { - Object ret = find(map, property); - if (ret == null) { - return defaultValue; - } else { - return ret.toString(); - } - } - - /** Returns the property as enum. */ - private > T get(Map map, String property, T defaultValue) { - Object ret = map.get(property); - if (ret == null) { - return defaultValue; - } else { - Class enumType = (Class) defaultValue.getClass(); - return Enum.valueOf(enumType, ret.toString().toUpperCase(Locale.ROOT)); - } - } - - /** Returns the property as boolean. */ - private boolean get(Map map, String property, boolean defaultValue) { - Object ret = map.get(property); - if (ret == null) { - return defaultValue; - } else if (ret instanceof Boolean) { - return (Boolean) ret; - } else { - return "1".equals(ret) || "true".equals(ret); - } - } - - /** - * Tries to find the property in the map. It tries the property first, then it tries the camel - * upper version. - */ - private Object find(Map map, String property) { - Object ret = map.get(property); - if (ret != null) { - return ret; - } - return map.get(toCamelUpper(property)); - } - - private String toCamelUpper(String property) { - String s = property.replace('.', '_'); - StringBuilder sb = new StringBuilder(s.length() + 5); - for (int i = 0; i < s.length(); ++i) { - char c = s.charAt(i); - if (i > 0 && Character.isUpperCase(c) && Character.isLowerCase(s.charAt(i - 1))) { - sb.append('_'); - } - sb.append(Character.toUpperCase(c)); - } - return sb.toString(); - } - - private void load(Map map, String location) { - stackStyle = get(map, "rhino.stack.style", stackStyle); - useJavaPolicySecurity = get(map, "rhino.use_java_policy_security", useJavaPolicySecurity); - printTrees = get(map, "rhino.printTrees", printTrees); - printICodes = get(map, "rhino.printICodes", printICodes); - debugStack = get(map, "rhino.debugStack", debugStack); - debugLinker = get(map, "rhino.debugLinker", debugStack); - } - - /** "rhino.stack.style" */ - private StackStyle stackStyle = StackStyle.RHINO; - - private boolean useJavaPolicySecurity; - private boolean printTrees = false; - private boolean printICodes = false; - private boolean debugStack = false; - private boolean debugLinker = false; - - public StackStyle stackStyle() { - return stackStyle; - } - - public boolean useJavaPolicySecurity() { - return useJavaPolicySecurity; - } - - public boolean printTrees() { - return printTrees; - } - - public boolean printICodes() { - return printICodes; - } - - public boolean debugStack() { - return debugStack; - } - - public boolean debugLinker() { - return debugLinker; - } -} diff --git a/rhino/src/main/java/org/mozilla/javascript/RhinoException.java b/rhino/src/main/java/org/mozilla/javascript/RhinoException.java index 5ceb959cc7..a88e800c71 100644 --- a/rhino/src/main/java/org/mozilla/javascript/RhinoException.java +++ b/rhino/src/main/java/org/mozilla/javascript/RhinoException.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.mozilla.javascript.config.RhinoConfig; /** The class of exceptions thrown by the JavaScript engine. */ public abstract class RhinoException extends RuntimeException { @@ -374,7 +375,7 @@ public static StackStyle getStackStyle() { private static final long serialVersionUID = 1883500631321581169L; // Just for testing! - private static StackStyle stackStyle = RhinoConfig.DEFAULT.stackStyle(); + private static StackStyle stackStyle = RhinoConfig.get("rhino.stack.style", StackStyle.RHINO); private String sourceName; private int lineNumber; diff --git a/rhino/src/main/java/org/mozilla/javascript/Token.java b/rhino/src/main/java/org/mozilla/javascript/Token.java index 4db392687c..128ccca048 100644 --- a/rhino/src/main/java/org/mozilla/javascript/Token.java +++ b/rhino/src/main/java/org/mozilla/javascript/Token.java @@ -6,6 +6,8 @@ package org.mozilla.javascript; +import org.mozilla.javascript.config.RhinoConfig; + /** * This class implements the JavaScript scanner. * @@ -24,8 +26,8 @@ public static enum CommentType { } // debug flags - public static final boolean printTrees = RhinoConfig.DEFAULT.printTrees(); - static final boolean printICode = RhinoConfig.DEFAULT.printICodes(); + public static final boolean printTrees = RhinoConfig.get("rhino.printTrees", false); + static final boolean printICode = RhinoConfig.get("rhino.printICode", false); static final boolean printNames = printTrees || printICode; /** Token types. These values correspond to JSTokenType values in jsscan.c. */ diff --git a/rhino/src/main/java/org/mozilla/javascript/config/RhinoConfig.java b/rhino/src/main/java/org/mozilla/javascript/config/RhinoConfig.java new file mode 100644 index 0000000000..aaec0f34f5 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/config/RhinoConfig.java @@ -0,0 +1,60 @@ +package org.mozilla.javascript.config; + +import java.util.Locale; + +/** + * With RhinoConfig, you can access the {@link RhinoProperties} in a typesafe way. + * + * @author Roland Praml, Foconis Analytics GmbH + */ +public class RhinoConfig { + + /** Returns the property as string. */ + private static String get(String property, String defaultValue) { + Object ret = RhinoProperties.get(property); + if (ret != null) { + return ret.toString(); + } + return defaultValue; + } + + /** Returns the property as string with null as default. */ + public static String get(String property) { + return get(property, (String) null); + } + + /** Returns the property as enum. Note: default value must be specified */ + public static > T get(String property, T defaultValue) { + Object ret = RhinoProperties.get(property); + if (ret != null) { + Class enumType = (Class) defaultValue.getClass(); + // We assume, that enums all are in UPPERCASES + return Enum.valueOf(enumType, ret.toString().toUpperCase(Locale.ROOT)); + } + return defaultValue; + } + + /** Returns the property as boolean. */ + public static boolean get(String property, boolean defaultValue) { + Object ret = RhinoProperties.get(property); + if (ret instanceof Boolean) { + return (Boolean) ret; + } else { + return "1".equals(ret) || "true".equals(ret); + } + } + + /** Returns the property as integer. */ + public static int get(String property, int defaultValue) { + Object ret = RhinoProperties.get(property); + if (ret instanceof Number) { + return ((Number) ret).intValue(); + } else { + try { + return Integer.decode(ret.toString()); + } catch (NumberFormatException e) { + } + } + return defaultValue; + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/config/RhinoProperties.java b/rhino/src/main/java/org/mozilla/javascript/config/RhinoProperties.java new file mode 100644 index 0000000000..e872de7ad4 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/config/RhinoProperties.java @@ -0,0 +1,163 @@ +package org.mozilla.javascript.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.ServiceLoader; + +/** + * Utility class to read current rhino configuration from various properties. + * + *

Rhino properties typically begins with "rhino." (properties) or "RHINO_" (env) + * + *

You can override this behaviour and implement a {@link RhinoPropertiesLoader} and register it + * as service. If no loader was found, the configuration is read from these locations by default: + * + *

    + *
  1. rhino.config file from current class' classpath + *
  2. rhino.config file from current threas's classpath + *
  3. rhino.config file from current directory + *
  4. rhino-test.config file from current class' classpath + *
  5. rhino-test.config file from current threas's classpath + *
  6. rhino-test.config file from current directory + *
  7. env variables starting with "RHINO_" (underscores are replaced by '.' and string is + *
  8. System-properties starting with "rhino." + *
+ * + *

(the later config can override previous ones) + * + *

The config files are in UTF-8 format and all keys in this configuration are case-insensitive + * and dot/underscore-insensitive. + * + *

+ * + *

This means, "rhino.use_java_policy_security" is equvalent to "RHINO_USE_JAVA_POLICY_SECURITY" + * + *

This class contains only the properties. Every plugin etc. can parse its config from there. + * + * @author Roland Praml, Foconis Analytics GmbH + */ +public class RhinoProperties { + + private static String[] CONFIG_FILES = {"rhino.config", "rhino-test.config"}; + private static final RhinoProperties INSTANCE = + AccessController.doPrivileged((PrivilegedAction) () -> init()); + + static RhinoProperties init() { + RhinoProperties props = new RhinoProperties(); + Iterator loader = + ServiceLoader.load(RhinoPropertiesLoader.class).iterator(); + if (loader.hasNext()) { + while (loader.hasNext()) { + loader.next().load(props); + } + } else { + props.loadDefaults(); + } + return props; + } + + /** Load properties from the default locations. */ + public void loadDefaults() { + ClassLoader classLoader = RhinoProperties.class.getClassLoader(); + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + + for (String configFile : CONFIG_FILES) { + loadFromClasspath(classLoader, configFile); + loadFromClasspath(contextClassLoader, configFile); + loadFromFile(new File(configFile)); + } + + addConfig(System.getenv()); + addConfig(System.getProperties()); + } + + private List> configs = new ArrayList<>(); + + /** Loads the configuration from the given file. */ + public void loadFromFile(File config) { + if (!config.exists()) { + return; + } + try (InputStream in = new FileInputStream(config)) { + Properties props = new Properties(); + props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); + addConfig(props); + } catch (IOException e) { + System.err.println( + "Error loading configuration from " + + config.getAbsolutePath() + + ": " + + e.getMessage()); + } + } + + /** Loads the configuration from classpath. */ + public void loadFromClasspath(ClassLoader cl, String location) { + if (cl != null) { + loadFromResource(cl.getResource(location)); + } + } + + /** Loads the configuration from the given resource. */ + public void loadFromResource(URL resource) { + if (resource == null) { + return; + } + try (InputStream in = resource.openStream()) { + Properties props = new Properties(); + props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); + addConfig(props); + } catch (IOException e) { + System.err.println( + "Error loading configuration from " + resource + ": " + e.getMessage()); + } + } + + /** Adds a config map */ + public void addConfig(Map config) { + configs.add(config); + } + + /** + * Tries to find the property in the maps. It tries the property first, then it tries the camel + * upper version. + */ + public static Object get(String property) { + for (Map map : INSTANCE.configs) { + Object ret = map.get(property); + if (ret != null) { + return ret; + } + ret = map.get(toCamelUpper(property)); + if (ret != null) { + return ret; + } + } + return null; + } + + private static String toCamelUpper(String property) { + String s = property.replace('.', '_'); + StringBuilder sb = new StringBuilder(s.length() + 5); + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + if (i > 0 && Character.isUpperCase(c) && Character.isLowerCase(s.charAt(i - 1))) { + sb.append('_'); + } + sb.append(Character.toUpperCase(c)); + } + return sb.toString(); + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/config/RhinoPropertiesLoader.java b/rhino/src/main/java/org/mozilla/javascript/config/RhinoPropertiesLoader.java new file mode 100644 index 0000000000..296abc2303 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/config/RhinoPropertiesLoader.java @@ -0,0 +1,13 @@ +package org.mozilla.javascript.config; + +/** + * Optional Loader interface for loading properties. You can override the + * DefaultRhinoPropertiesLoader by providing your implementation as service. + * + * @author Roland Praml, Foconis Analytics GmbH + */ +public interface RhinoPropertiesLoader { + + /** Classes can add their configs to rhinoProperties. */ + void load(RhinoProperties properties); +} diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java index 1b11403bb6..7f2ab7bec8 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java @@ -11,10 +11,10 @@ import jdk.dynalink.linker.LinkerServices; import org.mozilla.javascript.Callable; import org.mozilla.javascript.Context; -import org.mozilla.javascript.RhinoConfig; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.Token; +import org.mozilla.javascript.config.RhinoConfig; /** * This linker is the last one in the chain, and as such it must be able to link every type of @@ -23,7 +23,7 @@ */ @SuppressWarnings("AndroidJdkLibsChecker") class DefaultLinker implements GuardingDynamicLinker { - static final boolean DEBUG = RhinoConfig.DEFAULT.debugLinker(); + static final boolean DEBUG = RhinoConfig.get("rhino.debugLinker", false); @Override public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc)