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&nbsp;and&nbsp;Settings\UncleBob\src\share\classes"</code>,
 505      * a valid result would be a file object representing the file
 506      * <code>"C:\Documents&nbsp;and&nbsp;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 }