1 /* 2 * Copyright 2009 Sun Microsystems, Inc. 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. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 26 package com.sun.tools.javac.nio; 27 28 29 import java.io.ByteArrayOutputStream; 30 import java.io.Closeable; 31 import java.io.File; 32 import java.io.FileNotFoundException; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.OutputStreamWriter; 36 import java.lang.ref.SoftReference; 37 import java.lang.reflect.Constructor; 38 import java.net.MalformedURLException; 39 import java.net.URL; 40 import java.net.URLClassLoader; 41 import java.nio.ByteBuffer; 42 import java.nio.CharBuffer; 43 import java.nio.charset.Charset; 44 import java.nio.charset.CharsetDecoder; 45 import java.nio.charset.CoderResult; 46 import java.nio.charset.CodingErrorAction; 47 import java.nio.charset.IllegalCharsetNameException; 48 import java.nio.charset.UnsupportedCharsetException; 49 import java.nio.file.DirectoryStream; 50 import java.nio.file.FileSystem; 51 import java.nio.file.FileSystems; 52 import java.nio.file.Path; 53 import java.nio.file.attribute.Attributes; 54 import java.nio.file.attribute.BasicFileAttributes; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.Collection; 58 import java.util.Collections; 59 import java.util.Deque; 60 import java.util.HashMap; 61 import java.util.Iterator; 62 import java.util.LinkedHashSet; 63 import java.util.LinkedList; 64 import java.util.Map; 65 import java.util.Set; 66 import javax.lang.model.SourceVersion; 67 import javax.tools.FileObject; 68 import javax.tools.JavaFileManager; 69 import javax.tools.JavaFileObject; 70 import javax.tools.JavaFileObject.Kind; 71 import javax.tools.StandardLocation; 72 73 import static javax.tools.StandardLocation.*; 74 75 import com.sun.tools.javac.code.Source; 76 import com.sun.tools.javac.file.CloseableURLClassLoader; 77 import com.sun.tools.javac.file.Paths; 78 import com.sun.tools.javac.main.JavacOption; 79 import com.sun.tools.javac.main.OptionName; 80 import com.sun.tools.javac.main.RecognizedOptions; 81 import com.sun.tools.javac.util.Context; 82 import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition; 83 import com.sun.tools.javac.util.List; 84 import com.sun.tools.javac.util.ListBuffer; 85 import com.sun.tools.javac.util.Log; 86 import com.sun.tools.javac.util.Options; 87 88 import static com.sun.tools.javac.main.OptionName.*; 89 90 /** 91 * <p><b>This is NOT part of any API supported by Sun Microsystems. If 92 * you write code that depends on this, you do so at your own risk. 93 * This code and its internal interfaces are subject to change or 94 * deletion without notice.</b> 95 */ 96 public class JavacPathFileManager implements PathFileManager { 97 protected FileSystem defaultFileSystem; 98 99 /** 100 * Create a JavacPathFileManager using a given context, optionally registering 101 * it as the JavaFileManager for that context. 102 */ 103 public JavacPathFileManager(Context context, boolean register, Charset charset) { 104 if (register) 105 context.put(JavaFileManager.class, this); 106 this.charset = charset; 107 byteBufferCache = new ByteBufferCache(); 108 pathsForLocation = new HashMap<Location, PathsForLocation>(); 109 fileSystems = new HashMap<Path,FileSystem>(); 110 setContext(context); 111 } 112 113 /** 114 * Set the context for JavacPathFileManager. 115 */ 116 void setContext(Context context) { 117 log = Log.instance(context); 118 options = Options.instance(context); 119 searchPaths = Paths.instance(context); 120 } 121 122 @Override 123 public FileSystem getDefaultFileSystem() { 124 if (defaultFileSystem == null) 125 defaultFileSystem = FileSystems.getDefault(); 126 return defaultFileSystem; 127 } 128 129 @Override 130 public void setDefaultFileSystem(FileSystem fs) { 131 defaultFileSystem = fs; 132 } 133 134 @Override 135 public void flush() throws IOException { 136 contentCache.clear(); 137 } 138 139 @Override 140 public void close() throws IOException { 141 for (FileSystem fs: fileSystems.values()) 142 fs.close(); 143 } 144 145 @Override 146 public ClassLoader getClassLoader(Location location) { 147 nullCheck(location); 148 Iterable<? extends Path> path = getLocation(location); 149 if (path == null) 150 return null; 151 ListBuffer<URL> lb = new ListBuffer<URL>(); 152 for (Path p: path) { 153 try { 154 lb.append(p.toUri().toURL()); 155 } catch (MalformedURLException e) { 156 throw new AssertionError(e); 157 } 158 } 159 160 return getClassLoader(lb.toArray(new URL[lb.size()])); 161 } 162 163 // <editor-fold defaultstate="collapsed" desc="Location handling"> 164 165 public boolean hasLocation(Location location) { 166 return (getLocation(location) != null); 167 } 168 169 public Iterable<? extends Path> getLocation(Location location) { 170 nullCheck(location); 171 lazyInitSearchPaths(); 172 PathsForLocation path = pathsForLocation.get(location); 173 if (path == null && !pathsForLocation.containsKey(location)) { 174 setDefaultForLocation(location); 175 path = pathsForLocation.get(location); 176 } 177 return path; 178 } 179 180 private Path getOutputLocation(Location location) { 181 Iterable<? extends Path> paths = getLocation(location); 182 return (paths == null ? null : paths.iterator().next()); 183 } 184 185 public void setLocation(Location location, Iterable<? extends Path> searchPath) 186 throws IOException 187 { 188 nullCheck(location); 189 lazyInitSearchPaths(); 190 if (searchPath == null) { 191 setDefaultForLocation(location); 192 } else { 193 if (location.isOutputLocation()) 194 checkOutputPath(searchPath); 195 PathsForLocation pl = new PathsForLocation(); 196 for (Path p: searchPath) 197 pl.add(p); // TODO -Xlint:path warn if path not found 198 pathsForLocation.put(location, pl); 199 } 200 } 201 202 private void checkOutputPath(Iterable<? extends Path> searchPath) throws IOException { 203 Iterator<? extends Path> pathIter = searchPath.iterator(); 204 if (!pathIter.hasNext()) 205 throw new IllegalArgumentException("empty path for directory"); 206 Path path = pathIter.next(); 207 if (pathIter.hasNext()) 208 throw new IllegalArgumentException("path too long for directory"); 209 if (!path.exists()) 210 throw new FileNotFoundException(path + ": does not exist"); 211 else if (!isDirectory(path)) 212 throw new IOException(path + ": not a directory"); 213 } 214 215 private void setDefaultForLocation(Location locn) { 216 Collection<File> files = null; 217 if (locn instanceof StandardLocation) { 218 switch ((StandardLocation) locn) { 219 case CLASS_PATH: 220 files = searchPaths.userClassPath(); 221 break; 222 case PLATFORM_CLASS_PATH: 223 files = searchPaths.bootClassPath(); 224 break; 225 case SOURCE_PATH: 226 files = searchPaths.sourcePath(); 227 break; 228 case CLASS_OUTPUT: { 229 String arg = options.get(D); 230 files = (arg == null ? null : Collections.singleton(new File(arg))); 231 break; 232 } 233 case SOURCE_OUTPUT: { 234 String arg = options.get(S); 235 files = (arg == null ? null : Collections.singleton(new File(arg))); 236 break; 237 } 238 } 239 } 240 241 PathsForLocation pl = new PathsForLocation(); 242 if (files != null) { 243 for (File f: files) 244 pl.add(f.toPath()); 245 } 246 pathsForLocation.put(locn, pl); 247 } 248 249 private void lazyInitSearchPaths() { 250 if (!inited) { 251 setDefaultForLocation(PLATFORM_CLASS_PATH); 252 setDefaultForLocation(CLASS_PATH); 253 setDefaultForLocation(SOURCE_PATH); 254 inited = true; 255 } 256 } 257 // where 258 private boolean inited = false; 259 260 private Map<Location, PathsForLocation> pathsForLocation; 261 private Paths searchPaths; 262 263 private static class PathsForLocation extends LinkedHashSet<Path> { 264 private static final long serialVersionUID = 6788510222394486733L; 265 } 266 267 // </editor-fold> 268 269 // <editor-fold defaultstate="collapsed" desc="FileObject handling"> 270 271 @Override 272 public Path getPath(FileObject fo) { 273 fo.getClass(); // null check 274 if (!(fo instanceof PathFileObject)) 275 throw new IllegalArgumentException(); 276 return ((PathFileObject) fo).getPath(); 277 } 278 279 @Override 280 public boolean isSameFile(FileObject a, FileObject b) { 281 nullCheck(a); 282 nullCheck(b); 283 if (!(a instanceof PathFileObject)) 284 throw new IllegalArgumentException("Not supported: " + a); 285 if (!(b instanceof PathFileObject)) 286 throw new IllegalArgumentException("Not supported: " + b); 287 return ((PathFileObject) a).isSameFile((PathFileObject) b); 288 } 289 290 public Iterable<JavaFileObject> list(Location location, 291 String packageName, Set<Kind> kinds, boolean recurse) 292 throws IOException { 293 // validatePackageName(packageName); 294 nullCheck(packageName); 295 nullCheck(kinds); 296 297 Iterable<? extends Path> paths = getLocation(location); 298 if (paths == null) 299 return List.nil(); 300 ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>(); 301 302 for (Path path : paths) 303 list(path, packageName, kinds, recurse, results); 304 305 return results.toList(); 306 } 307 308 private void list(Path path, String packageName, Set<Kind> kinds, 309 boolean recurse, ListBuffer<JavaFileObject> results) 310 throws IOException { 311 if (!path.exists()) 312 return; 313 314 Path pathDir; 315 if (isDirectory(path)) 316 pathDir = path; 317 else { 318 FileSystem fs = getFileSystem(path); 319 if (fs == null) 320 return; 321 pathDir = fs.getRootDirectories().iterator().next(); 322 } 323 String sep = path.getFileSystem().getSeparator(); 324 Path packageDir = packageName.isEmpty() ? pathDir 325 : pathDir.resolve(packageName.replace(".", sep)); 326 if (!packageDir.exists()) 327 return; 328 329 // if (!caseMapCheck(d, subdirectory)) 330 // return; 331 332 Deque<Path> queue = new LinkedList<Path>(); 333 queue.add(packageDir); 334 335 Path dir; 336 while ((dir = queue.poll()) != null) { 337 DirectoryStream<Path> ds = dir.newDirectoryStream(); 338 try { 339 for (Path p: ds) { 340 String name = p.getName().toString(); 341 if (isDirectory(p)) { 342 if (recurse && SourceVersion.isIdentifier(name)) { 343 queue.add(p); 344 } 345 } else { 346 if (kinds.contains(getKind(name))) { 347 JavaFileObject fe = 348 PathFileObject.createDirectoryPathFileObject(this, p, pathDir); 349 results.append(fe); 350 } 351 } 352 } 353 } finally { 354 ds.close(); 355 } 356 } 357 } 358 359 @Override 360 public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths( 361 Iterable<? extends Path> paths) { 362 ArrayList<PathFileObject> result; 363 if (paths instanceof Collection<?>) 364 result = new ArrayList<PathFileObject>(((Collection<?>)paths).size()); 365 else 366 result = new ArrayList<PathFileObject>(); 367 for (Path p: paths) 368 result.add(PathFileObject.createSimplePathFileObject(this, nullCheck(p))); 369 return result; 370 } 371 372 @Override 373 public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) { 374 return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); 375 } 376 377 public JavaFileObject getJavaFileForInput(Location location, 378 String className, Kind kind) throws IOException { 379 return getFileForInput(location, getRelativePath(className, kind)); 380 } 381 382 public FileObject getFileForInput(Location location, 383 String packageName, String relativeName) throws IOException { 384 return getFileForInput(location, getRelativePath(packageName, relativeName)); 385 } 386 387 private JavaFileObject getFileForInput(Location location, String relativePath) 388 throws IOException { 389 for (Path p: getLocation(location)) { 390 if (isDirectory(p)) { 391 Path f = resolve(p, relativePath); 392 if (f.exists()) 393 return PathFileObject.createDirectoryPathFileObject(this, f, p); 394 } else { 395 FileSystem fs = getFileSystem(p); 396 if (fs != null) { 397 Path file = getPath(fs, relativePath); 398 if (file.exists()) 399 return PathFileObject.createJarPathFileObject(this, file); 400 } 401 } 402 } 403 return null; 404 } 405 406 public JavaFileObject getJavaFileForOutput(Location location, 407 String className, Kind kind, FileObject sibling) throws IOException { 408 return getFileForOutput(location, getRelativePath(className, kind), sibling); 409 } 410 411 public FileObject getFileForOutput(Location location, String packageName, 412 String relativeName, FileObject sibling) 413 throws IOException { 414 return getFileForOutput(location, getRelativePath(packageName, relativeName), sibling); 415 } 416 417 private JavaFileObject getFileForOutput(Location location, 418 String relativePath, FileObject sibling) { 419 Path dir = getOutputLocation(location); 420 if (dir == null) { 421 if (location == CLASS_OUTPUT) { 422 Path siblingDir = null; 423 if (sibling != null && sibling instanceof PathFileObject) { 424 siblingDir = ((PathFileObject) sibling).getPath().getParent(); 425 } 426 return PathFileObject.createSiblingPathFileObject(this, 427 siblingDir.resolve(getBaseName(relativePath)), 428 relativePath); 429 } else if (location == SOURCE_OUTPUT) { 430 dir = getOutputLocation(CLASS_OUTPUT); 431 } 432 } 433 434 Path file; 435 if (dir != null) { 436 file = resolve(dir, relativePath); 437 return PathFileObject.createDirectoryPathFileObject(this, file, dir); 438 } else { 439 file = getPath(getDefaultFileSystem(), relativePath); 440 return PathFileObject.createSimplePathFileObject(this, file); 441 } 442 443 } 444 445 public String inferBinaryName(Location location, JavaFileObject fo) { 446 nullCheck(fo); 447 // Need to match the path semantics of list(location, ...) 448 Iterable<? extends Path> paths = getLocation(location); 449 if (paths == null) { 450 return null; 451 } 452 453 if (!(fo instanceof PathFileObject)) 454 throw new IllegalArgumentException(fo.getClass().getName()); 455 456 return ((PathFileObject) fo).inferBinaryName(paths); 457 } 458 459 private FileSystem getFileSystem(Path p) throws IOException { 460 FileSystem fs = fileSystems.get(p); 461 if (fs == null) { 462 fs = FileSystems.newFileSystem(p, Collections.<String,Void>emptyMap(), null); 463 fileSystems.put(p, fs); 464 } 465 return fs; 466 } 467 468 private Map<Path,FileSystem> fileSystems; 469 470 // </editor-fold> 471 472 // <editor-fold defaultstate="collapsed" desc="Utility methods"> 473 474 private static String getRelativePath(String className, Kind kind) { 475 return className.replace(".", "/") + kind.extension; 476 } 477 478 private static String getRelativePath(String packageName, String relativeName) { 479 return packageName.replace(".", "/") + relativeName; 480 } 481 482 private static String getBaseName(String relativePath) { 483 int lastSep = relativePath.lastIndexOf("/"); 484 return relativePath.substring(lastSep + 1); // safe if "/" not found 485 } 486 487 private static String getExtension(String fileName) { 488 int lastDot = fileName.lastIndexOf("."); 489 return (lastDot == -1 ? "" : fileName.substring(lastDot)); 490 } 491 492 protected Kind getKind(String fileName) { 493 String extn = getExtension(fileName); 494 if (extn.equals(Kind.CLASS.extension)) 495 return JavaFileObject.Kind.CLASS; 496 else if (extn.equals(Kind.SOURCE.extension)) 497 return JavaFileObject.Kind.SOURCE; 498 else if (extn.equals(Kind.HTML.extension)) 499 return JavaFileObject.Kind.HTML; 500 else 501 return JavaFileObject.Kind.OTHER; 502 } 503 504 private static boolean isDirectory(Path path) throws IOException { 505 BasicFileAttributes attrs = Attributes.readBasicFileAttributes(path); 506 return attrs.isDirectory(); 507 } 508 509 private static Path getPath(FileSystem fs, String relativePath) { 510 return fs.getPath(relativePath.replace("/", fs.getSeparator())); 511 } 512 513 private static Path resolve(Path base, String relativePath) { 514 FileSystem fs = base.getFileSystem(); 515 Path rp = fs.getPath(relativePath.replace("/", fs.getSeparator())); 516 return base.resolve(rp); 517 } 518 519 // </editor-fold> 520 521 // <editor-fold defaultstate="collapsed" desc="BaseFileManager?"> 522 523 // The following code is mostly all cut n paste from JavacFileManager and 524 // could/should be shared with it, perhaps by moving to a new class 525 // BaseFileManager in com.sun.tools.javac.util. 526 // There are no references to java.io.File or java.nio.file.Path here. 527 528 /** 529 * The log to be used for error reporting. 530 */ 531 protected Log log; 532 533 /** 534 * User provided charset (through javax.tools). 535 */ 536 protected Charset charset; 537 538 private Options options; 539 540 protected String classLoaderClass; 541 542 protected Source getSource() { 543 String sourceName = options.get(OptionName.SOURCE); 544 Source source = null; 545 if (sourceName != null) 546 source = Source.lookup(sourceName); 547 return (source != null ? source : Source.DEFAULT); 548 } 549 550 protected ClassLoader getClassLoader(URL[] urls) { 551 ClassLoader thisClassLoader = getClass().getClassLoader(); 552 553 // Bug: 6558476 554 // Ideally, ClassLoader should be Closeable, but before JDK7 it is not. 555 // On older versions, try the following, to get a closeable classloader. 556 557 // 1: Allow client to specify the class to use via hidden option 558 if (classLoaderClass != null) { 559 try { 560 Class<? extends ClassLoader> loader = 561 Class.forName(classLoaderClass).asSubclass(ClassLoader.class); 562 Class<?>[] constrArgTypes = { URL[].class, ClassLoader.class }; 563 Constructor<? extends ClassLoader> constr = loader.getConstructor(constrArgTypes); 564 return constr.newInstance(new Object[] { urls, thisClassLoader }); 565 } catch (Throwable t) { 566 // ignore errors loading user-provided class loader, fall through 567 } 568 } 569 570 // 2: If URLClassLoader implements Closeable, use that. 571 if (Closeable.class.isAssignableFrom(URLClassLoader.class)) 572 return new URLClassLoader(urls, thisClassLoader); 573 574 // 3: Try using private reflection-based CloseableURLClassLoader 575 try { 576 return new CloseableURLClassLoader(urls, thisClassLoader); 577 } catch (Throwable t) { 578 // ignore errors loading workaround class loader, fall through 579 } 580 581 // 4: If all else fails, use plain old standard URLClassLoader 582 return new URLClassLoader(urls, thisClassLoader); 583 } 584 585 586 // <editor-fold defaultstate="collapsed" desc="Option handling"> 587 public boolean handleOption(String current, Iterator<String> remaining) { 588 for (JavacOption o: javacFileManagerOptions) { 589 if (o.matches(current)) { 590 if (o.hasArg()) { 591 if (remaining.hasNext()) { 592 if (!o.process(options, current, remaining.next())) 593 return true; 594 } 595 } else { 596 if (!o.process(options, current)) 597 return true; 598 } 599 // operand missing, or process returned false 600 throw new IllegalArgumentException(current); 601 } 602 } 603 604 return false; 605 } 606 // where 607 private static JavacOption[] javacFileManagerOptions = 608 RecognizedOptions.getJavacFileManagerOptions( 609 new RecognizedOptions.GrumpyHelper()); 610 611 public int isSupportedOption(String option) { 612 for (JavacOption o : javacFileManagerOptions) { 613 if (o.matches(option)) 614 return o.hasArg() ? 1 : 0; 615 } 616 return -1; 617 } 618 // </editor-fold> 619 620 // <editor-fold defaultstate="collapsed" desc="Encoding"> 621 private String defaultEncodingName; 622 private String getDefaultEncodingName() { 623 if (defaultEncodingName == null) { 624 defaultEncodingName = 625 new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding(); 626 } 627 return defaultEncodingName; 628 } 629 630 protected String getEncodingName() { 631 String encName = options.get(OptionName.ENCODING); 632 if (encName == null) 633 return getDefaultEncodingName(); 634 else 635 return encName; 636 } 637 638 CharBuffer decode(ByteBuffer inbuf, boolean ignoreEncodingErrors) { 639 String encodingName = getEncodingName(); 640 CharsetDecoder decoder; 641 try { 642 decoder = getDecoder(encodingName, ignoreEncodingErrors); 643 } catch (IllegalCharsetNameException e) { 644 log.error("unsupported.encoding", encodingName); 645 return (CharBuffer)CharBuffer.allocate(1).flip(); 646 } catch (UnsupportedCharsetException e) { 647 log.error("unsupported.encoding", encodingName); 648 return (CharBuffer)CharBuffer.allocate(1).flip(); 649 } 650 651 // slightly overestimate the buffer size to avoid reallocation. 652 float factor = 653 decoder.averageCharsPerByte() * 0.8f + 654 decoder.maxCharsPerByte() * 0.2f; 655 CharBuffer dest = CharBuffer. 656 allocate(10 + (int)(inbuf.remaining()*factor)); 657 658 while (true) { 659 CoderResult result = decoder.decode(inbuf, dest, true); 660 dest.flip(); 661 662 if (result.isUnderflow()) { // done reading 663 // make sure there is at least one extra character 664 if (dest.limit() == dest.capacity()) { 665 dest = CharBuffer.allocate(dest.capacity()+1).put(dest); 666 dest.flip(); 667 } 668 return dest; 669 } else if (result.isOverflow()) { // buffer too small; expand 670 int newCapacity = 671 10 + dest.capacity() + 672 (int)(inbuf.remaining()*decoder.maxCharsPerByte()); 673 dest = CharBuffer.allocate(newCapacity).put(dest); 674 } else if (result.isMalformed() || result.isUnmappable()) { 675 // bad character in input 676 677 // report coding error (warn only pre 1.5) 678 if (!getSource().allowEncodingErrors()) { 679 log.error(new SimpleDiagnosticPosition(dest.limit()), 680 "illegal.char.for.encoding", 681 charset == null ? encodingName : charset.name()); 682 } else { 683 log.warning(new SimpleDiagnosticPosition(dest.limit()), 684 "illegal.char.for.encoding", 685 charset == null ? encodingName : charset.name()); 686 } 687 688 // skip past the coding error 689 inbuf.position(inbuf.position() + result.length()); 690 691 // undo the flip() to prepare the output buffer 692 // for more translation 693 dest.position(dest.limit()); 694 dest.limit(dest.capacity()); 695 dest.put((char)0xfffd); // backward compatible 696 } else { 697 throw new AssertionError(result); 698 } 699 } 700 // unreached 701 } 702 703 CharsetDecoder getDecoder(String encodingName, boolean ignoreEncodingErrors) { 704 Charset cs = (this.charset == null) 705 ? Charset.forName(encodingName) 706 : this.charset; 707 CharsetDecoder decoder = cs.newDecoder(); 708 709 CodingErrorAction action; 710 if (ignoreEncodingErrors) 711 action = CodingErrorAction.REPLACE; 712 else 713 action = CodingErrorAction.REPORT; 714 715 return decoder 716 .onMalformedInput(action) 717 .onUnmappableCharacter(action); 718 } 719 // </editor-fold> 720 721 // <editor-fold defaultstate="collapsed" desc="ByteBuffers"> 722 /** 723 * Make a byte buffer from an input stream. 724 */ 725 ByteBuffer makeByteBuffer(InputStream in) 726 throws IOException { 727 int limit = in.available(); 728 if (limit < 1024) limit = 1024; 729 ByteBuffer result = byteBufferCache.get(limit); 730 int position = 0; 731 while (in.available() != 0) { 732 if (position >= limit) 733 // expand buffer 734 result = ByteBuffer. 735 allocate(limit <<= 1). 736 put((ByteBuffer)result.flip()); 737 int count = in.read(result.array(), 738 position, 739 limit - position); 740 if (count < 0) break; 741 result.position(position += count); 742 } 743 return (ByteBuffer)result.flip(); 744 } 745 746 void recycleByteBuffer(ByteBuffer bb) { 747 byteBufferCache.put(bb); 748 } 749 750 /** 751 * A single-element cache of direct byte buffers. 752 */ 753 private static class ByteBufferCache { 754 private ByteBuffer cached; 755 ByteBuffer get(int capacity) { 756 if (capacity < 20480) capacity = 20480; 757 ByteBuffer result = 758 (cached != null && cached.capacity() >= capacity) 759 ? (ByteBuffer)cached.clear() 760 : ByteBuffer.allocate(capacity + capacity>>1); 761 cached = null; 762 return result; 763 } 764 void put(ByteBuffer x) { 765 cached = x; 766 } 767 } 768 769 private final ByteBufferCache byteBufferCache; 770 // </editor-fold> 771 772 // <editor-fold defaultstate="collapsed" desc="Content cache"> 773 CharBuffer getCachedContent(JavaFileObject file) { 774 SoftReference<CharBuffer> r = contentCache.get(file); 775 return (r == null ? null : r.get()); 776 } 777 778 void cache(JavaFileObject file, CharBuffer cb) { 779 contentCache.put(file, new SoftReference<CharBuffer>(cb)); 780 } 781 782 private final Map<JavaFileObject, SoftReference<CharBuffer>> contentCache 783 = new HashMap<JavaFileObject, SoftReference<CharBuffer>>(); 784 // </editor-fold> 785 786 private static <T> T nullCheck(T o) { 787 o.getClass(); // null check 788 return o; 789 } 790 791 private static <T> Collection<T> nullCheck(Collection<T> it) { 792 for (T t : it) 793 t.getClass(); // null check 794 return it; 795 } 796 // </editor-fold> 797 }