Source.java revision 953:221a84ef44c0
1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.runtime;
27
28import java.io.ByteArrayOutputStream;
29import java.io.File;
30import java.io.FileNotFoundException;
31import java.io.IOError;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.Reader;
35import java.lang.ref.WeakReference;
36import java.net.MalformedURLException;
37import java.net.URISyntaxException;
38import java.net.URL;
39import java.net.URLConnection;
40import java.nio.charset.Charset;
41import java.nio.charset.StandardCharsets;
42import java.nio.file.Files;
43import java.nio.file.Path;
44import java.nio.file.Paths;
45import java.security.MessageDigest;
46import java.security.NoSuchAlgorithmException;
47import java.util.Arrays;
48import java.util.Base64;
49import java.util.Objects;
50import java.util.WeakHashMap;
51import jdk.nashorn.api.scripting.URLReader;
52import jdk.nashorn.internal.parser.Token;
53import jdk.nashorn.internal.runtime.logging.DebugLogger;
54import jdk.nashorn.internal.runtime.logging.Loggable;
55import jdk.nashorn.internal.runtime.logging.Logger;
56/**
57 * Source objects track the origin of JavaScript entities.
58 */
59@Logger(name="source")
60public final class Source implements Loggable {
61    private static final int BUF_SIZE = 8 * 1024;
62    private static final Cache CACHE = new Cache();
63
64    // Message digest to file name encoder
65    private final static Base64.Encoder BASE64 = Base64.getUrlEncoder().withoutPadding();
66
67    /**
68     * Descriptive name of the source as supplied by the user. Used for error
69     * reporting to the user. For example, SyntaxError will use this to print message.
70     * Used to implement __FILE__. Also used for SourceFile in .class for debugger usage.
71     */
72    private final String name;
73
74    /**
75     * Base directory the File or base part of the URL. Used to implement __DIR__.
76     * Used to load scripts relative to the 'directory' or 'base' URL of current script.
77     * This will be null when it can't be computed.
78     */
79    private final String base;
80
81    /** Source content */
82    private final Data data;
83
84    /** Cached hash code */
85    private int hash;
86
87    /** Base64-encoded SHA1 digest of this source object */
88    private volatile byte[] digest;
89
90    // Do *not* make this public, ever! Trusts the URL and content.
91    private Source(final String name, final String base, final Data data) {
92        this.name = name;
93        this.base = base;
94        this.data = data;
95    }
96
97    private static synchronized Source sourceFor(final String name, final String base, final URLData data) throws IOException {
98        try {
99            final Source newSource = new Source(name, base, data);
100            final Source existingSource = CACHE.get(newSource);
101            if (existingSource != null) {
102                // Force any access errors
103                data.checkPermissionAndClose();
104                return existingSource;
105            }
106
107            // All sources in cache must be fully loaded
108            data.load();
109            CACHE.put(newSource, newSource);
110
111            return newSource;
112        } catch (final RuntimeException e) {
113            final Throwable cause = e.getCause();
114            if (cause instanceof IOException) {
115                throw (IOException) cause;
116            }
117            throw e;
118        }
119    }
120
121    private static class Cache extends WeakHashMap<Source, WeakReference<Source>> {
122        public Source get(final Source key) {
123            final WeakReference<Source> ref = super.get(key);
124            return ref == null ? null : ref.get();
125        }
126
127        public void put(final Source key, final Source value) {
128            assert !(value.data instanceof RawData);
129            put(key, new WeakReference<>(value));
130        }
131    }
132
133    /* package-private */
134    DebuggerSupport.SourceInfo getSourceInfo() {
135        return new DebuggerSupport.SourceInfo(getName(), data.hashCode(),  data.url(), data.array());
136    }
137
138    // Wrapper to manage lazy loading
139    private static interface Data {
140
141        URL url();
142
143        int length();
144
145        long lastModified();
146
147        char[] array();
148
149        boolean isEvalCode();
150    }
151
152    private static class RawData implements Data {
153        private final char[] array;
154        private final boolean evalCode;
155        private int hash;
156
157        private RawData(final char[] array, final boolean evalCode) {
158            this.array = Objects.requireNonNull(array);
159            this.evalCode = evalCode;
160        }
161
162        private RawData(final String source, final boolean evalCode) {
163            this.array = Objects.requireNonNull(source).toCharArray();
164            this.evalCode = evalCode;
165        }
166
167        private RawData(final Reader reader) throws IOException {
168            this(readFully(reader), false);
169        }
170
171        @Override
172        public int hashCode() {
173            int h = hash;
174            if (h == 0) {
175                h = hash = Arrays.hashCode(array) ^ (evalCode? 1 : 0);
176            }
177            return h;
178        }
179
180        @Override
181        public boolean equals(final Object obj) {
182            if (this == obj) {
183                return true;
184            }
185            if (obj instanceof RawData) {
186                final RawData other = (RawData)obj;
187                return Arrays.equals(array, other.array) && evalCode == other.evalCode;
188            }
189            return false;
190        }
191
192        @Override
193        public String toString() {
194            return new String(array());
195        }
196
197        @Override
198        public URL url() {
199            return null;
200        }
201
202        @Override
203        public int length() {
204            return array.length;
205        }
206
207        @Override
208        public long lastModified() {
209            return 0;
210        }
211
212        @Override
213        public char[] array() {
214            return array;
215        }
216
217
218        @Override
219        public boolean isEvalCode() {
220            return evalCode;
221        }
222    }
223
224    private static class URLData implements Data {
225        private final URL url;
226        protected final Charset cs;
227        private int hash;
228        protected char[] array;
229        protected int length;
230        protected long lastModified;
231
232        private URLData(final URL url, final Charset cs) {
233            this.url = Objects.requireNonNull(url);
234            this.cs = cs;
235        }
236
237        @Override
238        public int hashCode() {
239            int h = hash;
240            if (h == 0) {
241                h = hash = url.hashCode();
242            }
243            return h;
244        }
245
246        @Override
247        public boolean equals(final Object other) {
248            if (this == other) {
249                return true;
250            }
251            if (!(other instanceof URLData)) {
252                return false;
253            }
254
255            final URLData otherData = (URLData) other;
256
257            if (url.equals(otherData.url)) {
258                // Make sure both have meta data loaded
259                try {
260                    if (isDeferred()) {
261                        // Data in cache is always loaded, and we only compare to cached data.
262                        assert !otherData.isDeferred();
263                        loadMeta();
264                    } else if (otherData.isDeferred()) {
265                        otherData.loadMeta();
266                    }
267                } catch (final IOException e) {
268                    throw new RuntimeException(e);
269                }
270
271                // Compare meta data
272                return this.length == otherData.length && this.lastModified == otherData.lastModified;
273            }
274            return false;
275        }
276
277        @Override
278        public String toString() {
279            return new String(array());
280        }
281
282        @Override
283        public URL url() {
284            return url;
285        }
286
287        @Override
288        public int length() {
289            return length;
290        }
291
292        @Override
293        public long lastModified() {
294            return lastModified;
295        }
296
297        @Override
298        public char[] array() {
299            assert !isDeferred();
300            return array;
301        }
302
303        @Override
304        public boolean isEvalCode() {
305            return false;
306        }
307
308        boolean isDeferred() {
309            return array == null;
310        }
311
312        @SuppressWarnings("try")
313        protected void checkPermissionAndClose() throws IOException {
314            try (InputStream in = url.openStream()) {
315                // empty
316            }
317            debug("permission checked for ", url);
318        }
319
320        protected void load() throws IOException {
321            if (array == null) {
322                final URLConnection c = url.openConnection();
323                try (InputStream in = c.getInputStream()) {
324                    array = cs == null ? readFully(in) : readFully(in, cs);
325                    length = array.length;
326                    lastModified = c.getLastModified();
327                    debug("loaded content for ", url);
328                }
329            }
330        }
331
332        protected void loadMeta() throws IOException {
333            if (length == 0 && lastModified == 0) {
334                final URLConnection c = url.openConnection();
335                length = c.getContentLength();
336                lastModified = c.getLastModified();
337                debug("loaded metadata for ", url);
338            }
339        }
340    }
341
342    private static class FileData extends URLData {
343        private final File file;
344
345        private FileData(final File file, final Charset cs) {
346            super(getURLFromFile(file), cs);
347            this.file = file;
348
349        }
350
351        @Override
352        protected void checkPermissionAndClose() throws IOException {
353            if (!file.canRead()) {
354                throw new FileNotFoundException(file + " (Permission Denied)");
355            }
356            debug("permission checked for ", file);
357        }
358
359        @Override
360        protected void loadMeta() {
361            if (length == 0 && lastModified == 0) {
362                length = (int) file.length();
363                lastModified = file.lastModified();
364                debug("loaded metadata for ", file);
365            }
366        }
367
368        @Override
369        protected void load() throws IOException {
370            if (array == null) {
371                array = cs == null ? readFully(file) : readFully(file, cs);
372                length = array.length;
373                lastModified = file.lastModified();
374                debug("loaded content for ", file);
375            }
376        }
377    }
378
379    private static void debug(final Object... msg) {
380        final DebugLogger logger = getLoggerStatic();
381        if (logger != null) {
382            logger.info(msg);
383        }
384    }
385
386    private char[] data() {
387        return data.array();
388    }
389
390    /**
391     * Returns a Source instance
392     *
393     * @param name    source name
394     * @param content contents as char array
395     * @param isEval does this represent code from 'eval' call?
396     * @return source instance
397     */
398    public static Source sourceFor(final String name, final char[] content, final boolean isEval) {
399        return new Source(name, baseName(name), new RawData(content, isEval));
400    }
401
402    /**
403     * Returns a Source instance
404     *
405     * @param name    source name
406     * @param content contents as char array
407     *
408     * @return source instance
409     */
410    public static Source sourceFor(final String name, final char[] content) {
411        return sourceFor(name, content, false);
412    }
413
414    /**
415     * Returns a Source instance
416     *
417     * @param name    source name
418     * @param content contents as string
419     * @param isEval does this represent code from 'eval' call?
420     * @return source instance
421     */
422    public static Source sourceFor(final String name, final String content, final boolean isEval) {
423        return new Source(name, baseName(name), new RawData(content, isEval));
424    }
425
426    /**
427     * Returns a Source instance
428     *
429     * @param name    source name
430     * @param content contents as string
431     * @return source instance
432     */
433    public static Source sourceFor(final String name, final String content) {
434        return sourceFor(name, content, false);
435    }
436
437    /**
438     * Constructor
439     *
440     * @param name  source name
441     * @param url   url from which source can be loaded
442     *
443     * @return source instance
444     *
445     * @throws IOException if source cannot be loaded
446     */
447    public static Source sourceFor(final String name, final URL url) throws IOException {
448        return sourceFor(name, url, null);
449    }
450
451    /**
452     * Constructor
453     *
454     * @param name  source name
455     * @param url   url from which source can be loaded
456     * @param cs    Charset used to convert bytes to chars
457     *
458     * @return source instance
459     *
460     * @throws IOException if source cannot be loaded
461     */
462    public static Source sourceFor(final String name, final URL url, final Charset cs) throws IOException {
463        return sourceFor(name, baseURL(url), new URLData(url, cs));
464    }
465
466    /**
467     * Constructor
468     *
469     * @param name  source name
470     * @param file  file from which source can be loaded
471     *
472     * @return source instance
473     *
474     * @throws IOException if source cannot be loaded
475     */
476    public static Source sourceFor(final String name, final File file) throws IOException {
477        return sourceFor(name, file, null);
478    }
479
480    /**
481     * Constructor
482     *
483     * @param name  source name
484     * @param file  file from which source can be loaded
485     * @param cs    Charset used to convert bytes to chars
486     *
487     * @return source instance
488     *
489     * @throws IOException if source cannot be loaded
490     */
491    public static Source sourceFor(final String name, final File file, final Charset cs) throws IOException {
492        final File absFile = file.getAbsoluteFile();
493        return sourceFor(name, dirName(absFile, null), new FileData(file, cs));
494    }
495
496    /**
497     * Returns an instance
498     *
499     * @param name source name
500     * @param reader reader from which source can be loaded
501     *
502     * @return source instance
503     *
504     * @throws IOException if source cannot be loaded
505     */
506    public static Source sourceFor(final String name, final Reader reader) throws IOException {
507        // Extract URL from URLReader to defer loading and reuse cached data if available.
508        if (reader instanceof URLReader) {
509            final URLReader urlReader = (URLReader) reader;
510            return sourceFor(name, urlReader.getURL(), urlReader.getCharset());
511        }
512        return new Source(name, baseName(name), new RawData(reader));
513    }
514
515    @Override
516    public boolean equals(final Object obj) {
517        if (this == obj) {
518            return true;
519        }
520        if (!(obj instanceof Source)) {
521            return false;
522        }
523        final Source other = (Source) obj;
524        return Objects.equals(name, other.name) && data.equals(other.data);
525    }
526
527    @Override
528    public int hashCode() {
529        int h = hash;
530        if (h == 0) {
531            h = hash = data.hashCode() ^ Objects.hashCode(name);
532        }
533        return h;
534    }
535
536    /**
537     * Fetch source content.
538     * @return Source content.
539     */
540    public String getString() {
541        return data.toString();
542    }
543
544    /**
545     * Get the user supplied name of this script.
546     * @return User supplied source name.
547     */
548    public String getName() {
549        return name;
550    }
551
552    /**
553     * Get the last modified time of this script.
554     * @return Last modified time.
555     */
556    public long getLastModified() {
557        return data.lastModified();
558    }
559
560    /**
561     * Get the "directory" part of the file or "base" of the URL.
562     * @return base of file or URL.
563     */
564    public String getBase() {
565        return base;
566    }
567
568    /**
569     * Fetch a portion of source content.
570     * @param start start index in source
571     * @param len length of portion
572     * @return Source content portion.
573     */
574    public String getString(final int start, final int len) {
575        return new String(data(), start, len);
576    }
577
578    /**
579     * Fetch a portion of source content associated with a token.
580     * @param token Token descriptor.
581     * @return Source content portion.
582     */
583    public String getString(final long token) {
584        final int start = Token.descPosition(token);
585        final int len = Token.descLength(token);
586        return new String(data(), start, len);
587    }
588
589    /**
590     * Returns the source URL of this script Source. Can be null if Source
591     * was created from a String or a char[].
592     *
593     * @return URL source or null
594     */
595    public URL getURL() {
596        return data.url();
597    }
598
599    /**
600     * Returns whether this source was submitted via 'eval' call or not.
601     *
602     * @return true if this source represents code submitted via 'eval'
603     */
604    public boolean isEvalCode() {
605        return data.isEvalCode();
606    }
607
608    /**
609     * Find the beginning of the line containing position.
610     * @param position Index to offending token.
611     * @return Index of first character of line.
612     */
613    private int findBOLN(final int position) {
614        final char[] d = data();
615        for (int i = position - 1; i > 0; i--) {
616            final char ch = d[i];
617
618            if (ch == '\n' || ch == '\r') {
619                return i + 1;
620            }
621        }
622
623        return 0;
624    }
625
626    /**
627     * Find the end of the line containing position.
628     * @param position Index to offending token.
629     * @return Index of last character of line.
630     */
631    private int findEOLN(final int position) {
632        final char[] d = data();
633        final int length = d.length;
634        for (int i = position; i < length; i++) {
635            final char ch = d[i];
636
637            if (ch == '\n' || ch == '\r') {
638                return i - 1;
639            }
640        }
641
642        return length - 1;
643    }
644
645    /**
646     * Return line number of character position.
647     *
648     * <p>This method can be expensive for large sources as it iterates through
649     * all characters up to {@code position}.</p>
650     *
651     * @param position Position of character in source content.
652     * @return Line number.
653     */
654    public int getLine(final int position) {
655        final char[] d = data();
656        // Line count starts at 1.
657        int line = 1;
658
659        for (int i = 0; i < position; i++) {
660            final char ch = d[i];
661            // Works for both \n and \r\n.
662            if (ch == '\n') {
663                line++;
664            }
665        }
666
667        return line;
668    }
669
670    /**
671     * Return column number of character position.
672     * @param position Position of character in source content.
673     * @return Column number.
674     */
675    public int getColumn(final int position) {
676        // TODO - column needs to account for tabs.
677        return position - findBOLN(position);
678    }
679
680    /**
681     * Return line text including character position.
682     * @param position Position of character in source content.
683     * @return Line text.
684     */
685    public String getSourceLine(final int position) {
686        // Find end of previous line.
687        final int first = findBOLN(position);
688        // Find end of this line.
689        final int last = findEOLN(position);
690
691        return new String(data(), first, last - first + 1);
692    }
693
694    /**
695     * Get the content of this source as a char array
696     * @return content
697     */
698    public char[] getContent() {
699        return data().clone();
700    }
701
702    /**
703     * Get the length in chars for this source
704     * @return length
705     */
706    public int getLength() {
707        return data.length();
708    }
709
710    /**
711     * Read all of the source until end of file. Return it as char array
712     *
713     * @param reader reader opened to source stream
714     * @return source as content
715     * @throws IOException if source could not be read
716     */
717    public static char[] readFully(final Reader reader) throws IOException {
718        final char[]        arr = new char[BUF_SIZE];
719        final StringBuilder sb  = new StringBuilder();
720
721        try {
722            int numChars;
723            while ((numChars = reader.read(arr, 0, arr.length)) > 0) {
724                sb.append(arr, 0, numChars);
725            }
726        } finally {
727            reader.close();
728        }
729
730        return sb.toString().toCharArray();
731    }
732
733    /**
734     * Read all of the source until end of file. Return it as char array
735     *
736     * @param file source file
737     * @return source as content
738     * @throws IOException if source could not be read
739     */
740    public static char[] readFully(final File file) throws IOException {
741        if (!file.isFile()) {
742            throw new IOException(file + " is not a file"); //TODO localize?
743        }
744        return byteToCharArray(Files.readAllBytes(file.toPath()));
745    }
746
747    /**
748     * Read all of the source until end of file. Return it as char array
749     *
750     * @param file source file
751     * @param cs Charset used to convert bytes to chars
752     * @return source as content
753     * @throws IOException if source could not be read
754     */
755    public static char[] readFully(final File file, final Charset cs) throws IOException {
756        if (!file.isFile()) {
757            throw new IOException(file + " is not a file"); //TODO localize?
758        }
759
760        final byte[] buf = Files.readAllBytes(file.toPath());
761        return (cs != null) ? new String(buf, cs).toCharArray() : byteToCharArray(buf);
762    }
763
764    /**
765     * Read all of the source until end of stream from the given URL. Return it as char array
766     *
767     * @param url URL to read content from
768     * @return source as content
769     * @throws IOException if source could not be read
770     */
771    public static char[] readFully(final URL url) throws IOException {
772        return readFully(url.openStream());
773    }
774
775    /**
776     * Read all of the source until end of file. Return it as char array
777     *
778     * @param url URL to read content from
779     * @param cs Charset used to convert bytes to chars
780     * @return source as content
781     * @throws IOException if source could not be read
782     */
783    public static char[] readFully(final URL url, final Charset cs) throws IOException {
784        return readFully(url.openStream(), cs);
785    }
786
787    /**
788     * Get a Base64-encoded SHA1 digest for this source.
789     *
790     * @return a Base64-encoded SHA1 digest for this source
791     */
792    public String getDigest() {
793        return new String(getDigestBytes(), StandardCharsets.US_ASCII);
794    }
795
796    private byte[] getDigestBytes() {
797        byte[] ldigest = digest;
798        if (ldigest == null) {
799            final char[] content = data();
800            final byte[] bytes = new byte[content.length * 2];
801
802            for (int i = 0; i < content.length; i++) {
803                bytes[i * 2]     = (byte)  (content[i] & 0x00ff);
804                bytes[i * 2 + 1] = (byte) ((content[i] & 0xff00) >> 8);
805            }
806
807            try {
808                final MessageDigest md = MessageDigest.getInstance("SHA-1");
809                if (name != null) {
810                    md.update(name.getBytes(StandardCharsets.UTF_8));
811                }
812                if (base != null) {
813                    md.update(base.getBytes(StandardCharsets.UTF_8));
814                }
815                if (getURL() != null) {
816                    md.update(getURL().toString().getBytes(StandardCharsets.UTF_8));
817                }
818                digest = ldigest = BASE64.encode(md.digest(bytes));
819            } catch (final NoSuchAlgorithmException e) {
820                throw new RuntimeException(e);
821            }
822        }
823        return ldigest;
824    }
825
826    /**
827     * Get the base url. This is currently used for testing only
828     * @param url a URL
829     * @return base URL for url
830     */
831    public static String baseURL(final URL url) {
832        if (url.getProtocol().equals("file")) {
833            try {
834                final Path path = Paths.get(url.toURI());
835                final Path parent = path.getParent();
836                return (parent != null) ? (parent + File.separator) : null;
837            } catch (final SecurityException | URISyntaxException | IOError e) {
838                return null;
839            }
840        }
841
842        // FIXME: is there a better way to find 'base' URL of a given URL?
843        String path = url.getPath();
844        if (path.isEmpty()) {
845            return null;
846        }
847        path = path.substring(0, path.lastIndexOf('/') + 1);
848        final int port = url.getPort();
849        try {
850            return new URL(url.getProtocol(), url.getHost(), port, path).toString();
851        } catch (final MalformedURLException e) {
852            return null;
853        }
854    }
855
856    private static String dirName(final File file, final String DEFAULT_BASE_NAME) {
857        final String res = file.getParent();
858        return (res != null) ? (res + File.separator) : DEFAULT_BASE_NAME;
859    }
860
861    // fake directory like name
862    private static String baseName(final String name) {
863        int idx = name.lastIndexOf('/');
864        if (idx == -1) {
865            idx = name.lastIndexOf('\\');
866        }
867        return (idx != -1) ? name.substring(0, idx + 1) : null;
868    }
869
870    private static char[] readFully(final InputStream is, final Charset cs) throws IOException {
871        return (cs != null) ? new String(readBytes(is), cs).toCharArray() : readFully(is);
872    }
873
874    private static char[] readFully(final InputStream is) throws IOException {
875        return byteToCharArray(readBytes(is));
876    }
877
878    private static char[] byteToCharArray(final byte[] bytes) {
879        Charset cs = StandardCharsets.UTF_8;
880        int start = 0;
881        // BOM detection.
882        if (bytes.length > 1 && bytes[0] == (byte) 0xFE && bytes[1] == (byte) 0xFF) {
883            start = 2;
884            cs = StandardCharsets.UTF_16BE;
885        } else if (bytes.length > 1 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE) {
886            start = 2;
887            cs = StandardCharsets.UTF_16LE;
888        } else if (bytes.length > 2 && bytes[0] == (byte) 0xEF && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF) {
889            start = 3;
890            cs = StandardCharsets.UTF_8;
891        } else if (bytes.length > 3 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE && bytes[2] == 0 && bytes[3] == 0) {
892            start = 4;
893            cs = Charset.forName("UTF-32LE");
894        } else if (bytes.length > 3 && bytes[0] == 0 && bytes[1] == 0 && bytes[2] == (byte) 0xFE && bytes[3] == (byte) 0xFF) {
895            start = 4;
896            cs = Charset.forName("UTF-32BE");
897        }
898
899        return new String(bytes, start, bytes.length - start, cs).toCharArray();
900    }
901
902    static byte[] readBytes(final InputStream is) throws IOException {
903        final byte[] arr = new byte[BUF_SIZE];
904        try {
905            try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
906                int numBytes;
907                while ((numBytes = is.read(arr, 0, arr.length)) > 0) {
908                    buf.write(arr, 0, numBytes);
909                }
910                return buf.toByteArray();
911            }
912        } finally {
913            is.close();
914        }
915    }
916
917    @Override
918    public String toString() {
919        return getName();
920    }
921
922    private static URL getURLFromFile(final File file) {
923        try {
924            return file.toURI().toURL();
925        } catch (final SecurityException | MalformedURLException ignored) {
926            return null;
927        }
928    }
929
930    private static DebugLogger getLoggerStatic() {
931        final Context context = Context.getContextTrustedOrNull();
932        return context == null ? null : context.getLogger(Source.class);
933    }
934
935    @Override
936    public DebugLogger initLogger(final Context context) {
937        return context.getLogger(this.getClass());
938    }
939
940    @Override
941    public DebugLogger getLogger() {
942        return initLogger(Context.getContextTrusted());
943    }
944}
945