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