MemoryFileManager.java revision 3376:4c740bddc648
1/* 2 * Copyright (c) 2014, 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 26package jdk.jshell; 27 28import java.io.ByteArrayInputStream; 29import java.io.ByteArrayOutputStream; 30import java.io.IOException; 31import java.io.InputStream; 32import java.io.OutputStream; 33import java.lang.reflect.InvocationTargetException; 34import java.lang.reflect.Method; 35import java.net.URI; 36import java.nio.file.FileSystems; 37import java.nio.file.Files; 38import java.nio.file.Path; 39import java.util.Collection; 40import java.util.Iterator; 41import java.util.Map; 42import java.util.NoSuchElementException; 43import java.util.Set; 44import java.util.TreeMap; 45 46import javax.tools.JavaFileObject.Kind; 47import static javax.tools.StandardLocation.CLASS_PATH; 48import javax.tools.FileObject; 49import javax.tools.JavaFileManager; 50import javax.tools.JavaFileObject; 51import javax.tools.SimpleJavaFileObject; 52import javax.tools.StandardJavaFileManager; 53import javax.tools.StandardLocation; 54 55import com.sun.tools.javac.util.DefinedBy; 56import com.sun.tools.javac.util.DefinedBy.Api; 57 58import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR; 59 60/** 61 * File manager for the compiler API. Reads from memory (Strings) and writes 62 * class files to memory (cached OutputMemoryJavaFileObject). 63 * 64 * @author Robert Field 65 */ 66class MemoryFileManager implements JavaFileManager { 67 68 private final StandardJavaFileManager stdFileManager; 69 70 private final Map<String, OutputMemoryJavaFileObject> classObjects = new TreeMap<>(); 71 72 private ClassFileCreationListener classListener = null; 73 74 private final ClassLoader loader = new REPLClassLoader(); 75 76 private final JShell proc; 77 78 // Upcoming Jigsaw 79 private Method inferModuleNameMethod = null; 80 private Method listModuleLocationsMethod = null; 81 82 Iterable<? extends Path> getLocationAsPaths(Location loc) { 83 return this.stdFileManager.getLocationAsPaths(loc); 84 } 85 86 static abstract class MemoryJavaFileObject extends SimpleJavaFileObject { 87 88 public MemoryJavaFileObject(String name, JavaFileObject.Kind kind) { 89 super(URI.create("string:///" + name.replace('.', '/') 90 + kind.extension), kind); 91 } 92 } 93 94 class SourceMemoryJavaFileObject extends MemoryJavaFileObject { 95 private final String src; 96 private final Object origin; 97 98 SourceMemoryJavaFileObject(Object origin, String className, String code) { 99 super(className, JavaFileObject.Kind.SOURCE); 100 this.origin = origin; 101 this.src = code; 102 } 103 104 public Object getOrigin() { 105 return origin; 106 } 107 108 @Override @DefinedBy(Api.COMPILER) 109 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 110 return src; 111 } 112 } 113 114 static class OutputMemoryJavaFileObject extends MemoryJavaFileObject { 115 116 /** 117 * Byte code created by the compiler will be stored in this 118 * ByteArrayOutputStream. 119 */ 120 private ByteArrayOutputStream bos = new ByteArrayOutputStream(); 121 private byte[] bytes = null; 122 123 private final String className; 124 125 public OutputMemoryJavaFileObject(String name, JavaFileObject.Kind kind) { 126 super(name, kind); 127 this.className = name; 128 } 129 130 public byte[] getBytes() { 131 if (bytes == null) { 132 bytes = bos.toByteArray(); 133 bos = null; 134 } 135 return bytes; 136 } 137 138 public void dump() { 139 try { 140 Path dumpDir = FileSystems.getDefault().getPath("dump"); 141 if (Files.notExists(dumpDir)) { 142 Files.createDirectory(dumpDir); 143 } 144 Path file = FileSystems.getDefault().getPath("dump", getName() + ".class"); 145 Files.write(file, getBytes()); 146 } catch (IOException ex) { 147 throw new RuntimeException(ex); 148 } 149 } 150 151 @Override @DefinedBy(Api.COMPILER) 152 public String getName() { 153 return className; 154 } 155 156 /** 157 * Will provide the compiler with an output stream that leads to our 158 * byte array. 159 */ 160 @Override @DefinedBy(Api.COMPILER) 161 public OutputStream openOutputStream() throws IOException { 162 return bos; 163 } 164 165 @Override @DefinedBy(Api.COMPILER) 166 public InputStream openInputStream() throws IOException { 167 return new ByteArrayInputStream(getBytes()); 168 } 169 } 170 171 // For restoring process-local execution support 172 class REPLClassLoader extends ClassLoader { 173 174 @Override 175 protected Class<?> findClass(String name) throws ClassNotFoundException { 176 OutputMemoryJavaFileObject fo = classObjects.get(name); 177 proc.debug(DBG_FMGR, "findClass %s = %s\n", name, fo); 178 if (fo == null) { 179 throw new ClassNotFoundException("Not ours"); 180 } 181 byte[] b = fo.getBytes(); 182 return super.defineClass(name, b, 0, b.length, null); 183 } 184 } 185 186 public MemoryFileManager(StandardJavaFileManager standardManager, JShell proc) { 187 this.stdFileManager = standardManager; 188 this.proc = proc; 189 } 190 191 private Collection<OutputMemoryJavaFileObject> generatedClasses() { 192 return classObjects.values(); 193 } 194 195 // For debugging dumps 196 public void dumpClasses() { 197 for (OutputMemoryJavaFileObject co : generatedClasses()) { 198 co.dump(); 199 } 200 } 201 202 // For restoring process-local execution support 203 public Class<?> findGeneratedClass(String genClassFullName) throws ClassNotFoundException { 204 for (OutputMemoryJavaFileObject co : generatedClasses()) { 205 if (co.className.equals(genClassFullName)) { 206 Class<?> klass = loadClass(co.className); 207 proc.debug(DBG_FMGR, "Loaded %s\n", klass); 208 return klass; 209 } 210 } 211 return null; 212 } 213 214 // For restoring process-local execution support 215 public byte[] findGeneratedBytes(String genClassFullName) throws ClassNotFoundException { 216 for (OutputMemoryJavaFileObject co : generatedClasses()) { 217 if (co.className.equals(genClassFullName)) { 218 return co.getBytes(); 219 } 220 } 221 return null; 222 } 223 224 // For restoring process-local execution support 225 public Class<?> loadClass(String name) throws ClassNotFoundException { 226 return getClassLoader(null).loadClass(name); 227 } 228 229 public JavaFileObject createSourceFileObject(Object origin, String name, String code) { 230 return new SourceMemoryJavaFileObject(origin, name, code); 231 } 232 233 // Make compatible with Jigsaw 234 @DefinedBy(Api.COMPILER) 235 public String inferModuleName(Location location) { 236 try { 237 if (inferModuleNameMethod == null) { 238 inferModuleNameMethod = JavaFileManager.class.getDeclaredMethod("inferModuleName", Location.class); 239 } 240 @SuppressWarnings("unchecked") 241 String result = (String) inferModuleNameMethod.invoke(stdFileManager, location); 242 return result; 243 } catch (NoSuchMethodException | SecurityException ex) { 244 throw new InternalError("Cannot lookup JavaFileManager method", ex); 245 } catch (IllegalAccessException | 246 IllegalArgumentException | 247 InvocationTargetException ex) { 248 throw new InternalError("Cannot invoke JavaFileManager method", ex); 249 } 250 } 251 252 // Make compatible with Jigsaw 253 @DefinedBy(Api.COMPILER) 254 public Iterable<Set<Location>> listModuleLocations(Location location) throws IOException { 255 try { 256 if (listModuleLocationsMethod == null) { 257 listModuleLocationsMethod = JavaFileManager.class.getDeclaredMethod("listModuleLocations", Location.class); 258 } 259 @SuppressWarnings("unchecked") 260 Iterable<Set<Location>> result = (Iterable<Set<Location>>) listModuleLocationsMethod.invoke(stdFileManager, location); 261 return result; 262 } catch (NoSuchMethodException | SecurityException ex) { 263 throw new InternalError("Cannot lookup JavaFileManager method", ex); 264 } catch (IllegalAccessException | 265 IllegalArgumentException | 266 InvocationTargetException ex) { 267 throw new InternalError("Cannot invoke JavaFileManager method", ex); 268 } 269 } 270 271 272 /** 273 * Returns a class loader for loading plug-ins from the given location. For 274 * example, to load annotation processors, a compiler will request a class 275 * loader for the {@link 276 * StandardLocation#ANNOTATION_PROCESSOR_PATH 277 * ANNOTATION_PROCESSOR_PATH} location. 278 * 279 * @param location a location 280 * @return a class loader for the given location; or {@code null} 281 * if loading plug-ins from the given location is disabled or if 282 * the location is not known 283 * @throws SecurityException if a class loader can not be created 284 * in the current security context 285 * @throws IllegalStateException if {@link #close} has been called 286 * and this file manager cannot be reopened 287 */ 288 @Override @DefinedBy(Api.COMPILER) 289 public ClassLoader getClassLoader(JavaFileManager.Location location) { 290 proc.debug(DBG_FMGR, "getClassLoader: location\n", location); 291 return loader; 292 } 293 294 /** 295 * Lists all file objects matching the given criteria in the given 296 * location. List file objects in "subpackages" if recurse is 297 * true. 298 * 299 * <p>Note: even if the given location is unknown to this file 300 * manager, it may not return {@code null}. Also, an unknown 301 * location may not cause an exception. 302 * 303 * @param location a location 304 * @param packageName a package name 305 * @param kinds return objects only of these kinds 306 * @param recurse if true include "subpackages" 307 * @return an Iterable of file objects matching the given criteria 308 * @throws IOException if an I/O error occurred, or if {@link 309 * #close} has been called and this file manager cannot be 310 * reopened 311 * @throws IllegalStateException if {@link #close} has been called 312 * and this file manager cannot be reopened 313 */ 314 @Override @DefinedBy(Api.COMPILER) 315 public Iterable<JavaFileObject> list(JavaFileManager.Location location, 316 String packageName, 317 Set<JavaFileObject.Kind> kinds, 318 boolean recurse) 319 throws IOException { 320 Iterable<JavaFileObject> stdList = stdFileManager.list(location, packageName, kinds, recurse); 321 if (location==CLASS_PATH && packageName.equals("REPL")) { 322 // if the desired list is for our JShell package, lazily iterate over 323 // first the standard list then any generated classes. 324 return () -> new Iterator<JavaFileObject>() { 325 boolean stdDone = false; 326 Iterator<? extends JavaFileObject> it; 327 328 @Override 329 public boolean hasNext() { 330 if (it == null) { 331 it = stdList.iterator(); 332 } 333 if (it.hasNext()) { 334 return true; 335 } 336 if (stdDone) { 337 return false; 338 } else { 339 stdDone = true; 340 it = generatedClasses().iterator(); 341 return it.hasNext(); 342 } 343 } 344 345 @Override 346 public JavaFileObject next() { 347 if (!hasNext()) { 348 throw new NoSuchElementException(); 349 } 350 return it.next(); 351 } 352 353 }; 354 } else { 355 return stdList; 356 } 357 } 358 359 /** 360 * Infers a binary name of a file object based on a location. The 361 * binary name returned might not be a valid binary name according to 362 * <cite>The Java&trade { throw new UnsupportedOperationException("Not supported yet."); } Language Specification</cite>. 363 * 364 * @param location a location 365 * @param file a file object 366 * @return a binary name or {@code null} the file object is not 367 * found in the given location 368 * @throws IllegalStateException if {@link #close} has been called 369 * and this file manager cannot be reopened 370 */ 371 @Override @DefinedBy(Api.COMPILER) 372 public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) { 373 if (file instanceof OutputMemoryJavaFileObject) { 374 OutputMemoryJavaFileObject ofo = (OutputMemoryJavaFileObject) file; 375 proc.debug(DBG_FMGR, "inferBinaryName %s => %s\n", file, ofo.getName()); 376 return ofo.getName(); 377 } else { 378 return stdFileManager.inferBinaryName(location, file); 379 } 380 } 381 382 /** 383 * Compares two file objects and return true if they represent the 384 * same underlying object. 385 * 386 * @param a a file object 387 * @param b a file object 388 * @return true if the given file objects represent the same 389 * underlying object 390 * 391 * @throws IllegalArgumentException if either of the arguments 392 * were created with another file manager and this file manager 393 * does not support foreign file objects 394 */ 395 @Override @DefinedBy(Api.COMPILER) 396 public boolean isSameFile(FileObject a, FileObject b) { 397 return stdFileManager.isSameFile(b, b); 398 } 399 400 /** 401 * Determines if the given option is supported and if so, the 402 * number of arguments the option takes. 403 * 404 * @param option an option 405 * @return the number of arguments the given option takes or -1 if 406 * the option is not supported 407 */ 408 @Override @DefinedBy(Api.COMPILER) 409 public int isSupportedOption(String option) { 410 proc.debug(DBG_FMGR, "isSupportedOption: %s\n", option); 411 return stdFileManager.isSupportedOption(option); 412 } 413 414 /** 415 * Handles one option. If {@code current} is an option to this 416 * file manager it will consume any arguments to that option from 417 * {@code remaining} and return true, otherwise return false. 418 * 419 * @param current current option 420 * @param remaining remaining options 421 * @return true if this option was handled by this file manager, 422 * false otherwise 423 * @throws IllegalArgumentException if this option to this file 424 * manager is used incorrectly 425 * @throws IllegalStateException if {@link #close} has been called 426 * and this file manager cannot be reopened 427 */ 428 @Override @DefinedBy(Api.COMPILER) 429 public boolean handleOption(String current, Iterator<String> remaining) { 430 proc.debug(DBG_FMGR, "handleOption: current: %s\n", current + 431 ", remaining: " + remaining); 432 return stdFileManager.handleOption(current, remaining); 433 } 434 435 /** 436 * Determines if a location is known to this file manager. 437 * 438 * @param location a location 439 * @return true if the location is known 440 */ 441 @Override @DefinedBy(Api.COMPILER) 442 public boolean hasLocation(JavaFileManager.Location location) { 443 proc.debug(DBG_FMGR, "hasLocation: location: %s\n", location); 444 return stdFileManager.hasLocation(location); 445 } 446 447 interface ClassFileCreationListener { 448 void newClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location, 449 String className, Kind kind, FileObject sibling); 450 } 451 452 void registerClassFileCreationListener(ClassFileCreationListener listen) { 453 this.classListener = listen; 454 } 455 456 /** 457 * Returns a {@linkplain JavaFileObject file object} for input 458 * representing the specified class of the specified kind in the 459 * given location. 460 * 461 * @param location a location 462 * @param className the name of a class 463 * @param kind the kind of file, must be one of {@link 464 * JavaFileObject.Kind#SOURCE SOURCE} or {@link 465 * JavaFileObject.Kind#CLASS CLASS} 466 * @return a file object, might return {@code null} if the 467 * file does not exist 468 * @throws IllegalArgumentException if the location is not known 469 * to this file manager and the file manager does not support 470 * unknown locations, or if the kind is not valid 471 * @throws IOException if an I/O error occurred, or if {@link 472 * #close} has been called and this file manager cannot be 473 * reopened 474 * @throws IllegalStateException if {@link #close} has been called 475 * and this file manager cannot be reopened 476 */ 477 @Override @DefinedBy(Api.COMPILER) 478 public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, 479 String className, 480 JavaFileObject.Kind kind) 481 throws IOException { 482 return stdFileManager.getJavaFileForInput(location, className, kind); 483 } 484 485 /** 486 * Returns a {@linkplain JavaFileObject file object} for output 487 * representing the specified class of the specified kind in the 488 * given location. 489 * 490 * <p>Optionally, this file manager might consider the sibling as 491 * a hint for where to place the output. The exact semantics of 492 * this hint is unspecified. The JDK compiler, javac, for 493 * example, will place class files in the same directories as 494 * originating source files unless a class file output directory 495 * is provided. To facilitate this behavior, javac might provide 496 * the originating source file as sibling when calling this 497 * method. 498 * 499 * @param location a location 500 * @param className the name of a class 501 * @param kind the kind of file, must be one of {@link 502 * JavaFileObject.Kind#SOURCE SOURCE} or {@link 503 * JavaFileObject.Kind#CLASS CLASS} 504 * @param sibling a file object to be used as hint for placement; 505 * might be {@code null} 506 * @return a file object for output 507 * @throws IllegalArgumentException if sibling is not known to 508 * this file manager, or if the location is not known to this file 509 * manager and the file manager does not support unknown 510 * locations, or if the kind is not valid 511 * @throws IOException if an I/O error occurred, or if {@link 512 * #close} has been called and this file manager cannot be 513 * reopened 514 * @throws IllegalStateException {@link #close} has been called 515 * and this file manager cannot be reopened 516 */ 517 @Override @DefinedBy(Api.COMPILER) 518 public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, 519 String className, Kind kind, FileObject sibling) throws IOException { 520 521 OutputMemoryJavaFileObject fo; 522 fo = new OutputMemoryJavaFileObject(className, kind); 523 classObjects.put(className, fo); 524 proc.debug(DBG_FMGR, "Set out file: %s = %s\n", className, fo); 525 if (classListener != null) { 526 classListener.newClassFile(fo, location, className, kind, sibling); 527 } 528 return fo; 529 } 530 531 /** 532 * Returns a {@linkplain FileObject file object} for input 533 * representing the specified <a href="JavaFileManager.html#relative_name">relative 534 * name</a> in the specified package in the given location. 535 * 536 * <p>If the returned object represents a {@linkplain 537 * JavaFileObject.Kind#SOURCE source} or {@linkplain 538 * JavaFileObject.Kind#CLASS class} file, it must be an instance 539 * of {@link JavaFileObject}. 540 * 541 * <p>Informally, the file object returned by this method is 542 * located in the concatenation of the location, package name, and 543 * relative name. For example, to locate the properties file 544 * "resources/compiler.properties" in the package 545 * "com.sun.tools.javac" in the {@linkplain 546 * StandardLocation#SOURCE_PATH SOURCE_PATH} location, this method 547 * might be called like so: 548 * 549 * <pre>getFileForInput(SOURCE_PATH, "com.sun.tools.javac", "resources/compiler.properties");</pre> 550 * 551 * <p>If the call was executed on Windows, with SOURCE_PATH set to 552 * <code>"C:\Documents and Settings\UncleBob\src\share\classes"</code>, 553 * a valid result would be a file object representing the file 554 * <code>"C:\Documents and Settings\UncleBob\src\share\classes\com\sun\tools\javac\resources\compiler.properties"</code>. 555 * 556 * @param location a location 557 * @param packageName a package name 558 * @param relativeName a relative name 559 * @return a file object, might return {@code null} if the file 560 * does not exist 561 * @throws IllegalArgumentException if the location is not known 562 * to this file manager and the file manager does not support 563 * unknown locations, or if {@code relativeName} is not valid 564 * @throws IOException if an I/O error occurred, or if {@link 565 * #close} has been called and this file manager cannot be 566 * reopened 567 * @throws IllegalStateException if {@link #close} has been called 568 * and this file manager cannot be reopened 569 */ 570 @Override @DefinedBy(Api.COMPILER) 571 public FileObject getFileForInput(JavaFileManager.Location location, 572 String packageName, 573 String relativeName) 574 throws IOException { 575 proc.debug(DBG_FMGR, "getFileForInput location=%s packageName=%s\n", location, packageName); 576 return stdFileManager.getFileForInput(location, packageName, relativeName); 577 } 578 579 /** 580 * Returns a {@linkplain FileObject file object} for output 581 * representing the specified <a href="JavaFileManager.html#relative_name">relative 582 * name</a> in the specified package in the given location. 583 * 584 * <p>Optionally, this file manager might consider the sibling as 585 * a hint for where to place the output. The exact semantics of 586 * this hint is unspecified. The JDK compiler, javac, for 587 * example, will place class files in the same directories as 588 * originating source files unless a class file output directory 589 * is provided. To facilitate this behavior, javac might provide 590 * the originating source file as sibling when calling this 591 * method. 592 * 593 * <p>If the returned object represents a {@linkplain 594 * JavaFileObject.Kind#SOURCE source} or {@linkplain 595 * JavaFileObject.Kind#CLASS class} file, it must be an instance 596 * of {@link JavaFileObject}. 597 * 598 * <p>Informally, the file object returned by this method is 599 * located in the concatenation of the location, package name, and 600 * relative name or next to the sibling argument. See {@link 601 * #getFileForInput getFileForInput} for an example. 602 * 603 * @param location a location 604 * @param packageName a package name 605 * @param relativeName a relative name 606 * @param sibling a file object to be used as hint for placement; 607 * might be {@code null} 608 * @return a file object 609 * @throws IllegalArgumentException if sibling is not known to 610 * this file manager, or if the location is not known to this file 611 * manager and the file manager does not support unknown 612 * locations, or if {@code relativeName} is not valid 613 * @throws IOException if an I/O error occurred, or if {@link 614 * #close} has been called and this file manager cannot be 615 * reopened 616 * @throws IllegalStateException if {@link #close} has been called 617 * and this file manager cannot be reopened 618 */ 619 @Override @DefinedBy(Api.COMPILER) 620 public FileObject getFileForOutput(JavaFileManager.Location location, 621 String packageName, 622 String relativeName, 623 FileObject sibling) 624 throws IOException { 625 throw new UnsupportedOperationException("getFileForOutput: location: " + location + 626 ", packageName: " + packageName + 627 ", relativeName: " + relativeName + 628 ", sibling: " + sibling); 629 } 630 631 /** 632 * Flushes any resources opened for output by this file manager 633 * directly or indirectly. Flushing a closed file manager has no 634 * effect. 635 * 636 * @throws IOException if an I/O error occurred 637 * @see #close 638 */ 639 @Override @DefinedBy(Api.COMPILER) 640 public void flush() throws IOException { 641 // Nothing to flush 642 } 643 644 /** 645 * Releases any resources opened by this file manager directly or 646 * indirectly. This might render this file manager useless and 647 * the effect of subsequent calls to methods on this object or any 648 * objects obtained through this object is undefined unless 649 * explicitly allowed. However, closing a file manager which has 650 * already been closed has no effect. 651 * 652 * @throws IOException if an I/O error occurred 653 * @see #flush 654 */ 655 @Override @DefinedBy(Api.COMPILER) 656 public void close() throws IOException { 657 // Nothing to close 658 } 659} 660