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