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