8040078: Avoid repeated reading of source for cached loads
Reviewed-by: jlaskey, lagergren
--- a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java Fri Apr 25 16:34:17 2014 +0200
@@ -27,16 +27,14 @@
import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
-import java.nio.charset.Charset;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permissions;
@@ -124,21 +122,21 @@
}
}
- // load engine.js and return content as a char[]
+ // load engine.js
@SuppressWarnings("resource")
- private static char[] loadEngineJSSource() {
+ private static Source loadEngineJSSource() {
final String script = "resources/engine.js";
try {
- final InputStream is = AccessController.doPrivileged(
- new PrivilegedExceptionAction<InputStream>() {
+ return AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Source>() {
@Override
- public InputStream run() throws Exception {
+ public Source run() throws IOException {
final URL url = NashornScriptEngine.class.getResource(script);
- return url.openStream();
+ return sourceFor(NashornException.ENGINE_SCRIPT_SOURCE_NAME, url);
}
- });
- return Source.readFully(new InputStreamReader(is));
- } catch (final PrivilegedActionException | IOException e) {
+ }
+ );
+ } catch (final PrivilegedActionException e) {
if (Context.DEBUG) {
e.printStackTrace();
}
@@ -147,7 +145,7 @@
}
// Source object for engine.js
- private static final Source ENGINE_SCRIPT_SRC = new Source(NashornException.ENGINE_SCRIPT_SOURCE_NAME, loadEngineJSSource());
+ private static final Source ENGINE_SCRIPT_SRC = loadEngineJSSource();
NashornScriptEngine(final NashornScriptEngineFactory factory, final ClassLoader appLoader) {
this(factory, DEFAULT_OPTIONS, appLoader);
@@ -282,19 +280,14 @@
private static Source makeSource(final Reader reader, final ScriptContext ctxt) throws ScriptException {
try {
- if (reader instanceof URLReader) {
- final URL url = ((URLReader)reader).getURL();
- final Charset cs = ((URLReader)reader).getCharset();
- return new Source(url.toString(), url, cs);
- }
- return new Source(getScriptName(ctxt), Source.readFully(reader));
- } catch (final IOException e) {
+ return sourceFor(getScriptName(ctxt), reader);
+ } catch (IOException e) {
throw new ScriptException(e);
}
}
private static Source makeSource(final String src, final ScriptContext ctxt) {
- return new Source(getScriptName(ctxt), src);
+ return sourceFor(getScriptName(ctxt), src);
}
private static String getScriptName(final ScriptContext ctxt) {
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java Fri Apr 25 16:34:17 2014 +0200
@@ -25,6 +25,8 @@
package jdk.nashorn.internal.ir.debug;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
+
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
@@ -88,7 +90,7 @@
* @return JSON string representation of AST of the supplied code
*/
public static String parse(final ScriptEnvironment env, final String code, final String name, final boolean includeLoc) {
- final Parser parser = new Parser(env, new Source(name, code), new Context.ThrowErrorManager(), env._strict);
+ final Parser parser = new Parser(env, sourceFor(name, code), new Context.ThrowErrorManager(), env._strict);
final JSONWriter jsonWriter = new JSONWriter(includeLoc);
try {
final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.symbolName());
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeFunction.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeFunction.java Fri Apr 25 16:34:17 2014 +0200
@@ -27,6 +27,7 @@
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
import java.util.List;
@@ -257,7 +258,7 @@
}
private static void checkFunctionParameters(final String params) {
- final Source src = new Source("<function>", params);
+ final Source src = sourceFor("<function>", params);
final Parser parser = new Parser(Global.getEnv(), src, new Context.ThrowErrorManager());
try {
parser.parseFormalParameterList();
@@ -267,7 +268,7 @@
}
private static void checkFunctionBody(final String funcBody) {
- final Source src = new Source("<function>", funcBody);
+ final Source src = sourceFor("<function>", funcBody);
final Parser parser = new Parser(Global.getEnv(), src, new Context.ThrowErrorManager());
try {
parser.parseFunctionBody();
--- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java Fri Apr 25 16:34:17 2014 +0200
@@ -32,6 +32,7 @@
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
import java.io.File;
import java.io.IOException;
@@ -501,7 +502,7 @@
*/
public Object eval(final ScriptObject initialScope, final String string, final Object callThis, final Object location, final boolean strict) {
final String file = (location == UNDEFINED || location == null) ? "<eval>" : location.toString();
- final Source source = new Source(file, string);
+ final Source source = sourceFor(file, string);
final boolean directEval = location != UNDEFINED; // is this direct 'eval' call or indirectly invoked eval?
final Global global = Context.getGlobal();
@@ -568,7 +569,7 @@
public Source run() {
try {
final URL resURL = Context.class.getResource(resource);
- return (resURL != null)? new Source(srcStr, resURL) : null;
+ return (resURL != null)? sourceFor(srcStr, resURL) : null;
} catch (final IOException exp) {
return null;
}
@@ -600,7 +601,7 @@
final String srcStr = (String)src;
if (srcStr.startsWith(LOAD_CLASSPATH)) {
URL url = getResourceURL(srcStr.substring(LOAD_CLASSPATH.length()));
- source = (url != null)? new Source(url.toString(), url) : null;
+ source = (url != null)? sourceFor(url.toString(), url) : null;
} else {
final File file = new File(srcStr);
if (srcStr.indexOf(':') != -1) {
@@ -613,31 +614,31 @@
} catch (final MalformedURLException e) {
url = file.toURI().toURL();
}
- source = new Source(url.toString(), url);
+ source = sourceFor(url.toString(), url);
}
} else if (file.isFile()) {
- source = new Source(srcStr, file);
+ source = sourceFor(srcStr, file);
}
}
} else if (src instanceof File && ((File)src).isFile()) {
final File file = (File)src;
- source = new Source(file.getName(), file);
+ source = sourceFor(file.getName(), file);
} else if (src instanceof URL) {
final URL url = (URL)src;
- source = new Source(url.toString(), url);
+ source = sourceFor(url.toString(), url);
} else if (src instanceof ScriptObject) {
final ScriptObject sobj = (ScriptObject)src;
if (sobj.has("script") && sobj.has("name")) {
final String script = JSType.toString(sobj.get("script"));
final String name = JSType.toString(sobj.get("name"));
- source = new Source(name, script);
+ source = sourceFor(name, script);
}
} else if (src instanceof Map) {
final Map<?,?> map = (Map<?,?>)src;
if (map.containsKey("script") && map.containsKey("name")) {
final String script = JSType.toString(map.get("script"));
final String name = JSType.toString(map.get("name"));
- source = new Source(name, script);
+ source = sourceFor(name, script);
}
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/JSONFunctions.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/JSONFunctions.java Fri Apr 25 16:34:17 2014 +0200
@@ -39,6 +39,8 @@
import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
+
/**
* Utilities used by "JSON" object implementation.
*/
@@ -77,9 +79,7 @@
*/
public static Object parse(final Object text, final Object reviver) {
final String str = JSType.toString(text);
- final JSONParser parser = new JSONParser(
- new Source("<json>", str),
- new Context.ThrowErrorManager());
+ final JSONParser parser = new JSONParser(sourceFor("<json>", str), new Context.ThrowErrorManager());
Node node;
--- a/nashorn/src/jdk/nashorn/internal/runtime/Source.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Source.java Fri Apr 25 16:34:17 2014 +0200
@@ -27,13 +27,16 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
+import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.net.URLConnection;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -43,13 +46,19 @@
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Objects;
+import java.util.WeakHashMap;
+import jdk.nashorn.api.scripting.URLReader;
import jdk.nashorn.internal.parser.Token;
/**
* Source objects track the origin of JavaScript entities.
- *
*/
public final class Source {
+
+ private static final DebugLogger DEBUG = new DebugLogger("source");
+ private static final int BUF_SIZE = 8 * 1024;
+ private static final Cache CACHE = new Cache();
+
/**
* Descriptive name of the source as supplied by the user. Used for error
* reporting to the user. For example, SyntaxError will use this to print message.
@@ -64,11 +73,8 @@
*/
private final String base;
- /** Cached source content. */
- private final char[] content;
-
- /** Length of source content. */
- private final int length;
+ /** Source content */
+ private final Data data;
/** Cached hash code */
private int hash;
@@ -76,40 +82,297 @@
/** Message digest */
private byte[] digest;
- /** Source URL if available */
- private final URL url;
+ // Do *not* make this public, ever! Trusts the URL and content.
+ private Source(final String name, final String base, final Data data) {
+ this.name = name;
+ this.base = base;
+ this.data = data;
+ }
+
+ private static synchronized Source sourceFor(final String name, final String base, final URLData data) throws IOException {
+ try {
+ final Source newSource = new Source(name, base, data);
+ final Source existingSource = CACHE.get(newSource);
+ if (existingSource != null) {
+ // Force any access errors
+ data.checkPermissionAndClose();
+ return existingSource;
+ } else {
+ // All sources in cache must be fully loaded
+ data.load();
+ CACHE.put(newSource, newSource);
+ return newSource;
+ }
+ } catch (RuntimeException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ }
+ throw e;
+ }
+ }
+
+ private static class Cache extends WeakHashMap<Source, WeakReference<Source>> {
+ public Source get(final Source key) {
+ final WeakReference<Source> ref = super.get(key);
+ return ref == null ? null : ref.get();
+ }
+
+ public void put(final Source key, final Source value) {
+ assert !(value.data instanceof RawData);
+ put(key, new WeakReference<>(value));
+ }
+ }
+
+ // Wrapper to manage lazy loading
+ private static interface Data {
+
+ URL url();
+
+ int length();
+
+ long lastModified();
+
+ char[] array();
+ }
+
+ private static class RawData implements Data {
+ private final char[] array;
+ private int hash;
+
+ private RawData(final char[] array) {
+ this.array = Objects.requireNonNull(array);
+ }
- private static final int BUFSIZE = 8 * 1024;
+ private RawData(final String source) {
+ this.array = Objects.requireNonNull(source).toCharArray();
+ }
+
+ private RawData(final Reader reader) throws IOException {
+ this(readFully(reader));
+ }
+
+ @Override
+ public int hashCode() {
+ int h = hash;
+ if (h == 0) {
+ h = hash = Arrays.hashCode(array);
+ }
+ return h;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof RawData) {
+ return Arrays.equals(array, ((RawData)obj).array);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return new String(array());
+ }
+
+ @Override
+ public URL url() {
+ return null;
+ }
+
+ @Override
+ public int length() {
+ return array.length;
+ }
+
+ @Override
+ public long lastModified() {
+ return 0;
+ }
+
+ @Override
+ public char[] array() {
+ return array;
+ }
+
+
+ }
+
+ private static class URLData implements Data {
+ private final URL url;
+ protected final Charset cs;
+ private int hash;
+ protected char[] array;
+ protected int length;
+ protected long lastModified;
+
+ private URLData(final URL url, final Charset cs) {
+ this.url = Objects.requireNonNull(url);
+ this.cs = cs;
+ }
- // Do *not* make this public ever! Trusts the URL and content. So has to be called
- // from other public constructors. Note that this can not be some init method as
- // we initialize final fields from here.
- private Source(final String name, final String base, final char[] content, final URL url) {
- this.name = name;
- this.base = base;
- this.content = content;
- this.length = content.length;
- this.url = url;
+ @Override
+ public int hashCode() {
+ int h = hash;
+ if (h == 0) {
+ h = hash = url.hashCode();
+ }
+ return h;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof URLData)) {
+ return false;
+ }
+
+ URLData otherData = (URLData) other;
+
+ if (url.equals(otherData.url)) {
+ // Make sure both have meta data loaded
+ try {
+ if (isDeferred()) {
+ // Data in cache is always loaded, and we only compare to cached data.
+ assert !otherData.isDeferred();
+ loadMeta();
+ } else if (otherData.isDeferred()) {
+ otherData.loadMeta();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ // Compare meta data
+ return this.length == otherData.length && this.lastModified == otherData.lastModified;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return new String(array());
+ }
+
+ @Override
+ public URL url() {
+ return url;
+ }
+
+ @Override
+ public int length() {
+ return length;
+ }
+
+ @Override
+ public long lastModified() {
+ return lastModified;
+ }
+
+ @Override
+ public char[] array() {
+ assert !isDeferred();
+ return array;
+ }
+
+ boolean isDeferred() {
+ return array == null;
+ }
+
+ protected void checkPermissionAndClose() throws IOException {
+ try (InputStream in = url.openStream()) {}
+ debug("permission checked for ", url);
+ }
+
+ protected void load() throws IOException {
+ if (array == null) {
+ final URLConnection c = url.openConnection();
+ try (InputStream in = c.getInputStream()) {
+ array = cs == null ? readFully(in) : readFully(in, cs);
+ length = array.length;
+ lastModified = c.getLastModified();
+ debug("loaded content for ", url);
+ }
+ }
+ }
+
+ protected void loadMeta() throws IOException {
+ if (length == 0 && lastModified == 0) {
+ final URLConnection c = url.openConnection();
+ length = c.getContentLength();
+ lastModified = c.getLastModified();
+ debug("loaded metadata for ", url);
+ }
+ }
+ }
+
+ private static class FileData extends URLData {
+ private final File file;
+
+ private FileData(final File file, final Charset cs) {
+ super(getURLFromFile(file), cs);
+ this.file = file;
+
+ }
+
+ @Override
+ protected void checkPermissionAndClose() throws IOException {
+ if (!file.canRead()) {
+ throw new FileNotFoundException(file + " (Permission Denied)");
+ }
+ debug("permission checked for ", file);
+ }
+
+ @Override
+ protected void loadMeta() {
+ if (length == 0 && lastModified == 0) {
+ length = (int) file.length();
+ lastModified = file.lastModified();
+ debug("loaded metadata for ", file);
+ }
+ }
+
+ @Override
+ protected void load() throws IOException {
+ if (array == null) {
+ array = cs == null ? readFully(file) : readFully(file, cs);
+ length = array.length;
+ lastModified = file.lastModified();
+ debug("loaded content for ", file);
+ }
+ }
+ }
+
+ private static void debug(final Object... msg) {
+ DEBUG.info(msg);
+ }
+
+ private char[] data() {
+ return data.array();
}
/**
- * Constructor
+ * Returns an instance
*
* @param name source name
* @param content contents as char array
*/
- public Source(final String name, final char[] content) {
- this(name, baseName(name, null), content, null);
+ public static Source sourceFor(final String name, final char[] content) {
+ return new Source(name, baseName(name), new RawData(content));
}
/**
- * Constructor
+ * Returns an instance
*
* @param name source name
* @param content contents as string
*/
- public Source(final String name, final String content) {
- this(name, content.toCharArray());
+ public static Source sourceFor(final String name, final String content) {
+ return new Source(name, baseName(name), new RawData(content));
}
/**
@@ -120,8 +383,8 @@
*
* @throws IOException if source cannot be loaded
*/
- public Source(final String name, final URL url) throws IOException {
- this(name, baseURL(url, null), readFully(url), url);
+ public static Source sourceFor(final String name, final URL url) throws IOException {
+ return sourceFor(name, url, null);
}
/**
@@ -133,8 +396,8 @@
*
* @throws IOException if source cannot be loaded
*/
- public Source(final String name, final URL url, final Charset cs) throws IOException {
- this(name, baseURL(url, null), readFully(url, cs), url);
+ public static Source sourceFor(final String name, final URL url, final Charset cs) throws IOException {
+ return sourceFor(name, baseURL(url), new URLData(url, cs));
}
/**
@@ -145,8 +408,8 @@
*
* @throws IOException if source cannot be loaded
*/
- public Source(final String name, final File file) throws IOException {
- this(name, dirName(file, null), readFully(file), getURLFromFile(file));
+ public static Source sourceFor(final String name, final File file) throws IOException {
+ return sourceFor(name, file, null);
}
/**
@@ -158,8 +421,25 @@
*
* @throws IOException if source cannot be loaded
*/
- public Source(final String name, final File file, final Charset cs) throws IOException {
- this(name, dirName(file, null), readFully(file, cs), getURLFromFile(file));
+ public static Source sourceFor(final String name, final File file, final Charset cs) throws IOException {
+ final File absFile = file.getAbsoluteFile();
+ return sourceFor(name, dirName(absFile, null), new FileData(file, cs));
+ }
+
+ /**
+ * Returns an instance
+ *
+ * @param name source name
+ * @param reader reader from which source can be loaded
+ * @throws IOException if source cannot be loaded
+ */
+ public static Source sourceFor(final String name, final Reader reader) throws IOException {
+ // Extract URL from URLReader to defer loading and reuse cached data if available.
+ if (reader instanceof URLReader) {
+ final URLReader urlReader = (URLReader) reader;
+ return sourceFor(name, urlReader.getURL(), urlReader.getCharset());
+ }
+ return new Source(name, baseName(name), new RawData(reader));
}
@Override
@@ -167,21 +447,18 @@
if (this == obj) {
return true;
}
-
if (!(obj instanceof Source)) {
return false;
}
-
- final Source src = (Source)obj;
- // Only compare content as a last resort measure
- return length == src.length && Objects.equals(url, src.url) && Objects.equals(name, src.name) && Arrays.equals(content, src.content);
+ final Source other = (Source) obj;
+ return Objects.equals(name, other.name) && data.equals(other.data);
}
@Override
public int hashCode() {
int h = hash;
if (h == 0) {
- h = hash = Arrays.hashCode(content) ^ Objects.hashCode(name);
+ h = hash = data.hashCode() ^ Objects.hashCode(name);
}
return h;
}
@@ -191,7 +468,7 @@
* @return Source content.
*/
public String getString() {
- return new String(content, 0, length);
+ return data.toString();
}
/**
@@ -203,6 +480,14 @@
}
/**
+ * Get the last modified time of this script.
+ * @return Last modified time.
+ */
+ public long getLastModified() {
+ return data.lastModified();
+ }
+
+ /**
* Get the "directory" part of the file or "base" of the URL.
* @return base of file or URL.
*/
@@ -217,7 +502,7 @@
* @return Source content portion.
*/
public String getString(final int start, final int len) {
- return new String(content, start, len);
+ return new String(data(), start, len);
}
/**
@@ -228,7 +513,7 @@
public String getString(final long token) {
final int start = Token.descPosition(token);
final int len = Token.descLength(token);
- return new String(content, start, len);
+ return new String(data(), start, len);
}
/**
@@ -238,7 +523,7 @@
* @return URL source or null
*/
public URL getURL() {
- return url;
+ return data.url();
}
/**
@@ -247,8 +532,9 @@
* @return Index of first character of line.
*/
private int findBOLN(final int position) {
+ final char[] data = data();
for (int i = position - 1; i > 0; i--) {
- final char ch = content[i];
+ final char ch = data[i];
if (ch == '\n' || ch == '\r') {
return i + 1;
@@ -264,8 +550,10 @@
* @return Index of last character of line.
*/
private int findEOLN(final int position) {
- for (int i = position; i < length; i++) {
- final char ch = content[i];
+ final char[] data = data();
+ final int length = data.length;
+ for (int i = position; i < length; i++) {
+ final char ch = data[i];
if (ch == '\n' || ch == '\r') {
return i - 1;
@@ -285,11 +573,12 @@
* @return Line number.
*/
public int getLine(final int position) {
+ final char[] data = data();
// Line count starts at 1.
int line = 1;
for (int i = 0; i < position; i++) {
- final char ch = content[i];
+ final char ch = data[i];
// Works for both \n and \r\n.
if (ch == '\n') {
line++;
@@ -320,7 +609,7 @@
// Find end of this line.
final int last = findEOLN(position);
- return new String(content, first, last - first + 1);
+ return new String(data(), first, last - first + 1);
}
/**
@@ -328,7 +617,7 @@
* @return content
*/
public char[] getContent() {
- return content.clone();
+ return data().clone();
}
/**
@@ -336,19 +625,18 @@
* @return length
*/
public int getLength() {
- return length;
+ return data.length();
}
/**
* Read all of the source until end of file. Return it as char array
*
- * @param reader reader opened to source stream
+ * @param reader reader opened to source stream
* @return source as content
- *
* @throws IOException if source could not be read
*/
public static char[] readFully(final Reader reader) throws IOException {
- final char[] arr = new char[BUFSIZE];
+ final char[] arr = new char[BUF_SIZE];
final StringBuilder sb = new StringBuilder();
try {
@@ -366,9 +654,8 @@
/**
* Read all of the source until end of file. Return it as char array
*
- * @param file source file
+ * @param file source file
* @return source as content
- *
* @throws IOException if source could not be read
*/
public static char[] readFully(final File file) throws IOException {
@@ -381,10 +668,9 @@
/**
* Read all of the source until end of file. Return it as char array
*
- * @param file source file
+ * @param file source file
* @param cs Charset used to convert bytes to chars
* @return source as content
- *
* @throws IOException if source could not be read
*/
public static char[] readFully(final File file, final Charset cs) throws IOException {
@@ -393,7 +679,7 @@
}
final byte[] buf = Files.readAllBytes(file.toPath());
- return (cs != null)? new String(buf, cs).toCharArray() : byteToCharArray(buf);
+ return (cs != null) ? new String(buf, cs).toCharArray() : byteToCharArray(buf);
}
/**
@@ -401,7 +687,6 @@
*
* @param url URL to read content from
* @return source as content
- *
* @throws IOException if source could not be read
*/
public static char[] readFully(final URL url) throws IOException {
@@ -414,7 +699,6 @@
* @param url URL to read content from
* @param cs Charset used to convert bytes to chars
* @return source as content
- *
* @throws IOException if source could not be read
*/
public static char[] readFully(final URL url, final Charset cs) throws IOException {
@@ -428,7 +712,7 @@
*/
public synchronized byte[] getDigest() {
if (digest == null) {
-
+ final char[] content = data();
final byte[] bytes = new byte[content.length * 2];
for (int i = 0; i < content.length; i++) {
@@ -444,8 +728,8 @@
if (base != null) {
md.update(base.getBytes(StandardCharsets.UTF_8));
}
- if (url != null) {
- md.update(url.toString().getBytes(StandardCharsets.UTF_8));
+ if (getURL() != null) {
+ md.update(getURL().toString().getBytes(StandardCharsets.UTF_8));
}
digest = md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
@@ -461,50 +745,46 @@
* @return base URL for url
*/
public static String baseURL(final URL url) {
- return baseURL(url, null);
- }
-
- private static String baseURL(final URL url, final String defaultValue) {
if (url.getProtocol().equals("file")) {
try {
final Path path = Paths.get(url.toURI());
final Path parent = path.getParent();
- return (parent != null) ? (parent + File.separator) : defaultValue;
+ return (parent != null) ? (parent + File.separator) : null;
} catch (final SecurityException | URISyntaxException | IOError e) {
- return defaultValue;
+ return null;
}
}
// FIXME: is there a better way to find 'base' URL of a given URL?
String path = url.getPath();
if (path.isEmpty()) {
- return defaultValue;
+ return null;
}
path = path.substring(0, path.lastIndexOf('/') + 1);
final int port = url.getPort();
try {
return new URL(url.getProtocol(), url.getHost(), port, path).toString();
} catch (final MalformedURLException e) {
- return defaultValue;
+ return null;
}
}
- private static String dirName(final File file, final String defaultValue) {
+ private static String dirName(final File file, final String DEFAULT_BASE_NAME) {
final String res = file.getParent();
- return (res != null)? (res + File.separator) : defaultValue;
+ return (res != null) ? (res + File.separator) : DEFAULT_BASE_NAME;
}
// fake directory like name
- private static String baseName(final String name, final String defaultValue) {
+ private static String baseName(final String name) {
int idx = name.lastIndexOf('/');
if (idx == -1) {
idx = name.lastIndexOf('\\');
}
- return (idx != -1)? name.substring(0, idx + 1) : defaultValue;
+ return (idx != -1) ? name.substring(0, idx + 1) : null;
}
private static char[] readFully(final InputStream is, final Charset cs) throws IOException {
- return (cs != null)? new String(readBytes(is), cs).toCharArray() : readFully(is);
+ return (cs != null) ? new String(readBytes(is), cs).toCharArray() : readFully(is);
}
private static char[] readFully(final InputStream is) throws IOException {
@@ -515,19 +795,19 @@
Charset cs = StandardCharsets.UTF_8;
int start = 0;
// BOM detection.
- if (bytes.length > 1 && bytes[0] == (byte)0xFE && bytes[1] == (byte)0xFF) {
+ if (bytes.length > 1 && bytes[0] == (byte) 0xFE && bytes[1] == (byte) 0xFF) {
start = 2;
cs = StandardCharsets.UTF_16BE;
- } else if (bytes.length > 1 && bytes[0] == (byte)0xFF && bytes[1] == (byte)0xFE) {
+ } else if (bytes.length > 1 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE) {
start = 2;
cs = StandardCharsets.UTF_16LE;
- } else if (bytes.length > 2 && bytes[0] == (byte)0xEF && bytes[1] == (byte)0xBB && bytes[2] == (byte)0xBF) {
+ } else if (bytes.length > 2 && bytes[0] == (byte) 0xEF && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF) {
start = 3;
cs = StandardCharsets.UTF_8;
- } else if (bytes.length > 3 && bytes[0] == (byte)0xFF && bytes[1] == (byte)0xFE && bytes[2] == 0 && bytes[3] == 0) {
+ } else if (bytes.length > 3 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE && bytes[2] == 0 && bytes[3] == 0) {
start = 4;
cs = Charset.forName("UTF-32LE");
- } else if (bytes.length > 3 && bytes[0] == 0 && bytes[1] == 0 && bytes[2] == (byte)0xFE && bytes[3] == (byte)0xFF) {
+ } else if (bytes.length > 3 && bytes[0] == 0 && bytes[1] == 0 && bytes[2] == (byte) 0xFE && bytes[3] == (byte) 0xFF) {
start = 4;
cs = Charset.forName("UTF-32BE");
}
@@ -536,7 +816,7 @@
}
static byte[] readBytes(final InputStream is) throws IOException {
- final byte[] arr = new byte[BUFSIZE];
+ final byte[] arr = new byte[BUF_SIZE];
try {
try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
int numBytes;
--- a/nashorn/src/jdk/nashorn/tools/Shell.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/src/jdk/nashorn/tools/Shell.java Fri Apr 25 16:34:17 2014 +0200
@@ -25,6 +25,8 @@
package jdk.nashorn.tools;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
@@ -50,7 +52,6 @@
import jdk.nashorn.internal.runtime.Property;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.ScriptFunction;
-import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.Source;
import jdk.nashorn.internal.runtime.options.Options;
@@ -244,7 +245,7 @@
// For each file on the command line.
for (final String fileName : files) {
- final FunctionNode functionNode = new Parser(env, new Source(fileName, new File(fileName)), errors).parse();
+ final FunctionNode functionNode = new Parser(env, sourceFor(fileName, new File(fileName)), errors).parse();
if (errors.getNumberOfErrors() != 0) {
return COMPILATION_ERROR;
@@ -302,7 +303,7 @@
}
final File file = new File(fileName);
- final ScriptFunction script = context.compileScript(new Source(fileName, file.toURI().toURL()), global);
+ final ScriptFunction script = context.compileScript(sourceFor(fileName, file), global);
if (script == null || errors.getNumberOfErrors() != 0) {
return COMPILATION_ERROR;
}
@@ -405,7 +406,7 @@
// initialize with "shell.js" script
try {
- final Source source = new Source("<shell.js>", Shell.class.getResource("resources/shell.js"));
+ final Source source = sourceFor("<shell.js>", Shell.class.getResource("resources/shell.js"));
context.eval(global, source.getString(), global, "<shell.js>", false);
} catch (final Exception e) {
err.println(e);
--- a/nashorn/test/script/trusted/JDK-8006529.js Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/test/script/trusted/JDK-8006529.js Fri Apr 25 16:34:17 2014 +0200
@@ -113,7 +113,7 @@
var getContextMethod = Context.class.getMethod("getContext")
var getEnvMethod = Context.class.getMethod("getEnv")
-var SourceConstructor = Source.class.getConstructor(java.lang.String.class, java.lang.String.class)
+var sourceForMethod = Source.class.getMethod("sourceFor", java.lang.String.class, java.lang.String.class)
var ParserConstructor = Parser.class.getConstructor(ScriptEnvironment.class, Source.class, ErrorManager.class)
var CompilerConstructor = Compiler.class.getConstructor(ScriptEnvironment.class)
@@ -121,7 +121,7 @@
// source code, returns a jdk.nashorn.internal.ir.FunctionNode object
// representing it.
function compile(source) {
- var source = SourceConstructor.newInstance("<no name>", source);
+ var source = sourceForMethod.invoke(null, "<no name>", source);
var env = getEnvMethod.invoke(getContextMethod.invoke(null))
--- a/nashorn/test/src/jdk/nashorn/internal/codegen/CompilerTest.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/test/src/jdk/nashorn/internal/codegen/CompilerTest.java Fri Apr 25 16:34:17 2014 +0200
@@ -25,6 +25,9 @@
package jdk.nashorn.internal.codegen;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
+import static jdk.nashorn.internal.runtime.Source.readFully;
+
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -32,7 +35,6 @@
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ErrorManager;
import jdk.nashorn.internal.runtime.ScriptFunction;
-import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.Source;
import jdk.nashorn.internal.runtime.options.Options;
import org.testng.Assert;
@@ -151,7 +153,7 @@
final boolean globalChanged = (oldGlobal != global);
try {
- final char[] buffer = Source.readFully(file);
+ final char[] buffer = readFully(file);
boolean excluded = false;
if (filter != null) {
@@ -170,7 +172,7 @@
if (globalChanged) {
Context.setGlobal(global);
}
- final Source source = new Source(file.getAbsolutePath(), buffer);
+ final Source source = sourceFor(file.getAbsolutePath(), buffer);
final ScriptFunction script = context.compileScript(source, global);
if (script == null || context.getErrorManager().getNumberOfErrors() > 0) {
log("Compile failed: " + file.getAbsolutePath());
--- a/nashorn/test/src/jdk/nashorn/internal/parser/ParserTest.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/test/src/jdk/nashorn/internal/parser/ParserTest.java Fri Apr 25 16:34:17 2014 +0200
@@ -25,6 +25,9 @@
package jdk.nashorn.internal.parser;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
+import static jdk.nashorn.internal.runtime.Source.readFully;
+
import java.io.File;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ErrorManager;
@@ -131,7 +134,7 @@
}
try {
- final char[] buffer = Source.readFully(file);
+ final char[] buffer = readFully(file);
boolean excluded = false;
if (filter != null) {
final String content = new String(buffer);
@@ -153,7 +156,7 @@
}
};
errors.setLimit(0);
- final Source source = new Source(file.getAbsolutePath(), buffer);
+ final Source source = sourceFor(file.getAbsolutePath(), buffer);
new Parser(context.getEnv(), source, errors).parse();
if (errors.getNumberOfErrors() > 0) {
log("Parse failed: " + file.getAbsolutePath());
--- a/nashorn/test/src/jdk/nashorn/internal/runtime/ContextTest.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/test/src/jdk/nashorn/internal/runtime/ContextTest.java Fri Apr 25 16:34:17 2014 +0200
@@ -25,6 +25,7 @@
package jdk.nashorn.internal.runtime;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@@ -107,7 +108,7 @@
}
private Object eval(final Context cx, final String name, final String code) {
- final Source source = new Source(name, code);
+ final Source source = sourceFor(name, code);
final ScriptObject global = Context.getGlobal();
final ScriptFunction func = cx.compileScript(source, global);
return func != null ? ScriptRuntime.apply(func, global) : null;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/src/jdk/nashorn/internal/runtime/SourceTest.java Fri Apr 25 16:34:17 2014 +0200
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.runtime;
+
+import jdk.nashorn.api.scripting.URLReader;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Arrays;
+
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+/**
+ * Tests different Source representations.
+ */
+public class SourceTest {
+
+ final private static String SOURCE_NAME = "source.js";
+ final private static String SOURCE_STRING = "var x = 1;";
+ final private static char[] SOURCE_CHARS = SOURCE_STRING.toCharArray();
+ final private static String RESOURCE_PATH = "resources/load_test.js";
+ final private static File SOURCE_FILE = new File("build/test/classes/jdk/nashorn/internal/runtime/" + RESOURCE_PATH);
+ final private static URL SOURCE_URL = SourceTest.class.getResource(RESOURCE_PATH);
+
+
+ @Test
+ public void testStringSource() {
+ testSources(sourceFor(SOURCE_NAME, SOURCE_STRING), sourceFor(SOURCE_NAME, SOURCE_STRING));
+ testSources(sourceFor(SOURCE_NAME, SOURCE_STRING), sourceFor(SOURCE_NAME, SOURCE_CHARS));
+ }
+
+ @Test
+ public void testCharArraySource() {
+ testSources(sourceFor(SOURCE_NAME, SOURCE_CHARS), sourceFor(SOURCE_NAME, SOURCE_CHARS));
+ testSources(sourceFor(SOURCE_NAME, SOURCE_CHARS), sourceFor(SOURCE_NAME, SOURCE_STRING));
+ }
+
+ @Test
+ public void testURLSource() {
+ try {
+ testSources(sourceFor(SOURCE_NAME, SOURCE_URL), sourceFor(SOURCE_NAME, SOURCE_URL));
+ testSources(sourceFor(SOURCE_NAME, SOURCE_URL), sourceFor(SOURCE_NAME, new URLReader(SOURCE_URL)));
+
+ } catch (final IOException e) {
+ fail(e.toString());
+ }
+ }
+
+ @Test
+ public void testURLReaderSource() {
+ try {
+ System.err.println(SourceTest.class.getResource(""));
+ testSources(sourceFor(SOURCE_NAME, new URLReader(SOURCE_URL)), sourceFor(SOURCE_NAME, new URLReader(SOURCE_URL)));
+ testSources(sourceFor(SOURCE_NAME, new URLReader(SOURCE_URL)), sourceFor(SOURCE_NAME, SOURCE_URL));
+ } catch (final IOException e) {
+ fail(e.toString());
+ }
+ }
+
+ @Test
+ public void testReaderSource() {
+ try {
+ testSources(sourceFor(SOURCE_NAME, getReader(RESOURCE_PATH)), sourceFor(SOURCE_NAME, getReader(RESOURCE_PATH)));
+ } catch (final IOException e) {
+ fail(e.toString());
+ }
+ }
+
+ @Test
+ public void testFileSource() {
+ try {
+ testSources(sourceFor(SOURCE_NAME, SOURCE_FILE), sourceFor(SOURCE_NAME, SOURCE_FILE));
+ } catch (final IOException e) {
+ fail(e.toString());
+ }
+ }
+
+ private Reader getReader(final String path) {
+ return new InputStreamReader(SourceTest.class.getResourceAsStream(path));
+ }
+
+ private void testSources(final Source source1, final Source source2) {
+ final char[] chars1 = source1.getContent();
+ final char[] chars2 = source2.getContent();
+ final String str1 = source1.getString();
+ final String str2 = source2.getString();
+ assertTrue(Arrays.equals(chars1, chars2));
+ assertEquals(str1, str2);
+ assertEquals(source1.hashCode(), source2.hashCode());
+ assertTrue(source1.equals(source2));
+ // Test for immutability
+ Arrays.fill(source1.getContent(), (char)0);
+ Arrays.fill(source2.getContent(), (char)1);
+ assertTrue(Arrays.equals(source1.getContent(), str1.toCharArray()));
+ assertTrue(Arrays.equals(source1.getContent(), chars1));
+ assertTrue(Arrays.equals(source1.getContent(), source2.getContent()));
+ }
+}
--- a/nashorn/test/src/jdk/nashorn/internal/test/framework/SharedContextEvaluator.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/test/src/jdk/nashorn/internal/test/framework/SharedContextEvaluator.java Fri Apr 25 16:34:17 2014 +0200
@@ -25,6 +25,7 @@
package jdk.nashorn.internal.test.framework;
+import static jdk.nashorn.internal.runtime.Source.sourceFor;
import static jdk.nashorn.tools.Shell.COMPILATION_ERROR;
import static jdk.nashorn.tools.Shell.RUNTIME_ERROR;
import static jdk.nashorn.tools.Shell.SUCCESS;
@@ -39,7 +40,6 @@
import jdk.nashorn.internal.runtime.ErrorManager;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptRuntime;
-import jdk.nashorn.internal.runtime.Source;
import jdk.nashorn.internal.runtime.options.Options;
/**
@@ -125,7 +125,7 @@
continue;
}
final File file = new File(fileName);
- ScriptFunction script = context.compileScript(new Source(fileName, file.toURI().toURL()), global);
+ ScriptFunction script = context.compileScript(sourceFor(fileName, file.toURI().toURL()), global);
if (script == null || errors.getNumberOfErrors() != 0) {
return COMPILATION_ERROR;
--- a/nashorn/test/src/jdk/nashorn/test/models/SourceHelper.java Fri Apr 25 14:20:07 2014 +0200
+++ b/nashorn/test/src/jdk/nashorn/test/models/SourceHelper.java Fri Apr 25 16:34:17 2014 +0200
@@ -46,7 +46,7 @@
}
public static String readFully(final URL url) throws IOException {
- return new Source(url.toString(), url).getString();
+ return Source.sourceFor(url.toString(), url).getString();
}
public static String readFully(final Reader reader) throws IOException {