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