MemoryFileManager.java revision 3770:d813bfb238a9
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
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 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}
612