Source.java revision 991:46af7f9765dc
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 /** 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} 969