1 /* 2 * Copyright (c) 2009, 2016, 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 com.sun.tools.javac.file; 27 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.lang.ref.SoftReference; 31 import java.lang.reflect.Constructor; 32 import java.lang.reflect.InvocationTargetException; 33 import java.lang.reflect.Method; 34 import java.net.URL; 35 import java.net.URLClassLoader; 36 import java.nio.ByteBuffer; 37 import java.nio.CharBuffer; 38 import java.nio.charset.Charset; 39 import java.nio.charset.CharsetDecoder; 40 import java.nio.charset.CoderResult; 41 import java.nio.charset.CodingErrorAction; 42 import java.nio.charset.IllegalCharsetNameException; 43 import java.nio.charset.UnsupportedCharsetException; 44 import java.nio.file.Path; 45 import java.util.Collection; 46 import java.util.HashMap; 47 import java.util.HashSet; 48 import java.util.Iterator; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.Set; 52 53 import javax.tools.JavaFileManager; 54 import javax.tools.JavaFileObject; 55 import javax.tools.JavaFileObject.Kind; 56 57 import com.sun.tools.javac.main.Option; 58 import com.sun.tools.javac.main.OptionHelper; 59 import com.sun.tools.javac.main.OptionHelper.GrumpyHelper; 60 import com.sun.tools.javac.resources.CompilerProperties.Errors; 61 import com.sun.tools.javac.util.Abort; 62 import com.sun.tools.javac.util.Context; 63 import com.sun.tools.javac.util.DefinedBy; 64 import com.sun.tools.javac.util.DefinedBy.Api; 65 import com.sun.tools.javac.util.Log; 66 import com.sun.tools.javac.util.Options; 67 68 /** 69 * Utility methods for building a filemanager. 70 * There are no references here to file-system specific objects such as 71 * java.io.File or java.nio.file.Path. 72 */ 73 public abstract class BaseFileManager implements JavaFileManager { 74 protected BaseFileManager(Charset charset) { 75 this.charset = charset; 76 byteBufferCache = new ByteBufferCache(); 77 locations = createLocations(); 78 } 79 80 /** 81 * Set the context for JavacPathFileManager. 82 * @param context the context containing items to be associated with the file manager 83 */ 84 public void setContext(Context context) { 85 log = Log.instance(context); 86 options = Options.instance(context); 87 classLoaderClass = options.get("procloader"); 88 89 // Avoid initializing Lint 90 boolean warn = options.isLintSet("path"); 91 locations.update(log, warn, FSInfo.instance(context)); 92 93 // Setting this option is an indication that close() should defer actually closing 94 // the file manager until after a specified period of inactivity. 95 // This is to accomodate clients which save references to Symbols created for use 96 // within doclets or annotation processors, and which then attempt to use those 97 // references after the tool exits, having closed any internally managed file manager. 98 // Ideally, such clients should run the tool via the javax.tools API, providing their 99 // own file manager, which can be closed by the client when all use of that file 100 // manager is complete. 101 // If the option has a numeric value, it will be interpreted as the duration, 102 // in seconds, of the period of inactivity to wait for, before the file manager 103 // is actually closed. 104 // See also deferredClose(). 105 String s = options.get("fileManager.deferClose"); 106 if (s != null) { 107 try { 108 deferredCloseTimeout = (int) (Float.parseFloat(s) * 1000); 109 } catch (NumberFormatException e) { 110 deferredCloseTimeout = 60 * 1000; // default: one minute, in millis 111 } 112 } 113 } 114 115 protected Locations createLocations() { 116 return new Locations(); 117 } 118 119 /** 120 * The log to be used for error reporting. 121 */ 122 public Log log; 123 124 /** 125 * User provided charset (through javax.tools). 126 */ 127 protected Charset charset; 128 129 protected Options options; 130 131 protected String classLoaderClass; 132 133 protected final Locations locations; 134 135 /** 136 * A flag for clients to use to indicate that this file manager should 137 * be closed when it is no longer required. 138 */ 139 public boolean autoClose; 140 141 /** 142 * Wait for a period of inactivity before calling close(). 143 * The length of the period of inactivity is given by {@code deferredCloseTimeout} 144 */ 145 protected void deferredClose() { 146 Thread t = new Thread(getClass().getName() + " DeferredClose") { 147 @Override 148 public void run() { 149 try { 150 synchronized (BaseFileManager.this) { 151 long now = System.currentTimeMillis(); 152 while (now < lastUsedTime + deferredCloseTimeout) { 153 BaseFileManager.this.wait(lastUsedTime + deferredCloseTimeout - now); 154 now = System.currentTimeMillis(); 155 } 156 deferredCloseTimeout = 0; 157 close(); 158 } 159 } catch (InterruptedException e) { 160 } catch (IOException e) { 161 } 162 } 163 }; 164 t.setDaemon(true); 165 t.start(); 166 } 167 168 synchronized void updateLastUsedTime() { 169 if (deferredCloseTimeout > 0) { // avoid updating the time unnecessarily 170 lastUsedTime = System.currentTimeMillis(); 171 } 172 } 173 174 private long lastUsedTime = System.currentTimeMillis(); 175 protected long deferredCloseTimeout = 0; 176 177 public void clear() { 178 new HashSet<>(options.keySet()).forEach(k -> options.remove(k)); 179 } 180 181 protected ClassLoader getClassLoader(URL[] urls) { 182 ClassLoader thisClassLoader = getClass().getClassLoader(); 183 184 // Allow the following to specify a closeable classloader 185 // other than URLClassLoader. 186 187 // 1: Allow client to specify the class to use via hidden option 188 if (classLoaderClass != null) { 189 try { 190 Class<? extends ClassLoader> loader = 191 Class.forName(classLoaderClass).asSubclass(ClassLoader.class); 192 Class<?>[] constrArgTypes = { URL[].class, ClassLoader.class }; 193 Constructor<? extends ClassLoader> constr = loader.getConstructor(constrArgTypes); 194 return constr.newInstance(urls, thisClassLoader); 195 } catch (ReflectiveOperationException t) { 196 // ignore errors loading user-provided class loader, fall through 197 } 198 } 199 return new URLClassLoader(urls, thisClassLoader); 200 } 201 202 public boolean isDefaultBootClassPath() { 203 return locations.isDefaultBootClassPath(); 204 } 205 206 public boolean isDefaultSystemModulesPath() { 207 return locations.isDefaultSystemModulesPath(); 208 } 209 210 // <editor-fold defaultstate="collapsed" desc="Option handling"> 211 @Override @DefinedBy(Api.COMPILER) 212 public boolean handleOption(String current, Iterator<String> remaining) { 213 OptionHelper helper = new GrumpyHelper(log) { 214 @Override 215 public String get(Option option) { 216 return options.get(option); 217 } 218 219 @Override 220 public void put(String name, String value) { 221 options.put(name, value); 222 } 223 224 @Override 225 public void remove(String name) { 226 options.remove(name); 227 } 228 229 @Override 230 public boolean handleFileManagerOption(Option option, String value) { 231 return handleOption(option, value); 232 } 233 }; 234 235 Option o = Option.lookup(current, javacFileManagerOptions); 236 if (o == null) { 237 return false; 238 } 239 240 try { 241 o.handleOption(helper, current, remaining); 242 } catch (Option.InvalidValueException e) { 243 throw new IllegalArgumentException(e.getMessage(), e); 244 } 245 246 return true; 247 } 248 // where 249 protected static final Set<Option> javacFileManagerOptions = 250 Option.getJavacFileManagerOptions(); 251 252 @Override @DefinedBy(Api.COMPILER) 253 public int isSupportedOption(String option) { 254 Option o = Option.lookup(option, javacFileManagerOptions); 255 return (o == null) ? -1 : o.hasArg() ? 1 : 0; 256 } 257 258 protected String multiReleaseValue; 259 260 /** 261 * Common back end for OptionHelper handleFileManagerOption. 262 * @param option the option whose value to be set 263 * @param value the value for the option 264 * @return true if successful, and false otherwise 265 */ 266 public boolean handleOption(Option option, String value) { 267 switch (option) { 268 case ENCODING: 269 encodingName = value; 270 return true; 271 272 case MULTIRELEASE: 273 multiReleaseValue = value; 274 locations.setMultiReleaseValue(value); 275 return true; 276 277 default: 278 return locations.handleOption(option, value); 279 } 280 } 281 282 /** 283 * Call handleOption for collection of options and corresponding values. 284 * @param map a collection of options and corresponding values 285 * @return true if all the calls are successful 286 */ 287 public boolean handleOptions(Map<Option, String> map) { 288 boolean ok = true; 289 for (Map.Entry<Option, String> e: map.entrySet()) { 290 try { 291 ok = ok & handleOption(e.getKey(), e.getValue()); 292 } catch (IllegalArgumentException ex) { 293 log.error(Errors.IllegalArgumentForOption(e.getKey().getPrimaryName(), ex.getMessage())); 294 ok = false; 295 } 296 } 297 return ok; 298 } 299 300 // </editor-fold> 301 302 // <editor-fold defaultstate="collapsed" desc="Encoding"> 303 private String encodingName; 304 private String defaultEncodingName; 305 private String getDefaultEncodingName() { 306 if (defaultEncodingName == null) { 307 defaultEncodingName = Charset.defaultCharset().name(); 308 } 309 return defaultEncodingName; 310 } 311 312 public String getEncodingName() { 313 return (encodingName != null) ? encodingName : getDefaultEncodingName(); 314 } 315 316 public CharBuffer decode(ByteBuffer inbuf, boolean ignoreEncodingErrors) { 317 String encName = getEncodingName(); 318 CharsetDecoder decoder; 319 try { 320 decoder = getDecoder(encName, ignoreEncodingErrors); 321 } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { 322 log.error(Errors.UnsupportedEncoding(encName)); 323 return CharBuffer.allocate(1).flip(); 324 } 325 326 // slightly overestimate the buffer size to avoid reallocation. 327 float factor = 328 decoder.averageCharsPerByte() * 0.8f + 329 decoder.maxCharsPerByte() * 0.2f; 330 CharBuffer dest = CharBuffer. 331 allocate(10 + (int)(inbuf.remaining()*factor)); 332 333 while (true) { 334 CoderResult result = decoder.decode(inbuf, dest, true); 335 dest.flip(); 336 337 if (result.isUnderflow()) { // done reading 338 // make sure there is at least one extra character 339 if (dest.limit() == dest.capacity()) { 340 dest = CharBuffer.allocate(dest.capacity()+1).put(dest); 341 dest.flip(); 342 } 343 return dest; 344 } else if (result.isOverflow()) { // buffer too small; expand 345 int newCapacity = 346 10 + dest.capacity() + 347 (int)(inbuf.remaining()*decoder.maxCharsPerByte()); 348 dest = CharBuffer.allocate(newCapacity).put(dest); 349 } else if (result.isMalformed() || result.isUnmappable()) { 350 // bad character in input 351 StringBuilder unmappable = new StringBuilder(); 352 int len = result.length(); 353 354 for (int i = 0; i < len; i++) { 355 unmappable.append(String.format("%02X", inbuf.get())); 356 } 357 358 String charsetName = charset == null ? encName : charset.name(); 359 360 log.error(dest.limit(), 361 Errors.IllegalCharForEncoding(unmappable.toString(), charsetName)); 362 363 // undo the flip() to prepare the output buffer 364 // for more translation 365 dest.position(dest.limit()); 366 dest.limit(dest.capacity()); 367 dest.put((char)0xfffd); // backward compatible 368 } else { 369 throw new AssertionError(result); 370 } 371 } 372 // unreached 373 } 374 375 public CharsetDecoder getDecoder(String encodingName, boolean ignoreEncodingErrors) { 376 Charset cs = (this.charset == null) 377 ? Charset.forName(encodingName) 378 : this.charset; 379 CharsetDecoder decoder = cs.newDecoder(); 380 381 CodingErrorAction action; 382 if (ignoreEncodingErrors) 383 action = CodingErrorAction.REPLACE; 384 else 385 action = CodingErrorAction.REPORT; 386 387 return decoder 388 .onMalformedInput(action) 389 .onUnmappableCharacter(action); 390 } 391 // </editor-fold> 392 393 // <editor-fold defaultstate="collapsed" desc="ByteBuffers"> 394 /** 395 * Make a byte buffer from an input stream. 396 * @param in the stream 397 * @return a byte buffer containing the contents of the stream 398 * @throws IOException if an error occurred while reading the stream 399 */ 400 public ByteBuffer makeByteBuffer(InputStream in) 401 throws IOException { 402 int limit = in.available(); 403 if (limit < 1024) limit = 1024; 404 ByteBuffer result = byteBufferCache.get(limit); 405 int position = 0; 406 while (in.available() != 0) { 407 if (position >= limit) 408 // expand buffer 409 result = ByteBuffer. 410 allocate(limit <<= 1). 411 put(result.flip()); 412 int count = in.read(result.array(), 413 position, 414 limit - position); 415 if (count < 0) break; 416 result.position(position += count); 417 } 418 return result.flip(); 419 } 420 421 public void recycleByteBuffer(ByteBuffer bb) { 422 byteBufferCache.put(bb); 423 } 424 425 /** 426 * A single-element cache of direct byte buffers. 427 */ 428 private static class ByteBufferCache { 429 private ByteBuffer cached; 430 ByteBuffer get(int capacity) { 431 if (capacity < 20480) capacity = 20480; 432 ByteBuffer result = 433 (cached != null && cached.capacity() >= capacity) 434 ? cached.clear() 435 : ByteBuffer.allocate(capacity + capacity>>1); 436 cached = null; 437 return result; 438 } 439 void put(ByteBuffer x) { 440 cached = x; 441 } 442 } 443 444 private final ByteBufferCache byteBufferCache; 445 // </editor-fold> 446 447 // <editor-fold defaultstate="collapsed" desc="Content cache"> 448 public CharBuffer getCachedContent(JavaFileObject file) { 449 ContentCacheEntry e = contentCache.get(file); 450 if (e == null) 451 return null; 452 453 if (!e.isValid(file)) { 454 contentCache.remove(file); 455 return null; 456 } 457 458 return e.getValue(); 459 } 460 461 public void cache(JavaFileObject file, CharBuffer cb) { 462 contentCache.put(file, new ContentCacheEntry(file, cb)); 463 } 464 465 public void flushCache(JavaFileObject file) { 466 contentCache.remove(file); 467 } 468 469 protected final Map<JavaFileObject, ContentCacheEntry> contentCache = new HashMap<>(); 470 471 protected static class ContentCacheEntry { 472 final long timestamp; 473 final SoftReference<CharBuffer> ref; 474 475 ContentCacheEntry(JavaFileObject file, CharBuffer cb) { 476 this.timestamp = file.getLastModified(); 477 this.ref = new SoftReference<>(cb); 478 } 479 480 boolean isValid(JavaFileObject file) { 481 return timestamp == file.getLastModified(); 482 } 483 484 CharBuffer getValue() { 485 return ref.get(); 486 } 487 } 488 // </editor-fold> 489 490 public static Kind getKind(Path path) { 491 return getKind(path.getFileName().toString()); 492 } 493 494 public static Kind getKind(String name) { 495 if (name.endsWith(Kind.CLASS.extension)) 496 return Kind.CLASS; 497 else if (name.endsWith(Kind.SOURCE.extension)) 498 return Kind.SOURCE; 499 else if (name.endsWith(Kind.HTML.extension)) 500 return Kind.HTML; 501 else 502 return Kind.OTHER; 503 } 504 505 protected static <T> T nullCheck(T o) { 506 return Objects.requireNonNull(o); 507 } 508 509 protected static <T> Collection<T> nullCheck(Collection<T> it) { 510 for (T t : it) 511 Objects.requireNonNull(t); 512 return it; 513 } 514 }