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