Source.java revision 971:c93b6091b11e
1139825Simp/* 298542Smckusick * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 398542Smckusick * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 498542Smckusick * 598542Smckusick * This code is free software; you can redistribute it and/or modify it 698542Smckusick * under the terms of the GNU General Public License version 2 only, as 798542Smckusick * published by the Free Software Foundation. Oracle designates this 898542Smckusick * particular file as subject to the "Classpath" exception as provided 998542Smckusick * by Oracle in the LICENSE file that accompanied this code. 1098542Smckusick * 11136721Srwatson * This code is distributed in the hope that it will be useful, but WITHOUT 12136721Srwatson * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13136721Srwatson * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14136721Srwatson * version 2 for more details (a copy is included in the LICENSE file that 15136721Srwatson * accompanied this code). 16136721Srwatson * 17136721Srwatson * You should have received a copy of the GNU General Public License version 18136721Srwatson * 2 along with this work; if not, write to the Free Software Foundation, 19136721Srwatson * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20136721Srwatson * 21136721Srwatson * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22136721Srwatson * or visit www.oracle.com if you need additional information or have any 23136721Srwatson * questions. 24136721Srwatson */ 25136721Srwatson 26136721Srwatsonpackage jdk.nashorn.internal.runtime; 27136721Srwatson 28136721Srwatsonimport java.io.ByteArrayOutputStream; 29136721Srwatsonimport java.io.File; 30136721Srwatsonimport java.io.FileNotFoundException; 31136721Srwatsonimport java.io.IOError; 321541Srgrimesimport java.io.IOException; 331541Srgrimesimport java.io.InputStream; 341541Srgrimesimport java.io.Reader; 351541Srgrimesimport java.lang.ref.WeakReference; 361541Srgrimesimport java.net.MalformedURLException; 371541Srgrimesimport java.net.URISyntaxException; 381541Srgrimesimport java.net.URL; 391541Srgrimesimport java.net.URLConnection; 401541Srgrimesimport java.nio.charset.Charset; 411541Srgrimesimport java.nio.charset.StandardCharsets; 421541Srgrimesimport java.nio.file.Files; 431541Srgrimesimport java.nio.file.Path; 441541Srgrimesimport java.nio.file.Paths; 451541Srgrimesimport java.security.MessageDigest; 461541Srgrimesimport java.security.NoSuchAlgorithmException; 471541Srgrimesimport java.util.Arrays; 481541Srgrimesimport java.util.Base64; 491541Srgrimesimport java.util.Objects; 501541Srgrimesimport java.util.WeakHashMap; 511541Srgrimesimport jdk.nashorn.api.scripting.URLReader; 521541Srgrimesimport jdk.nashorn.internal.parser.Token; 531541Srgrimesimport jdk.nashorn.internal.runtime.logging.DebugLogger; 541541Srgrimesimport jdk.nashorn.internal.runtime.logging.Loggable; 551541Srgrimesimport jdk.nashorn.internal.runtime.logging.Logger; 561541Srgrimes/** 571541Srgrimes * Source objects track the origin of JavaScript entities. 581541Srgrimes */ 5922521Sdyson@Logger(name="source") 601541Srgrimespublic final class Source implements Loggable { 611541Srgrimes private static final int BUF_SIZE = 8 * 1024; 62116192Sobrien private static final Cache CACHE = new Cache(); 63116192Sobrien 64116192Sobrien // Message digest to file name encoder 6513260Swollman private final static Base64.Encoder BASE64 = Base64.getUrlEncoder().withoutPadding(); 6613260Swollman 671541Srgrimes /** 68224778Srwatson * Descriptive name of the source as supplied by the user. Used for error 691541Srgrimes * reporting to the user. For example, SyntaxError will use this to print message. 7060041Sphk * Used to implement __FILE__. Also used for SourceFile in .class for debugger usage. 711541Srgrimes */ 7250253Sbde private final String name; 73202113Smckusick 7474548Smckusick /** 75108524Salfred * Base directory the File or base part of the URL. Used to implement __DIR__. 76164033Srwatson * Used to load scripts relative to the 'directory' or 'base' URL of current script. 771541Srgrimes * This will be null when it can't be computed. 781541Srgrimes */ 791541Srgrimes private final String base; 8041124Sdg 81202113Smckusick /** Source content */ 8212911Sphk private final Data data; 831541Srgrimes 84216796Skib /** Cached hash code */ 851541Srgrimes private int hash; 86202113Smckusick 87202113Smckusick /** Base64-encoded SHA1 digest of this source object */ 88216796Skib private volatile byte[] digest; 89216796Skib 90202113Smckusick /** source URL set via //@ sourceURL or //# sourceURL directive */ 9159241Srwatson private String explicitURL; 921541Srgrimes 931541Srgrimes // Do *not* make this public, ever! Trusts the URL and content. 9441124Sdg private Source(final String name, final String base, final Data data) { 9530474Sphk this.name = name; 961541Srgrimes this.base = base; 971541Srgrimes this.data = data; 981541Srgrimes } 99216796Skib 1001541Srgrimes private static synchronized Source sourceFor(final String name, final String base, final URLData data) throws IOException { 101203763Smckusick try { 102207141Sjeff final Source newSource = new Source(name, base, data); 10312590Sbde final Source existingSource = CACHE.get(newSource); 104207141Sjeff if (existingSource != null) { 10598542Smckusick // Force any access errors 106207141Sjeff data.checkPermissionAndClose(); 107216796Skib return existingSource; 108216796Skib } 109216796Skib 110216796Skib // All sources in cache must be fully loaded 111216796Skib data.load(); 112173464Sobrien CACHE.put(newSource, newSource); 11398542Smckusick 11431352Sbde return newSource; 115207141Sjeff } catch (final RuntimeException e) { 116207141Sjeff final Throwable cause = e.getCause(); 11792728Salfred if (cause instanceof IOException) { 118203763Smckusick throw (IOException) cause; 119203763Smckusick } 12098542Smckusick throw e; 121207141Sjeff } 122207141Sjeff } 123207141Sjeff 12498542Smckusick private static class Cache extends WeakHashMap<Source, WeakReference<Source>> { 12598542Smckusick public Source get(final Source key) { 12698542Smckusick final WeakReference<Source> ref = super.get(key); 1271541Srgrimes return ref == null ? null : ref.get(); 1281541Srgrimes } 12996755Strhodes 1308876Srgrimes public void put(final Source key, final Source value) { 1311541Srgrimes assert !(value.data instanceof RawData); 1321541Srgrimes put(key, new WeakReference<>(value)); 1331541Srgrimes } 1341541Srgrimes } 1351541Srgrimes 1361541Srgrimes /* package-private */ 1371541Srgrimes DebuggerSupport.SourceInfo getSourceInfo() { 1381541Srgrimes return new DebuggerSupport.SourceInfo(getName(), data.hashCode(), data.url(), data.array()); 1391541Srgrimes } 140166051Smpp 1411541Srgrimes // Wrapper to manage lazy loading 1421541Srgrimes private static interface Data { 1431541Srgrimes 1441541Srgrimes URL url(); 1451541Srgrimes 1461541Srgrimes int length(); 1471549Srgrimes 148187790Srwatson long lastModified(); 14996506Sphk 15098542Smckusick char[] array(); 151187790Srwatson 1521541Srgrimes boolean isEvalCode(); 15398542Smckusick } 1541541Srgrimes 15596506Sphk private static class RawData implements Data { 156140704Sjeff private final char[] array; 15798542Smckusick private final boolean evalCode; 158203763Smckusick private int hash; 159151906Sps 160151906Sps private RawData(final char[] array, final boolean evalCode) { 161166924Sbrian this.array = Objects.requireNonNull(array); 1626357Sphk this.evalCode = evalCode; 1636357Sphk } 1646357Sphk 1658876Srgrimes private RawData(final String source, final boolean evalCode) { 1661541Srgrimes this.array = Objects.requireNonNull(source).toCharArray(); 1671541Srgrimes this.evalCode = evalCode; 168140704Sjeff } 169140704Sjeff 170173464Sobrien private RawData(final Reader reader) throws IOException { 1711541Srgrimes this(readFully(reader), false); 17250253Sbde } 17350253Sbde 17450253Sbde @Override 1751541Srgrimes public int hashCode() { 1761541Srgrimes int h = hash; 1771541Srgrimes if (h == 0) { 1787170Sdg h = hash = Arrays.hashCode(array) ^ (evalCode? 1 : 0); 179173464Sobrien } 18089637Smckusick return h; 18189637Smckusick } 182140704Sjeff 183140704Sjeff @Override 184140704Sjeff public boolean equals(final Object obj) { 185140704Sjeff if (this == obj) { 186140704Sjeff return true; 187140704Sjeff } 188140704Sjeff if (obj instanceof RawData) { 1891541Srgrimes final RawData other = (RawData)obj; 1901541Srgrimes return Arrays.equals(array, other.array) && evalCode == other.evalCode; 191170587Srwatson } 19229609Sphk return false; 1931541Srgrimes } 1941541Srgrimes 1951541Srgrimes @Override 1961541Srgrimes public String toString() { 1971541Srgrimes return new String(array()); 1981541Srgrimes } 1991541Srgrimes 200207141Sjeff @Override 2011541Srgrimes public URL url() { 202166924Sbrian return null; 203166924Sbrian } 204187790Srwatson 205187790Srwatson @Override 206187790Srwatson public int length() { 207187790Srwatson return array.length; 2081541Srgrimes } 2091541Srgrimes 2101541Srgrimes @Override 211166142Smpp public long lastModified() { 2121541Srgrimes return 0; 213140704Sjeff } 2141541Srgrimes 2151541Srgrimes @Override 2161541Srgrimes public char[] array() { 21798542Smckusick return array; 218140704Sjeff } 2191541Srgrimes 220222958Sjeff 22189637Smckusick @Override 222220374Smckusick public boolean isEvalCode() { 22389637Smckusick return evalCode; 22489637Smckusick } 225140704Sjeff } 226223114Smckusick 227151906Sps private static class URLData implements Data { 228151906Sps private final URL url; 229151906Sps protected final Charset cs; 230151906Sps private int hash; 2311541Srgrimes protected char[] array; 2321541Srgrimes protected int length; 2331541Srgrimes protected long lastModified; 2341541Srgrimes 2351541Srgrimes private URLData(final URL url, final Charset cs) { 2361541Srgrimes this.url = Objects.requireNonNull(url); 2371541Srgrimes this.cs = cs; 2381541Srgrimes } 2391541Srgrimes 2401541Srgrimes @Override 2411541Srgrimes public int hashCode() { 2421549Srgrimes int h = hash; 243187790Srwatson if (h == 0) { 24496506Sphk h = hash = url.hashCode(); 24598542Smckusick } 246100344Smckusick return h; 24798542Smckusick } 248187790Srwatson 2491541Srgrimes @Override 2501541Srgrimes public boolean equals(final Object other) { 2511541Srgrimes if (this == other) { 25289637Smckusick return true; 25389637Smckusick } 2541541Srgrimes if (!(other instanceof URLData)) { 255140704Sjeff return false; 256203763Smckusick } 257248521Skib 258100344Smckusick final URLData otherData = (URLData) other; 259151906Sps 260151906Sps if (url.equals(otherData.url)) { 261166924Sbrian // Make sure both have meta data loaded 2628876Srgrimes try { 2631541Srgrimes if (isDeferred()) { 26489637Smckusick // Data in cache is always loaded, and we only compare to cached data. 2651541Srgrimes assert !otherData.isDeferred(); 266140704Sjeff loadMeta(); 267140704Sjeff } else if (otherData.isDeferred()) { 268248521Skib otherData.loadMeta(); 269248521Skib } 270140704Sjeff } catch (final IOException e) { 271173464Sobrien throw new RuntimeException(e); 27289637Smckusick } 27362976Smckusick 2741541Srgrimes // Compare meta data 2751541Srgrimes return this.length == otherData.length && this.lastModified == otherData.lastModified; 2761541Srgrimes } 27750253Sbde return false; 27850253Sbde } 2798456Srgrimes 2801541Srgrimes @Override 2811541Srgrimes public String toString() { 2821541Srgrimes return new String(array()); 2837170Sdg } 284173464Sobrien 28589637Smckusick @Override 28689637Smckusick public URL url() { 287170587Srwatson return url; 288140704Sjeff } 2891541Srgrimes 290140704Sjeff @Override 291100344Smckusick public int length() { 29298687Smux return length; 29398542Smckusick } 29437555Sbde 2951541Srgrimes @Override 2961541Srgrimes public long lastModified() { 297140704Sjeff return lastModified; 2981541Srgrimes } 2991541Srgrimes 3001541Srgrimes @Override 301248521Skib public char[] array() { 3023487Sphk assert !isDeferred(); 3031541Srgrimes return array; 3041541Srgrimes } 3051541Srgrimes 3066864Sdg @Override 30798542Smckusick public boolean isEvalCode() { 30898542Smckusick return false; 3096864Sdg } 3106864Sdg 3116864Sdg boolean isDeferred() { 3128876Srgrimes return array == null; 3131541Srgrimes } 31498542Smckusick 3153487Sphk @SuppressWarnings("try") 3161541Srgrimes protected void checkPermissionAndClose() throws IOException { 3171541Srgrimes try (InputStream in = url.openStream()) { 3181541Srgrimes // empty 3191541Srgrimes } 3201541Srgrimes debug("permission checked for ", url); 3211541Srgrimes } 3221541Srgrimes 3231541Srgrimes protected void load() throws IOException { 324140704Sjeff if (array == null) { 32598542Smckusick final URLConnection c = url.openConnection(); 3263487Sphk try (InputStream in = c.getInputStream()) { 3271541Srgrimes array = cs == null ? readFully(in) : readFully(in, cs); 32823560Smpp length = array.length; 329166924Sbrian lastModified = c.getLastModified(); 330166924Sbrian debug("loaded content for ", url); 331187790Srwatson } 332187790Srwatson } 333187790Srwatson } 334187790Srwatson 3357399Sdg protected void loadMeta() throws IOException { 3361541Srgrimes if (length == 0 && lastModified == 0) { 337248521Skib final URLConnection c = url.openConnection(); 338192260Salc length = c.getContentLength(); 339192260Salc lastModified = c.getLastModified(); 3401541Srgrimes debug("loaded metadata for ", url); 3411541Srgrimes } 3421541Srgrimes } 3431541Srgrimes } 3441541Srgrimes 3451541Srgrimes private static class FileData extends URLData { 3461541Srgrimes private final File file; 3471541Srgrimes 3481541Srgrimes private FileData(final File file, final Charset cs) { 3491541Srgrimes super(getURLFromFile(file), cs); 3501541Srgrimes this.file = file; 3518876Srgrimes 3528876Srgrimes } 3531541Srgrimes 3541541Srgrimes @Override 3551541Srgrimes protected void checkPermissionAndClose() throws IOException { 3561541Srgrimes if (!file.canRead()) { 3571541Srgrimes throw new FileNotFoundException(file + " (Permission Denied)"); 3586993Sdg } 3591541Srgrimes debug("permission checked for ", file); 36058087Smckusick } 3611541Srgrimes 3621541Srgrimes @Override 3631541Srgrimes protected void loadMeta() { 3641541Srgrimes if (length == 0 && lastModified == 0) { 3651541Srgrimes length = (int) file.length(); 3661541Srgrimes lastModified = file.lastModified(); 3671541Srgrimes debug("loaded metadata for ", file); 3681541Srgrimes } 3691541Srgrimes } 3701541Srgrimes 3711541Srgrimes @Override 3721541Srgrimes protected void load() throws IOException { 3731541Srgrimes if (array == null) { 3741541Srgrimes array = cs == null ? readFully(file) : readFully(file, cs); 3751541Srgrimes length = array.length; 3761541Srgrimes lastModified = file.lastModified(); 3771541Srgrimes debug("loaded content for ", file); 3781541Srgrimes } 37958087Smckusick } 3801541Srgrimes } 3811541Srgrimes 3821541Srgrimes private static void debug(final Object... msg) { 3831541Srgrimes final DebugLogger logger = getLoggerStatic(); 3841541Srgrimes if (logger != null) { 3851541Srgrimes logger.info(msg); 38650253Sbde } 38750253Sbde } 3881541Srgrimes 3891541Srgrimes private char[] data() { 3901541Srgrimes return data.array(); 391207141Sjeff } 3921541Srgrimes 3931541Srgrimes /** 39489637Smckusick * Returns a Source instance 395140704Sjeff * 396223127Smckusick * @param name source name 397166924Sbrian * @param content contents as char array 398166924Sbrian * @param isEval does this represent code from 'eval' call? 399187790Srwatson * @return source instance 400187790Srwatson */ 401187790Srwatson public static Source sourceFor(final String name, final char[] content, final boolean isEval) { 402187790Srwatson return new Source(name, baseName(name), new RawData(content, isEval)); 4037399Sdg } 4041541Srgrimes 405248521Skib /** 406192260Salc * Returns a Source instance 407192260Salc * 4081541Srgrimes * @param name source name 4091541Srgrimes * @param content contents as char array 4101541Srgrimes * 4111541Srgrimes * @return source instance 412140704Sjeff */ 4131541Srgrimes public static Source sourceFor(final String name, final char[] content) { 4141541Srgrimes return sourceFor(name, content, false); 4151541Srgrimes } 41698542Smckusick 417140704Sjeff /** 4181541Srgrimes * Returns a Source instance 4191541Srgrimes * 4201541Srgrimes * @param name source name 4211541Srgrimes * @param content contents as string 4221541Srgrimes * @param isEval does this represent code from 'eval' call? 423222958Sjeff * @return source instance 42489637Smckusick */ 425140704Sjeff public static Source sourceFor(final String name, final String content, final boolean isEval) { 426203818Skib return new Source(name, baseName(name), new RawData(content, isEval)); 427140704Sjeff } 428203818Skib 429203818Skib /** 430140704Sjeff * Returns a Source instance 431222724Smckusick * 43289637Smckusick * @param name source name 43389637Smckusick * @param content contents as string 434140704Sjeff * @return source instance 435140704Sjeff */ 436140704Sjeff public static Source sourceFor(final String name, final String content) { 437223114Smckusick return sourceFor(name, content, false); 438151906Sps } 439151906Sps 440151906Sps /** 441151906Sps * Constructor 4421541Srgrimes * 4431541Srgrimes * @param name source name 4441541Srgrimes * @param url url from which source can be loaded 4451541Srgrimes * 4461541Srgrimes * @return source instance 4471541Srgrimes * 4481541Srgrimes * @throws IOException if source cannot be loaded 4491541Srgrimes */ 45098542Smckusick public static Source sourceFor(final String name, final URL url) throws IOException { 45198542Smckusick return sourceFor(name, url, null); 45298542Smckusick } 45398542Smckusick 45498542Smckusick /** 45598542Smckusick * Constructor 45698542Smckusick * 45798542Smckusick * @param name source name 4581541Srgrimes * @param url url from which source can be loaded 45974548Smckusick * @param cs Charset used to convert bytes to chars 46074548Smckusick * 46174548Smckusick * @return source instance 46212911Sphk * 46374548Smckusick * @throws IOException if source cannot be loaded 46422521Sdyson */ 46531352Sbde public static Source sourceFor(final String name, final URL url, final Charset cs) throws IOException { 46674548Smckusick return sourceFor(name, baseURL(url), new URLData(url, cs)); 46722521Sdyson } 46842351Sbde 46942351Sbde /** 47042351Sbde * Constructor 47131351Sbde * 4721541Srgrimes * @param name source name 4731541Srgrimes * @param file file from which source can be loaded 4741541Srgrimes * 4751541Srgrimes * @return source instance 4761541Srgrimes * 4771541Srgrimes * @throws IOException if source cannot be loaded 4781541Srgrimes */ 47998542Smckusick public static Source sourceFor(final String name, final File file) throws IOException { 48098542Smckusick return sourceFor(name, file, null); 48198542Smckusick } 482207141Sjeff 483207141Sjeff /** 484207141Sjeff * Constructor 485207141Sjeff * 486207141Sjeff * @param name source name 487207141Sjeff * @param file file from which source can be loaded 488207141Sjeff * @param cs Charset used to convert bytes to chars 489207141Sjeff * 49098542Smckusick * @return source instance 49198542Smckusick * 49298542Smckusick * @throws IOException if source cannot be loaded 49398542Smckusick */ 49498542Smckusick public static Source sourceFor(final String name, final File file, final Charset cs) throws IOException { 49598542Smckusick final File absFile = file.getAbsoluteFile(); 49698542Smckusick return sourceFor(name, dirName(absFile, null), new FileData(file, cs)); 49798542Smckusick } 49898542Smckusick 49998542Smckusick /** 50098542Smckusick * Returns an instance 50198542Smckusick * 5021541Srgrimes * @param name source name 5031541Srgrimes * @param reader reader from which source can be loaded 5041541Srgrimes * 5051541Srgrimes * @return source instance 50698542Smckusick * 5071541Srgrimes * @throws IOException if source cannot be loaded 508140704Sjeff */ 50998542Smckusick public static Source sourceFor(final String name, final Reader reader) throws IOException { 51098542Smckusick // Extract URL from URLReader to defer loading and reuse cached data if available. 51198542Smckusick if (reader instanceof URLReader) { 5121541Srgrimes final URLReader urlReader = (URLReader) reader; 51398542Smckusick return sourceFor(name, urlReader.getURL(), urlReader.getCharset()); 5141541Srgrimes } 5151541Srgrimes return new Source(name, baseName(name), new RawData(reader)); 5161541Srgrimes } 5171541Srgrimes 518140704Sjeff @Override 519254995Smckusick public boolean equals(final Object obj) { 520260828Smckusick if (this == obj) { 521254995Smckusick return true; 522254995Smckusick } 523254995Smckusick if (!(obj instanceof Source)) { 524254995Smckusick return false; 525260828Smckusick } 5261541Srgrimes final Source other = (Source) obj; 5271541Srgrimes return Objects.equals(name, other.name) && data.equals(other.data); 5281541Srgrimes } 5291541Srgrimes 5301541Srgrimes @Override 531173464Sobrien public int hashCode() { 53222521Sdyson int h = hash; 53322521Sdyson if (h == 0) { 53422521Sdyson h = hash = data.hashCode() ^ Objects.hashCode(name); 53522521Sdyson } 5361541Srgrimes return h; 5371541Srgrimes } 53822521Sdyson 53922521Sdyson /** 54022521Sdyson * Fetch source content. 54122521Sdyson * @return Source content. 54222521Sdyson */ 54322521Sdyson public String getString() { 5441541Srgrimes return data.toString(); 5451541Srgrimes } 546242520Smckusick 547242520Smckusick /** 548242520Smckusick * Get the user supplied name of this script. 549242520Smckusick * @return User supplied source name. 550242520Smckusick */ 551242520Smckusick public String getName() { 552242520Smckusick return name; 553242520Smckusick } 554242520Smckusick 555242520Smckusick /** 556242520Smckusick * Get the last modified time of this script. 557242520Smckusick * @return Last modified time. 5581541Srgrimes */ 5591541Srgrimes public long getLastModified() { 5601541Srgrimes return data.lastModified(); 5611541Srgrimes } 5621541Srgrimes 5631541Srgrimes /** 5641541Srgrimes * Get the "directory" part of the file or "base" of the URL. 5651541Srgrimes * @return base of file or URL. 5661541Srgrimes */ 5671541Srgrimes public String getBase() { 5681541Srgrimes return base; 5691541Srgrimes } 5701541Srgrimes 5711541Srgrimes /** 57298542Smckusick * Fetch a portion of source content. 5731541Srgrimes * @param start start index in source 5741541Srgrimes * @param len length of portion 5751541Srgrimes * @return Source content portion. 5761541Srgrimes */ 5771541Srgrimes public String getString(final int start, final int len) { 5781541Srgrimes return new String(data(), start, len); 5791541Srgrimes } 58098542Smckusick 5811541Srgrimes /** 5821541Srgrimes * Fetch a portion of source content associated with a token. 5831541Srgrimes * @param token Token descriptor. 5841541Srgrimes * @return Source content portion. 5851541Srgrimes */ 5861541Srgrimes public String getString(final long token) { 5871541Srgrimes final int start = Token.descPosition(token); 5881541Srgrimes final int len = Token.descLength(token); 589173464Sobrien return new String(data(), start, len); 590174126Skensmith } 591174126Skensmith 5921541Srgrimes /** 5931541Srgrimes * Returns the source URL of this script Source. Can be null if Source 5941541Srgrimes * was created from a String or a char[]. 5951541Srgrimes * 5961541Srgrimes * @return URL source or null 59798542Smckusick */ 5981541Srgrimes public URL getURL() { 5991541Srgrimes return data.url(); 600140704Sjeff } 601140704Sjeff 602140704Sjeff /** 603140704Sjeff * Get explicit source URL. 604140704Sjeff * @return URL set vial sourceURL directive 6051541Srgrimes */ 6061541Srgrimes public String getExplicitURL() { 60798542Smckusick return explicitURL; 608207141Sjeff } 609140704Sjeff 6101541Srgrimes /** 611140704Sjeff * Set explicit source URL. 6121541Srgrimes * @param explicitURL URL set via sourceURL directive 6131541Srgrimes */ 6141541Srgrimes public void setExplicitURL(final String explicitURL) { 6151541Srgrimes this.explicitURL = explicitURL; 6161541Srgrimes } 6171541Srgrimes 6181541Srgrimes /** 61922521Sdyson * Returns whether this source was submitted via 'eval' call or not. 62022521Sdyson * 621241011Smdf * @return true if this source represents code submitted via 'eval' 622241011Smdf */ 62398542Smckusick public boolean isEvalCode() { 62422521Sdyson return data.isEvalCode(); 6251541Srgrimes } 6261541Srgrimes 62734266Sjulian /** 6281541Srgrimes * Find the beginning of the line containing position. 62934266Sjulian * @param position Index to offending token. 63034266Sjulian * @return Index of first character of line. 631173464Sobrien */ 63222521Sdyson private int findBOLN(final int position) { 63322521Sdyson final char[] d = data(); 63422521Sdyson for (int i = position - 1; i > 0; i--) { 63522521Sdyson final char ch = d[i]; 6361541Srgrimes 6371541Srgrimes if (ch == '\n' || ch == '\r') { 63822521Sdyson return i + 1; 63922521Sdyson } 64022521Sdyson } 64122521Sdyson 64234266Sjulian return 0; 64398542Smckusick } 64434266Sjulian 64534266Sjulian /** 64634266Sjulian * Find the end of the line containing position. 64734266Sjulian * @param position Index to offending token. 64834266Sjulian * @return Index of last character of line. 64934266Sjulian */ 65034266Sjulian private int findEOLN(final int position) { 65134266Sjulian final char[] d = data(); 6521541Srgrimes final int length = d.length; 6531541Srgrimes for (int i = position; i < length; i++) { 6541541Srgrimes final char ch = d[i]; 6551541Srgrimes 6561541Srgrimes if (ch == '\n' || ch == '\r') { 6571541Srgrimes return i - 1; 6588876Srgrimes } 6591541Srgrimes } 6601541Srgrimes 6611541Srgrimes return length - 1; 6621541Srgrimes } 6631541Srgrimes 6641541Srgrimes /** 6651541Srgrimes * Return line number of character position. 6661541Srgrimes * 6671541Srgrimes * <p>This method can be expensive for large sources as it iterates through 66898542Smckusick * all characters up to {@code position}.</p> 6691541Srgrimes * 6701541Srgrimes * @param position Position of character in source content. 6711541Srgrimes * @return Line number. 6721541Srgrimes */ 6731541Srgrimes public int getLine(final int position) { 6741541Srgrimes final char[] d = data(); 67542374Sbde // Line count starts at 1. 676141526Sphk int line = 1; 6771541Srgrimes 67846568Speter for (int i = 0; i < position; i++) { 6791541Srgrimes final char ch = d[i]; 6801541Srgrimes // Works for both \n and \r\n. 6811541Srgrimes if (ch == '\n') { 6821541Srgrimes line++; 68346568Speter } 6841541Srgrimes } 6851541Srgrimes 6861541Srgrimes return line; 68722521Sdyson } 68822521Sdyson 68922521Sdyson /** 69022521Sdyson * Return column number of character position. 6911541Srgrimes * @param position Position of character in source content. 69234266Sjulian * @return Column number. 693140704Sjeff */ 69434266Sjulian public int getColumn(final int position) { 695223127Smckusick // TODO - column needs to account for tabs. 6961541Srgrimes return position - findBOLN(position); 697173464Sobrien } 69822521Sdyson 69922521Sdyson /** 70022521Sdyson * Return line text including character position. 70150305Ssheldonh * @param position Position of character in source content. 70250305Ssheldonh * @return Line text. 70322521Sdyson */ 70422521Sdyson public String getSourceLine(final int position) { 70522521Sdyson // Find end of previous line. 7061541Srgrimes final int first = findBOLN(position); 70722521Sdyson // Find end of this line. 70822521Sdyson final int last = findEOLN(position); 70922521Sdyson 71022521Sdyson return new String(data(), first, last - first + 1); 71122521Sdyson } 71222521Sdyson 7131541Srgrimes /** 7141541Srgrimes * Get the content of this source as a char array 7151541Srgrimes * @return content 7161541Srgrimes */ 7171541Srgrimes public char[] getContent() { 71898542Smckusick return data().clone(); 7191541Srgrimes } 7201541Srgrimes 7211541Srgrimes /** 7221541Srgrimes * Get the length in chars for this source 72398542Smckusick * @return length 72498542Smckusick */ 72598542Smckusick public int getLength() { 72698542Smckusick return data.length(); 72798542Smckusick } 72898542Smckusick 72998542Smckusick /** 73098542Smckusick * Read all of the source until end of file. Return it as char array 73198542Smckusick * 73298542Smckusick * @param reader reader opened to source stream 73398542Smckusick * @return source as content 73498542Smckusick * @throws IOException if source could not be read 73598542Smckusick */ 736140704Sjeff public static char[] readFully(final Reader reader) throws IOException { 73798542Smckusick final char[] arr = new char[BUF_SIZE]; 73898542Smckusick final StringBuilder sb = new StringBuilder(); 73998542Smckusick 74098542Smckusick try { 74198542Smckusick int numChars; 74298542Smckusick while ((numChars = reader.read(arr, 0, arr.length)) > 0) { 74398542Smckusick sb.append(arr, 0, numChars); 74498542Smckusick } 745140704Sjeff } finally { 746254995Smckusick reader.close(); 747260828Smckusick } 748254995Smckusick 749254995Smckusick return sb.toString().toCharArray(); 750254995Smckusick } 751254995Smckusick 752260828Smckusick /** 75398542Smckusick * Read all of the source until end of file. Return it as char array 75498542Smckusick * 75598542Smckusick * @param file source file 75698542Smckusick * @return source as content 75798542Smckusick * @throws IOException if source could not be read 758173464Sobrien */ 75998542Smckusick public static char[] readFully(final File file) throws IOException { 76098542Smckusick if (!file.isFile()) { 76198542Smckusick throw new IOException(file + " is not a file"); //TODO localize? 76298542Smckusick } 76398542Smckusick return byteToCharArray(Files.readAllBytes(file.toPath())); 76498542Smckusick } 76598542Smckusick 76698542Smckusick /** 76798542Smckusick * Read all of the source until end of file. Return it as char array 76898542Smckusick * 76998542Smckusick * @param file source file 77098542Smckusick * @param cs Charset used to convert bytes to chars 77198542Smckusick * @return source as content 77298542Smckusick * @throws IOException if source could not be read 773242520Smckusick */ 774242520Smckusick public static char[] readFully(final File file, final Charset cs) throws IOException { 775242520Smckusick if (!file.isFile()) { 776242520Smckusick throw new IOException(file + " is not a file"); //TODO localize? 777242520Smckusick } 778242520Smckusick 779242520Smckusick final byte[] buf = Files.readAllBytes(file.toPath()); 780242520Smckusick return (cs != null) ? new String(buf, cs).toCharArray() : byteToCharArray(buf); 781242520Smckusick } 782242520Smckusick 783242520Smckusick /** 784242520Smckusick * Read all of the source until end of stream from the given URL. Return it as char array 78598542Smckusick * 78698542Smckusick * @param url URL to read content from 78798542Smckusick * @return source as content 78898542Smckusick * @throws IOException if source could not be read 78998542Smckusick */ 79098542Smckusick public static char[] readFully(final URL url) throws IOException { 79198542Smckusick return readFully(url.openStream()); 79298542Smckusick } 79398542Smckusick 79498542Smckusick /** 79598542Smckusick * Read all of the source until end of file. Return it as char array 79698542Smckusick * 79798542Smckusick * @param url URL to read content from 79898542Smckusick * @param cs Charset used to convert bytes to chars 79998542Smckusick * @return source as content 80098542Smckusick * @throws IOException if source could not be read 80198542Smckusick */ 80298542Smckusick public static char[] readFully(final URL url, final Charset cs) throws IOException { 80398542Smckusick return readFully(url.openStream(), cs); 80498542Smckusick } 80598542Smckusick 80698542Smckusick /** 80798542Smckusick * Get a Base64-encoded SHA1 digest for this source. 80898542Smckusick * 80998542Smckusick * @return a Base64-encoded SHA1 digest for this source 81098542Smckusick */ 81198542Smckusick public String getDigest() { 81298542Smckusick return new String(getDigestBytes(), StandardCharsets.US_ASCII); 81398542Smckusick } 81498542Smckusick 81598542Smckusick private byte[] getDigestBytes() { 816173464Sobrien byte[] ldigest = digest; 817174126Skensmith if (ldigest == null) { 818174126Skensmith final char[] content = data(); 81998542Smckusick final byte[] bytes = new byte[content.length * 2]; 82098542Smckusick 82198542Smckusick for (int i = 0; i < content.length; i++) { 82298542Smckusick bytes[i * 2] = (byte) (content[i] & 0x00ff); 82398542Smckusick bytes[i * 2 + 1] = (byte) ((content[i] & 0xff00) >> 8); 82498542Smckusick } 82598542Smckusick 82698542Smckusick try { 827140704Sjeff final MessageDigest md = MessageDigest.getInstance("SHA-1"); 828140704Sjeff if (name != null) { 829140704Sjeff md.update(name.getBytes(StandardCharsets.UTF_8)); 830140704Sjeff } 831140704Sjeff if (base != null) { 83298542Smckusick md.update(base.getBytes(StandardCharsets.UTF_8)); 83398542Smckusick } 83498542Smckusick if (getURL() != null) { 835207141Sjeff md.update(getURL().toString().getBytes(StandardCharsets.UTF_8)); 836140704Sjeff } 83798542Smckusick digest = ldigest = BASE64.encode(md.digest(bytes)); 838140704Sjeff } catch (final NoSuchAlgorithmException e) { 83998542Smckusick throw new RuntimeException(e); 84098542Smckusick } 84198542Smckusick } 84298542Smckusick return ldigest; 84398542Smckusick } 84498542Smckusick 84598542Smckusick /** 84698542Smckusick * Get the base url. This is currently used for testing only 84798542Smckusick * @param url a URL 848103594Sobrien * @return base URL for url 84998542Smckusick */ 85098542Smckusick public static String baseURL(final URL url) { 85198542Smckusick if (url.getProtocol().equals("file")) { 85298542Smckusick try { 85398542Smckusick final Path path = Paths.get(url.toURI()); 85498542Smckusick final Path parent = path.getParent(); 85598542Smckusick return (parent != null) ? (parent + File.separator) : null; 85698542Smckusick } catch (final SecurityException | URISyntaxException | IOError e) { 857173464Sobrien return null; 85898542Smckusick } 85998542Smckusick } 86098542Smckusick 86198542Smckusick // FIXME: is there a better way to find 'base' URL of a given URL? 86298542Smckusick String path = url.getPath(); 86398542Smckusick if (path.isEmpty()) { 86498542Smckusick return null; 86598542Smckusick } 866103594Sobrien path = path.substring(0, path.lastIndexOf('/') + 1); 86798542Smckusick final int port = url.getPort(); 86898542Smckusick try { 86998542Smckusick return new URL(url.getProtocol(), url.getHost(), port, path).toString(); 87098542Smckusick } catch (final MalformedURLException e) { 87198542Smckusick return null; 87298542Smckusick } 87398542Smckusick } 87498542Smckusick 87598542Smckusick private static String dirName(final File file, final String DEFAULT_BASE_NAME) { 87698542Smckusick final String res = file.getParent(); 87798542Smckusick return (res != null) ? (res + File.separator) : DEFAULT_BASE_NAME; 87898542Smckusick } 87998542Smckusick 88098542Smckusick // fake directory like name 88198542Smckusick private static String baseName(final String name) { 88298542Smckusick int idx = name.lastIndexOf('/'); 88398542Smckusick if (idx == -1) { 88498542Smckusick idx = name.lastIndexOf('\\'); 88598542Smckusick } 88698542Smckusick return (idx != -1) ? name.substring(0, idx + 1) : null; 88798542Smckusick } 88898542Smckusick 88998542Smckusick private static char[] readFully(final InputStream is, final Charset cs) throws IOException { 89098542Smckusick return (cs != null) ? new String(readBytes(is), cs).toCharArray() : readFully(is); 89198542Smckusick } 89298542Smckusick 89398542Smckusick private static char[] readFully(final InputStream is) throws IOException { 89498542Smckusick return byteToCharArray(readBytes(is)); 89598542Smckusick } 89698542Smckusick 89798542Smckusick private static char[] byteToCharArray(final byte[] bytes) { 89898542Smckusick Charset cs = StandardCharsets.UTF_8; 89998542Smckusick int start = 0; 90098542Smckusick // BOM detection. 90198542Smckusick if (bytes.length > 1 && bytes[0] == (byte) 0xFE && bytes[1] == (byte) 0xFF) { 902141526Sphk start = 2; 90398542Smckusick cs = StandardCharsets.UTF_16BE; 90498542Smckusick } else if (bytes.length > 1 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE) { 90598542Smckusick start = 2; 90698542Smckusick cs = StandardCharsets.UTF_16LE; 90798542Smckusick } else if (bytes.length > 2 && bytes[0] == (byte) 0xEF && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF) { 90898542Smckusick start = 3; 90998542Smckusick cs = StandardCharsets.UTF_8; 91098542Smckusick } else if (bytes.length > 3 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE && bytes[2] == 0 && bytes[3] == 0) { 91198542Smckusick start = 4; 91298542Smckusick cs = Charset.forName("UTF-32LE"); 91398542Smckusick } else if (bytes.length > 3 && bytes[0] == 0 && bytes[1] == 0 && bytes[2] == (byte) 0xFE && bytes[3] == (byte) 0xFF) { 91498542Smckusick start = 4; 91598542Smckusick cs = Charset.forName("UTF-32BE"); 91698542Smckusick } 91798542Smckusick 91898542Smckusick return new String(bytes, start, bytes.length - start, cs).toCharArray(); 919140704Sjeff } 92098542Smckusick 921223127Smckusick static byte[] readBytes(final InputStream is) throws IOException { 92298542Smckusick final byte[] arr = new byte[BUF_SIZE]; 923173464Sobrien try { 92498542Smckusick try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { 92598542Smckusick int numBytes; 92698542Smckusick while ((numBytes = is.read(arr, 0, arr.length)) > 0) { 92798542Smckusick buf.write(arr, 0, numBytes); 92898542Smckusick } 92998542Smckusick return buf.toByteArray(); 93099590Sbde } 93198542Smckusick } finally { 93298542Smckusick is.close(); 93398542Smckusick } 93498542Smckusick } 93598542Smckusick 93698542Smckusick @Override 93798542Smckusick public String toString() { 93898542Smckusick return getName(); 93998542Smckusick } 94098542Smckusick 94198542Smckusick private static URL getURLFromFile(final File file) { 94298542Smckusick try { 94398542Smckusick return file.toURI().toURL(); 94498542Smckusick } catch (final SecurityException | MalformedURLException ignored) { 94598542Smckusick return null; 94698542Smckusick } 94798542Smckusick } 94898542Smckusick 9491541Srgrimes private static DebugLogger getLoggerStatic() { 95096755Strhodes final Context context = Context.getContextTrustedOrNull(); 9518876Srgrimes return context == null ? null : context.getLogger(Source.class); 9521541Srgrimes } 9531541Srgrimes 9541541Srgrimes @Override 9551541Srgrimes public DebugLogger initLogger(final Context context) { 9561541Srgrimes return context.getLogger(this.getClass()); 9571541Srgrimes } 958166051Smpp 9591541Srgrimes @Override 9601541Srgrimes public DebugLogger getLogger() { 9611541Srgrimes return initLogger(Context.getContextTrusted()); 9621541Srgrimes } 9631541Srgrimes} 9641549Srgrimes