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