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