TaskFactory.java revision 3294:9adfb22ff08f
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 com.sun.source.tree.CompilationUnitTree;
29import com.sun.source.tree.Tree;
30import com.sun.source.util.Trees;
31import com.sun.tools.javac.api.JavacTaskImpl;
32import com.sun.tools.javac.api.JavacTool;
33import com.sun.tools.javac.util.Context;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.List;
37import javax.tools.Diagnostic;
38import javax.tools.DiagnosticCollector;
39import javax.tools.JavaCompiler;
40import javax.tools.JavaFileManager;
41import javax.tools.JavaFileObject;
42import javax.tools.ToolProvider;
43import static jdk.jshell.Util.*;
44import com.sun.source.tree.ImportTree;
45import com.sun.tools.javac.code.Types;
46import com.sun.tools.javac.util.JavacMessages;
47import jdk.jshell.MemoryFileManager.OutputMemoryJavaFileObject;
48import java.util.Collections;
49import java.util.Locale;
50import static javax.tools.StandardLocation.CLASS_OUTPUT;
51import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
52import java.io.File;
53import java.util.Collection;
54import java.util.HashMap;
55import java.util.LinkedHashMap;
56import java.util.Map;
57import java.util.stream.Collectors;
58import static java.util.stream.Collectors.toList;
59import java.util.stream.Stream;
60import javax.lang.model.util.Elements;
61import javax.tools.FileObject;
62import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
63import jdk.jshell.ClassTracker.ClassInfo;
64import jdk.Version;
65
66/**
67 * The primary interface to the compiler API.  Parsing, analysis, and
68 * compilation to class files (in memory).
69 * @author Robert Field
70 */
71class TaskFactory {
72
73    private final JavaCompiler compiler;
74    private final MemoryFileManager fileManager;
75    private final JShell state;
76    private String classpath = System.getProperty("java.class.path");
77    private final static Version INITIAL_SUPPORTED_VER = Version.parse("9");
78
79    TaskFactory(JShell state) {
80        this.state = state;
81        this.compiler = ToolProvider.getSystemJavaCompiler();
82        if (compiler == null) {
83            throw new UnsupportedOperationException("Compiler not available, must be run with full JDK 9.");
84        }
85        Version current = Version.parse(System.getProperty("java.specification.version"));
86        if (INITIAL_SUPPORTED_VER.compareToIgnoreOpt(current) > 0)  {
87            throw new UnsupportedOperationException("Wrong compiler, must be run with full JDK 9.");
88        }
89        this.fileManager = new MemoryFileManager(
90                compiler.getStandardFileManager(null, null, null), state);
91    }
92
93    void addToClasspath(String path) {
94        classpath = classpath + File.pathSeparator + path;
95        List<String> args = new ArrayList<>();
96        args.add(classpath);
97        fileManager().handleOption("-classpath", args.iterator());
98    }
99
100    MemoryFileManager fileManager() {
101        return fileManager;
102    }
103
104    private interface SourceHandler<T> {
105
106        JavaFileObject sourceToFileObject(MemoryFileManager fm, T t);
107
108        Diag diag(Diagnostic<? extends JavaFileObject> d);
109    }
110
111    private class StringSourceHandler implements SourceHandler<String> {
112
113        @Override
114        public JavaFileObject sourceToFileObject(MemoryFileManager fm, String src) {
115            return fm.createSourceFileObject(src, "$NeverUsedName$", src);
116        }
117
118        @Override
119        public Diag diag(final Diagnostic<? extends JavaFileObject> d) {
120            return new Diag() {
121
122                @Override
123                public boolean isError() {
124                    return d.getKind() == Diagnostic.Kind.ERROR;
125                }
126
127                @Override
128                public long getPosition() {
129                    return d.getPosition();
130                }
131
132                @Override
133                public long getStartPosition() {
134                    return d.getStartPosition();
135                }
136
137                @Override
138                public long getEndPosition() {
139                    return d.getEndPosition();
140                }
141
142                @Override
143                public String getCode() {
144                    return d.getCode();
145                }
146
147                @Override
148                public String getMessage(Locale locale) {
149                    return expunge(d.getMessage(locale));
150                }
151
152                @Override
153                Unit unitOrNull() {
154                    return null;
155                }
156            };
157        }
158    }
159
160    private class WrapSourceHandler implements SourceHandler<OuterWrap> {
161
162        final OuterWrap wrap;
163
164        WrapSourceHandler(OuterWrap wrap) {
165            this.wrap = wrap;
166        }
167
168        @Override
169        public JavaFileObject sourceToFileObject(MemoryFileManager fm, OuterWrap w) {
170            return fm.createSourceFileObject(w, w.classFullName(), w.wrapped());
171        }
172
173        @Override
174        public Diag diag(Diagnostic<? extends JavaFileObject> d) {
175            return wrap.wrapDiag(d);
176        }
177    }
178
179    private class UnitSourceHandler implements SourceHandler<Unit> {
180
181        @Override
182        public JavaFileObject sourceToFileObject(MemoryFileManager fm, Unit u) {
183            return fm.createSourceFileObject(u,
184                    state.maps.classFullName(u.snippet()),
185                    u.snippet().outerWrap().wrapped());
186        }
187
188        @Override
189        public Diag diag(Diagnostic<? extends JavaFileObject> d) {
190            SourceMemoryJavaFileObject smjfo = (SourceMemoryJavaFileObject) d.getSource();
191            Unit u = (Unit) smjfo.getOrigin();
192            return u.snippet().outerWrap().wrapDiag(d);
193        }
194    }
195
196    /**
197     * Parse a snippet of code (as a String) using the parser subclass.  Return
198     * the parse tree (and errors).
199     */
200    class ParseTask extends BaseTask {
201
202        private final Iterable<? extends CompilationUnitTree> cuts;
203        private final List<? extends Tree> units;
204
205        ParseTask(final String source) {
206            super(Stream.of(source),
207                    new StringSourceHandler(),
208                    "-XDallowStringFolding=false", "-proc:none");
209            ReplParserFactory.instance(getContext());
210            cuts = parse();
211            units = Util.stream(cuts)
212                    .flatMap(cut -> {
213                        List<? extends ImportTree> imps = cut.getImports();
214                        return (!imps.isEmpty() ? imps : cut.getTypeDecls()).stream();
215                    })
216                    .collect(toList());
217        }
218
219        private Iterable<? extends CompilationUnitTree> parse() {
220            try {
221                return task.parse();
222            } catch (Exception ex) {
223                throw new InternalError("Exception during parse - " + ex.getMessage(), ex);
224            }
225        }
226
227        List<? extends Tree> units() {
228            return units;
229        }
230
231        @Override
232        Iterable<? extends CompilationUnitTree> cuTrees() {
233            return cuts;
234        }
235    }
236
237    /**
238     * Run the normal "analyze()" pass of the compiler over the wrapped snippet.
239     */
240    class AnalyzeTask extends BaseTask {
241
242        private final Iterable<? extends CompilationUnitTree> cuts;
243
244        AnalyzeTask(final OuterWrap wrap) {
245            this(Stream.of(wrap),
246                    new WrapSourceHandler(wrap),
247                    "-XDshouldStopPolicy=FLOW", "-proc:none");
248        }
249
250        AnalyzeTask(final Collection<Unit> units) {
251            this(units.stream(), new UnitSourceHandler(),
252                    "-XDshouldStopPolicy=FLOW", "-Xlint:unchecked", "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED", "-proc:none");
253        }
254
255        <T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
256                String... extraOptions) {
257            super(stream, sourceHandler, extraOptions);
258            cuts = analyze();
259        }
260
261        private Iterable<? extends CompilationUnitTree> analyze() {
262            try {
263                Iterable<? extends CompilationUnitTree> cuts = task.parse();
264                task.analyze();
265                return cuts;
266            } catch (Exception ex) {
267                throw new InternalError("Exception during analyze - " + ex.getMessage(), ex);
268            }
269        }
270
271        @Override
272        Iterable<? extends CompilationUnitTree> cuTrees() {
273            return cuts;
274        }
275
276        Elements getElements() {
277            return task.getElements();
278        }
279
280        javax.lang.model.util.Types getTypes() {
281            return task.getTypes();
282        }
283    }
284
285    /**
286     * Unit the wrapped snippet to class files.
287     */
288    class CompileTask extends BaseTask {
289
290        private final Map<Unit, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
291
292        CompileTask(Collection<Unit> units) {
293            super(units.stream(), new UnitSourceHandler(),
294                    "-Xlint:unchecked", "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED", "-proc:none");
295        }
296
297        boolean compile() {
298            fileManager.registerClassFileCreationListener(this::listenForNewClassFile);
299            boolean result = task.call();
300            fileManager.registerClassFileCreationListener(null);
301            return result;
302        }
303
304
305        List<ClassInfo> classInfoList(Unit u) {
306            List<OutputMemoryJavaFileObject> l = classObjs.get(u);
307            if (l == null) return Collections.emptyList();
308            return l.stream()
309                    .map(fo -> state.classTracker.classInfo(fo.getName(), fo.getBytes()))
310                    .collect(Collectors.toList());
311        }
312
313        private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
314                String className, JavaFileObject.Kind kind, FileObject sibling) {
315            //debug("listenForNewClassFile %s loc=%s kind=%s\n", className, location, kind);
316            if (location == CLASS_OUTPUT) {
317                state.debug(DBG_GEN, "Compiler generating class %s\n", className);
318                Unit u = ((sibling instanceof SourceMemoryJavaFileObject)
319                        && (((SourceMemoryJavaFileObject) sibling).getOrigin() instanceof Unit))
320                        ? (Unit) ((SourceMemoryJavaFileObject) sibling).getOrigin()
321                        : null;
322                classObjs.compute(u, (k, v) -> (v == null)? new ArrayList<>() : v)
323                        .add(jfo);
324            }
325        }
326
327        @Override
328        Iterable<? extends CompilationUnitTree> cuTrees() {
329            throw new UnsupportedOperationException("Not supported.");
330        }
331    }
332
333    abstract class BaseTask {
334
335        final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
336        final JavacTaskImpl task;
337        private DiagList diags = null;
338        private final SourceHandler<?> sourceHandler;
339        private final Context context = new Context();
340        private Types types;
341        private JavacMessages messages;
342        private Trees trees;
343
344        private <T>BaseTask(Stream<T> inputs,
345                //BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator,
346                SourceHandler<T> sh,
347                String... extraOptions) {
348            this.sourceHandler = sh;
349            List<String> options = Arrays.asList(extraOptions);
350            Iterable<? extends JavaFileObject> compilationUnits = inputs
351                            .map(in -> sh.sourceToFileObject(fileManager, in))
352                            .collect(Collectors.toList());
353            this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
354                    fileManager, diagnostics, options, null,
355                    compilationUnits, context);
356        }
357
358        abstract Iterable<? extends CompilationUnitTree> cuTrees();
359
360        CompilationUnitTree firstCuTree() {
361            return cuTrees().iterator().next();
362        }
363
364        Diag diag(Diagnostic<? extends JavaFileObject> diag) {
365            return sourceHandler.diag(diag);
366        }
367
368        Context getContext() {
369            return context;
370        }
371
372        Types types() {
373            if (types == null) {
374                types = Types.instance(context);
375            }
376            return types;
377        }
378
379        JavacMessages messages() {
380            if (messages == null) {
381                messages = JavacMessages.instance(context);
382            }
383            return messages;
384        }
385
386        Trees trees() {
387            if (trees == null) {
388                trees = Trees.instance(task);
389            }
390            return trees;
391        }
392
393        // ------------------ diags functionality
394
395        DiagList getDiagnostics() {
396            if (diags == null) {
397                LinkedHashMap<String, Diag> diagMap = new LinkedHashMap<>();
398                for (Diagnostic<? extends JavaFileObject> in : diagnostics.getDiagnostics()) {
399                    Diag d = diag(in);
400                    String uniqueKey = d.getCode() + ":" + d.getPosition() + ":" + d.getMessage(PARSED_LOCALE);
401                    diagMap.put(uniqueKey, d);
402                }
403                diags = new DiagList(diagMap.values());
404            }
405            return diags;
406        }
407
408        boolean hasErrors() {
409            return getDiagnostics().hasErrors();
410        }
411
412        String shortErrorMessage() {
413            StringBuilder sb = new StringBuilder();
414            for (Diag diag : getDiagnostics()) {
415                for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
416                    if (!line.trim().startsWith("location:")) {
417                        sb.append(line);
418                    }
419                }
420            }
421            return sb.toString();
422        }
423
424        void debugPrintDiagnostics(String src) {
425            for (Diag diag : getDiagnostics()) {
426                state.debug(DBG_GEN, "ERROR --\n");
427                for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
428                    if (!line.trim().startsWith("location:")) {
429                        state.debug(DBG_GEN, "%s\n", line);
430                    }
431                }
432                int start = (int) diag.getStartPosition();
433                int end = (int) diag.getEndPosition();
434                if (src != null) {
435                    String[] srcLines = src.split("\\r?\\n");
436                    for (String line : srcLines) {
437                        state.debug(DBG_GEN, "%s\n", line);
438                    }
439
440                    StringBuilder sb = new StringBuilder();
441                    for (int i = 0; i < start; ++i) {
442                        sb.append(' ');
443                    }
444                    sb.append('^');
445                    if (end > start) {
446                        for (int i = start + 1; i < end; ++i) {
447                            sb.append('-');
448                        }
449                        sb.append('^');
450                    }
451                    state.debug(DBG_GEN, "%s\n", sb.toString());
452                }
453                state.debug(DBG_GEN, "printDiagnostics start-pos = %d ==> %d -- wrap = %s\n",
454                        diag.getStartPosition(), start, this);
455                state.debug(DBG_GEN, "Code: %s\n", diag.getCode());
456                state.debug(DBG_GEN, "Pos: %d (%d - %d) -- %s\n", diag.getPosition(),
457                        diag.getStartPosition(), diag.getEndPosition(), diag.getMessage(null));
458            }
459        }
460    }
461
462}
463