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