TaskFactory.java revision 3062:15bdc18525ff
1214921Scognet/*
2185222Ssam * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
3185222Ssam * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4185222Ssam *
5185222Ssam * This code is free software; you can redistribute it and/or modify it
6185222Ssam * under the terms of the GNU General Public License version 2 only, as
7185222Ssam * published by the Free Software Foundation.  Oracle designates this
8185222Ssam * particular file as subject to the "Classpath" exception as provided
9185222Ssam * by Oracle in the LICENSE file that accompanied this code.
10185222Ssam *
11185222Ssam * This code is distributed in the hope that it will be useful, but WITHOUT
12185222Ssam * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13185222Ssam * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14185222Ssam * version 2 for more details (a copy is included in the LICENSE file that
15185222Ssam * accompanied this code).
16185222Ssam *
17185222Ssam * You should have received a copy of the GNU General Public License version
18185222Ssam * 2 along with this work; if not, write to the Free Software Foundation,
19185222Ssam * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20185222Ssam *
21185222Ssam * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22185222Ssam * or visit www.oracle.com if you need additional information or have any
23185222Ssam * questions.
24185222Ssam */
25185222Ssam
26185222Ssampackage jdk.jshell;
27185222Ssam
28185222Ssamimport com.sun.source.tree.CompilationUnitTree;
29185222Ssamimport com.sun.source.tree.Tree;
30185222Ssamimport com.sun.source.util.Trees;
31185222Ssamimport com.sun.tools.javac.api.JavacTaskImpl;
32185222Ssamimport com.sun.tools.javac.api.JavacTool;
33185222Ssamimport com.sun.tools.javac.util.Context;
34185222Ssamimport java.util.ArrayList;
35185222Ssamimport java.util.Arrays;
36185222Ssamimport java.util.Iterator;
37185222Ssamimport java.util.List;
38185222Ssamimport javax.tools.Diagnostic;
39185222Ssamimport javax.tools.DiagnosticCollector;
40185222Ssamimport javax.tools.JavaCompiler;
41185222Ssamimport javax.tools.JavaFileManager;
42185222Ssamimport javax.tools.JavaFileObject;
43185222Ssamimport javax.tools.ToolProvider;
44185222Ssamimport static jdk.jshell.Util.*;
45185222Ssamimport com.sun.source.tree.ImportTree;
46185222Ssamimport com.sun.tools.javac.code.Types;
47185222Ssamimport com.sun.tools.javac.util.JavacMessages;
48185222Ssamimport jdk.jshell.MemoryFileManager.OutputMemoryJavaFileObject;
49185222Ssamimport java.util.Collections;
50185222Ssamimport java.util.Locale;
51185222Ssamimport static javax.tools.StandardLocation.CLASS_OUTPUT;
52185222Ssamimport static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
53185222Ssamimport java.io.File;
54185222Ssamimport java.util.Collection;
55185222Ssamimport java.util.HashMap;
56185222Ssamimport java.util.LinkedHashMap;
57185222Ssamimport java.util.Map;
58185222Ssamimport java.util.stream.Collectors;
59185222Ssamimport java.util.stream.Stream;
60185222Ssamimport javax.lang.model.util.Elements;
61185222Ssamimport javax.tools.FileObject;
62185222Ssamimport jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
63185222Ssamimport jdk.jshell.ClassTracker.ClassInfo;
64185222Ssam
65185222Ssam/**
66185222Ssam * The primary interface to the compiler API.  Parsing, analysis, and
67185222Ssam * compilation to class files (in memory).
68185222Ssam * @author Robert Field
69186334Ssam */
70185222Ssamclass TaskFactory {
71185222Ssam
72185222Ssam    private final JavaCompiler compiler;
73185222Ssam    private final MemoryFileManager fileManager;
74185222Ssam    private final JShell state;
75185222Ssam    private String classpath = System.getProperty("java.class.path");
76185222Ssam
77185222Ssam    TaskFactory(JShell state) {
78185222Ssam        this.state = state;
79214921Scognet        this.compiler = ToolProvider.getSystemJavaCompiler();
80185222Ssam        if (compiler == null) {
81185222Ssam            throw new UnsupportedOperationException("Compiler not available, must be run with full JDK 9.");
82185222Ssam        }
83217769Smckusick        if (!System.getProperty("java.specification.version").equals("1.9"))  {
84185222Ssam            throw new UnsupportedOperationException("Wrong compiler, must be run with full JDK 9.");
85185222Ssam        }
86185222Ssam        this.fileManager = new MemoryFileManager(
87214921Scognet                compiler.getStandardFileManager(null, null, null), state);
88185222Ssam    }
89214921Scognet
90214921Scognet    void addToClasspath(String path) {
91214921Scognet        classpath = classpath + File.pathSeparator + path;
92214921Scognet        List<String> args = new ArrayList<>();
93185222Ssam        args.add(classpath);
94185222Ssam        fileManager().handleOption("-classpath", args.iterator());
95185222Ssam    }
96185222Ssam
97214921Scognet    MemoryFileManager fileManager() {
98186261Ssam        return fileManager;
99185222Ssam    }
100185222Ssam
101185222Ssam    private interface SourceHandler<T> {
102185222Ssam
103185222Ssam        JavaFileObject sourceToFileObject(MemoryFileManager fm, T t);
104185222Ssam
105214921Scognet        Diag diag(Diagnostic<? extends JavaFileObject> d);
106185222Ssam    }
107185222Ssam
108185222Ssam    private class StringSourceHandler implements SourceHandler<String> {
109185222Ssam
110185222Ssam        @Override
111185222Ssam        public JavaFileObject sourceToFileObject(MemoryFileManager fm, String src) {
112185222Ssam            return fm.createSourceFileObject(src, "$NeverUsedName$", src);
113185222Ssam        }
114185222Ssam
115185222Ssam        @Override
116185222Ssam        public Diag diag(final Diagnostic<? extends JavaFileObject> d) {
117185222Ssam            return new Diag() {
118185222Ssam
119185222Ssam                @Override
120185222Ssam                public boolean isError() {
121185222Ssam                    return d.getKind() == Diagnostic.Kind.ERROR;
122185222Ssam                }
123185222Ssam
124185222Ssam                @Override
125185222Ssam                public long getPosition() {
126185222Ssam                    return d.getPosition();
127185222Ssam                }
128185222Ssam
129185222Ssam                @Override
130185222Ssam                public long getStartPosition() {
131185222Ssam                    return d.getStartPosition();
132185222Ssam                }
133185222Ssam
134185222Ssam                @Override
135185222Ssam                public long getEndPosition() {
136185222Ssam                    return d.getEndPosition();
137185222Ssam                }
138185222Ssam
139185222Ssam                @Override
140185222Ssam                public String getCode() {
141185222Ssam                    return d.getCode();
142185222Ssam                }
143185222Ssam
144185222Ssam                @Override
145185222Ssam                public String getMessage(Locale locale) {
146185222Ssam                    return expunge(d.getMessage(locale));
147185222Ssam                }
148248293Sbrooks
149185222Ssam                @Override
150214921Scognet                Unit unitOrNull() {
151214921Scognet                    return null;
152214921Scognet                }
153214921Scognet            };
154214921Scognet        }
155214921Scognet    }
156214921Scognet
157214921Scognet    private class WrapSourceHandler implements SourceHandler<OuterWrap> {
158214921Scognet
159214921Scognet        final OuterWrap wrap;
160214921Scognet
161214921Scognet        WrapSourceHandler(OuterWrap wrap) {
162214921Scognet            this.wrap = wrap;
163214921Scognet        }
164214921Scognet
165214921Scognet        @Override
166214921Scognet        public JavaFileObject sourceToFileObject(MemoryFileManager fm, OuterWrap w) {
167214921Scognet            return fm.createSourceFileObject(w, w.classFullName(), w.wrapped());
168214921Scognet        }
169214921Scognet
170214921Scognet        @Override
171214921Scognet        public Diag diag(Diagnostic<? extends JavaFileObject> d) {
172214921Scognet            return wrap.wrapDiag(d);
173214921Scognet        }
174214921Scognet    }
175214921Scognet
176214921Scognet    private class UnitSourceHandler implements SourceHandler<Unit> {
177214921Scognet
178214921Scognet        @Override
179214921Scognet        public JavaFileObject sourceToFileObject(MemoryFileManager fm, Unit u) {
180185222Ssam            return fm.createSourceFileObject(u,
181185222Ssam                    state.maps.classFullName(u.snippet()),
182185222Ssam                    u.snippet().outerWrap().wrapped());
183214921Scognet        }
184214921Scognet
185185222Ssam        @Override
186214921Scognet        public Diag diag(Diagnostic<? extends JavaFileObject> d) {
187185222Ssam            SourceMemoryJavaFileObject smjfo = (SourceMemoryJavaFileObject) d.getSource();
188214921Scognet            Unit u = (Unit) smjfo.getOrigin();
189185222Ssam            return u.snippet().outerWrap().wrapDiag(d);
190214921Scognet        }
191185222Ssam    }
192214921Scognet
193185222Ssam    /**
194214921Scognet     * Parse a snippet of code (as a String) using the parser subclass.  Return
195185222Ssam     * the parse tree (and errors).
196214921Scognet     */
197185222Ssam    class ParseTask extends BaseTask {
198214921Scognet
199185222Ssam        private final CompilationUnitTree cut;
200214921Scognet        private final List<? extends Tree> units;
201185222Ssam
202214921Scognet        ParseTask(final String source) {
203185222Ssam            super(Stream.of(source),
204214921Scognet                    new StringSourceHandler(),
205185222Ssam                    "-XDallowStringFolding=false", "-proc:none");
206214921Scognet            ReplParserFactory.instance(getContext());
207185222Ssam            Iterable<? extends CompilationUnitTree> asts = parse();
208185222Ssam            Iterator<? extends CompilationUnitTree> it = asts.iterator();
209185222Ssam            if (it.hasNext()) {
210185222Ssam                this.cut = it.next();
211185222Ssam                List<? extends ImportTree> imps = cut.getImports();
212185222Ssam                this.units = !imps.isEmpty() ? imps : cut.getTypeDecls();
213185222Ssam            } else {
214214921Scognet                this.cut = null;
215185222Ssam                this.units = Collections.emptyList();
216185222Ssam            }
217185222Ssam        }
218185222Ssam
219185222Ssam        private Iterable<? extends CompilationUnitTree> parse() {
220185222Ssam            try {
221185222Ssam                return task.parse();
222185222Ssam            } catch (Exception ex) {
223185222Ssam                throw new InternalError("Exception during parse - " + ex.getMessage(), ex);
224185222Ssam            }
225185222Ssam        }
226185222Ssam
227185222Ssam        List<? extends Tree> units() {
228185222Ssam            return units;
229185222Ssam        }
230185222Ssam
231214921Scognet        @Override
232185222Ssam        CompilationUnitTree cuTree() {
233214921Scognet            return cut;
234185222Ssam        }
235185222Ssam    }
236185222Ssam
237185222Ssam    /**
238185222Ssam     * Run the normal "analyze()" pass of the compiler over the wrapped snippet.
239226275Snwhitehorn     */
240226275Snwhitehorn    class AnalyzeTask extends BaseTask {
241226275Snwhitehorn
242185222Ssam        private final CompilationUnitTree cut;
243185222Ssam
244185222Ssam        AnalyzeTask(final OuterWrap wrap) {
245185222Ssam            this(Stream.of(wrap),
246185222Ssam                    new WrapSourceHandler(wrap),
247185222Ssam                    "-XDshouldStopPolicy=FLOW", "-proc:none");
248185222Ssam        }
249185222Ssam
250185222Ssam        AnalyzeTask(final Collection<Unit> units) {
251185222Ssam            this(units.stream(), new UnitSourceHandler(),
252185222Ssam                    "-XDshouldStopPolicy=FLOW", "-Xlint:unchecked", "-proc:none");
253185222Ssam        }
254185222Ssam
255185222Ssam        <T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
256185222Ssam                String... extraOptions) {
257185222Ssam            super(stream, sourceHandler, extraOptions);
258185222Ssam            Iterator<? extends CompilationUnitTree> cuts = analyze().iterator();
259185222Ssam            if (cuts.hasNext()) {
260185222Ssam                this.cut = cuts.next();
261185222Ssam                //proc.debug("AnalyzeTask element=%s  cutp=%s  cut=%s\n", e, cutp, cut);
262185222Ssam            } else {
263185222Ssam                this.cut = null;
264185222Ssam                //proc.debug("AnalyzeTask -- no elements -- %s\n", getDiagnostics());
265185222Ssam            }
266185222Ssam        }
267185222Ssam
268185222Ssam        private Iterable<? extends CompilationUnitTree> analyze() {
269185222Ssam            try {
270185222Ssam                Iterable<? extends CompilationUnitTree> cuts = task.parse();
271185222Ssam                task.analyze();
272185222Ssam                return cuts;
273185222Ssam            } catch (Exception ex) {
274185222Ssam                throw new InternalError("Exception during analyze - " + ex.getMessage(), ex);
275185222Ssam            }
276185222Ssam        }
277185222Ssam
278185222Ssam        @Override
279185222Ssam        CompilationUnitTree cuTree() {
280185222Ssam            return cut;
281185222Ssam        }
282185222Ssam
283185222Ssam        Elements getElements() {
284185222Ssam            return task.getElements();
285185222Ssam        }
286185222Ssam
287185222Ssam        javax.lang.model.util.Types getTypes() {
288185222Ssam            return task.getTypes();
289185222Ssam        }
290185222Ssam    }
291185222Ssam
292185222Ssam    /**
293185222Ssam     * Unit the wrapped snippet to class files.
294185222Ssam     */
295185222Ssam    class CompileTask extends BaseTask {
296185222Ssam
297185222Ssam        private final Map<Unit, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
298185222Ssam
299185222Ssam        CompileTask(Collection<Unit> units) {
300185222Ssam            super(units.stream(), new UnitSourceHandler(),
301185222Ssam                    "-Xlint:unchecked", "-proc:none");
302185222Ssam        }
303185222Ssam
304185222Ssam        boolean compile() {
305185222Ssam            fileManager.registerClassFileCreationListener(this::listenForNewClassFile);
306185222Ssam            boolean result = task.call();
307185222Ssam            fileManager.registerClassFileCreationListener(null);
308185222Ssam            return result;
309185222Ssam        }
310185222Ssam
311185222Ssam
312185222Ssam        List<ClassInfo> classInfoList(Unit u) {
313185222Ssam            List<OutputMemoryJavaFileObject> l = classObjs.get(u);
314185222Ssam            if (l == null) return Collections.emptyList();
315185222Ssam            return l.stream()
316185222Ssam                    .map(fo -> state.classTracker.classInfo(fo.getName(), fo.getBytes()))
317185222Ssam                    .collect(Collectors.toList());
318185222Ssam        }
319185222Ssam
320185222Ssam        private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
321185222Ssam                String className, JavaFileObject.Kind kind, FileObject sibling) {
322185222Ssam            //debug("listenForNewClassFile %s loc=%s kind=%s\n", className, location, kind);
323214921Scognet            if (location == CLASS_OUTPUT) {
324185222Ssam                state.debug(DBG_GEN, "Compiler generating class %s\n", className);
325185222Ssam                Unit u = ((sibling instanceof SourceMemoryJavaFileObject)
326185222Ssam                        && (((SourceMemoryJavaFileObject) sibling).getOrigin() instanceof Unit))
327185222Ssam                        ? (Unit) ((SourceMemoryJavaFileObject) sibling).getOrigin()
328214921Scognet                        : null;
329185222Ssam                classObjs.compute(u, (k, v) -> (v == null)? new ArrayList<>() : v)
330185222Ssam                        .add(jfo);
331185222Ssam            }
332185222Ssam        }
333185222Ssam
334185222Ssam        @Override
335185222Ssam        CompilationUnitTree cuTree() {
336185222Ssam            throw new UnsupportedOperationException("Not supported.");
337185222Ssam        }
338214921Scognet    }
339214921Scognet
340214921Scognet    abstract class BaseTask {
341214921Scognet
342214921Scognet        final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
343214921Scognet        final JavacTaskImpl task;
344185222Ssam        private DiagList diags = null;
345214921Scognet        private final SourceHandler<?> sourceHandler;
346185222Ssam        private final Context context = new Context();
347214921Scognet        private Types types;
348214921Scognet        private JavacMessages messages;
349214921Scognet        private Trees trees;
350214921Scognet
351214921Scognet        private <T>BaseTask(Stream<T> inputs,
352214921Scognet                //BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator,
353214921Scognet                SourceHandler<T> sh,
354214921Scognet                String... extraOptions) {
355214921Scognet            this.sourceHandler = sh;
356185222Ssam            List<String> options = Arrays.asList(extraOptions);
357214921Scognet            Iterable<? extends JavaFileObject> compilationUnits = inputs
358214921Scognet                            .map(in -> sh.sourceToFileObject(fileManager, in))
359214921Scognet                            .collect(Collectors.toList());
360214921Scognet            this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
361214921Scognet                    fileManager, diagnostics, options, null,
362214921Scognet                    compilationUnits, context);
363185222Ssam        }
364185222Ssam
365185222Ssam        abstract CompilationUnitTree cuTree();
366185222Ssam
367185222Ssam        Diag diag(Diagnostic<? extends JavaFileObject> diag) {
368185222Ssam            return sourceHandler.diag(diag);
369185222Ssam        }
370185222Ssam
371185222Ssam        Context getContext() {
372185222Ssam            return context;
373185222Ssam        }
374185222Ssam
375185222Ssam        Types types() {
376185222Ssam            if (types == null) {
377185222Ssam                types = Types.instance(context);
378185222Ssam            }
379185222Ssam            return types;
380185222Ssam        }
381185222Ssam
382185222Ssam        JavacMessages messages() {
383185222Ssam            if (messages == null) {
384185222Ssam                messages = JavacMessages.instance(context);
385185222Ssam            }
386185222Ssam            return messages;
387185222Ssam        }
388185222Ssam
389185222Ssam        Trees trees() {
390214921Scognet            if (trees == null) {
391185222Ssam                trees = Trees.instance(task);
392214921Scognet            }
393214921Scognet            return trees;
394185222Ssam        }
395185222Ssam
396214921Scognet        // ------------------ diags functionality
397214921Scognet
398185222Ssam        DiagList getDiagnostics() {
399185222Ssam            if (diags == null) {
400214921Scognet                LinkedHashMap<String, Diag> diagMap = new LinkedHashMap<>();
401185222Ssam                for (Diagnostic<? extends JavaFileObject> in : diagnostics.getDiagnostics()) {
402214921Scognet                    Diag d = diag(in);
403185222Ssam                    String uniqueKey = d.getCode() + ":" + d.getPosition() + ":" + d.getMessage(null);
404185222Ssam                    diagMap.put(uniqueKey, d);
405185222Ssam                }
406185222Ssam                diags = new DiagList(diagMap.values());
407185222Ssam            }
408185222Ssam            return diags;
409185222Ssam        }
410185222Ssam
411214921Scognet        boolean hasErrors() {
412185222Ssam            return getDiagnostics().hasErrors();
413185222Ssam        }
414214921Scognet
415214921Scognet        String shortErrorMessage() {
416185222Ssam            StringBuilder sb = new StringBuilder();
417185222Ssam            for (Diag diag : getDiagnostics()) {
418185222Ssam                for (String line : diag.getMessage(null).split("\\r?\\n")) {
419185222Ssam                    if (!line.trim().startsWith("location:")) {
420185222Ssam                        sb.append(line);
421185222Ssam                    }
422185222Ssam                }
423185222Ssam            }
424214921Scognet            return sb.toString();
425214921Scognet        }
426214921Scognet
427214921Scognet        void debugPrintDiagnostics(String src) {
428214921Scognet            for (Diag diag : getDiagnostics()) {
429214921Scognet                state.debug(DBG_GEN, "ERROR --\n");
430185222Ssam                for (String line : diag.getMessage(null).split("\\r?\\n")) {
431185222Ssam                    if (!line.trim().startsWith("location:")) {
432185222Ssam                        state.debug(DBG_GEN, "%s\n", line);
433185222Ssam                    }
434185222Ssam                }
435185222Ssam                int start = (int) diag.getStartPosition();
436185222Ssam                int end = (int) diag.getEndPosition();
437214921Scognet                if (src != null) {
438214921Scognet                    String[] srcLines = src.split("\\r?\\n");
439185222Ssam                    for (String line : srcLines) {
440185222Ssam                        state.debug(DBG_GEN, "%s\n", line);
441185222Ssam                    }
442185222Ssam
443185222Ssam                    StringBuilder sb = new StringBuilder();
444185222Ssam                    for (int i = 0; i < start; ++i) {
445185222Ssam                        sb.append(' ');
446185222Ssam                    }
447185222Ssam                    sb.append('^');
448185222Ssam                    if (end > start) {
449185222Ssam                        for (int i = start + 1; i < end; ++i) {
450185222Ssam                            sb.append('-');
451185222Ssam                        }
452185222Ssam                        sb.append('^');
453214921Scognet                    }
454185222Ssam                    state.debug(DBG_GEN, "%s\n", sb.toString());
455214921Scognet                }
456185222Ssam                state.debug(DBG_GEN, "printDiagnostics start-pos = %d ==> %d -- wrap = %s\n",
457214921Scognet                        diag.getStartPosition(), start, this);
458185222Ssam                state.debug(DBG_GEN, "Code: %s\n", diag.getCode());
459214921Scognet                state.debug(DBG_GEN, "Pos: %d (%d - %d) -- %s\n", diag.getPosition(),
460185222Ssam                        diag.getStartPosition(), diag.getEndPosition(), diag.getMessage(null));
461185222Ssam            }
462185222Ssam        }
463185222Ssam    }
464185222Ssam
465185222Ssam}
466214921Scognet