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