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