TaskFactory.java revision 3871:9ed8e9a27b00
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.compareToIgnoreOptional(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            if (smjfo == null) {
165                // Handle failure that doesn't preserve mapping
166                return new StringSourceHandler().diag(d);
167            }
168            OuterWrap w = (OuterWrap) smjfo.getOrigin();
169            return w.wrapDiag(d);
170        }
171    }
172
173    /**
174     * Parse a snippet of code (as a String) using the parser subclass.  Return
175     * the parse tree (and errors).
176     */
177    class ParseTask extends BaseTask {
178
179        private final Iterable<? extends CompilationUnitTree> cuts;
180        private final List<? extends Tree> units;
181
182        ParseTask(final String source) {
183            super(Stream.of(source),
184                    new StringSourceHandler(),
185                    "-XDallowStringFolding=false", "-proc:none");
186            ReplParserFactory.instance(getContext());
187            cuts = parse();
188            units = Util.stream(cuts)
189                    .flatMap(cut -> {
190                        List<? extends ImportTree> imps = cut.getImports();
191                        return (!imps.isEmpty() ? imps : cut.getTypeDecls()).stream();
192                    })
193                    .collect(toList());
194        }
195
196        private Iterable<? extends CompilationUnitTree> parse() {
197            try {
198                return task.parse();
199            } catch (Exception ex) {
200                throw new InternalError("Exception during parse - " + ex.getMessage(), ex);
201            }
202        }
203
204        List<? extends Tree> units() {
205            return units;
206        }
207
208        @Override
209        Iterable<? extends CompilationUnitTree> cuTrees() {
210            return cuts;
211        }
212    }
213
214    /**
215     * Run the normal "analyze()" pass of the compiler over the wrapped snippet.
216     */
217    class AnalyzeTask extends BaseTask {
218
219        private final Iterable<? extends CompilationUnitTree> cuts;
220
221        AnalyzeTask(final OuterWrap wrap, String... extraArgs) {
222            this(Collections.singletonList(wrap), extraArgs);
223        }
224
225        AnalyzeTask(final Collection<OuterWrap> wraps, String... extraArgs) {
226            this(wraps.stream(),
227                    new WrapSourceHandler(),
228                    Util.join(new String[] {
229                        "--should-stop:at=FLOW", "-Xlint:unchecked",
230                        "-proc:none"
231                    }, extraArgs));
232        }
233
234        private <T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
235                String... extraOptions) {
236            super(stream, sourceHandler, extraOptions);
237            cuts = analyze();
238        }
239
240        private Iterable<? extends CompilationUnitTree> analyze() {
241            try {
242                Iterable<? extends CompilationUnitTree> cuts = task.parse();
243                task.analyze();
244                return cuts;
245            } catch (Exception ex) {
246                throw new InternalError("Exception during analyze - " + ex.getMessage(), ex);
247            }
248        }
249
250        @Override
251        Iterable<? extends CompilationUnitTree> cuTrees() {
252            return cuts;
253        }
254
255        Elements getElements() {
256            return task.getElements();
257        }
258
259        javax.lang.model.util.Types getTypes() {
260            return task.getTypes();
261        }
262    }
263
264    /**
265     * Unit the wrapped snippet to class files.
266     */
267    class CompileTask extends BaseTask {
268
269        private final Map<OuterWrap, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
270
271        CompileTask(final Collection<OuterWrap> wraps) {
272            super(wraps.stream(), new WrapSourceHandler(),
273                    "-Xlint:unchecked", "-proc:none", "-parameters");
274        }
275
276        boolean compile() {
277            fileManager.registerClassFileCreationListener(this::listenForNewClassFile);
278            boolean result = task.call();
279            fileManager.registerClassFileCreationListener(null);
280            return result;
281        }
282
283        // Returns the list of classes generated during this compile.
284        // Stores the mapping between class name and current compiled bytes.
285        List<String> classList(OuterWrap w) {
286            List<OutputMemoryJavaFileObject> l = classObjs.get(w);
287            if (l == null) {
288                return Collections.emptyList();
289            }
290            List<String> list = new ArrayList<>();
291            for (OutputMemoryJavaFileObject fo : l) {
292                state.classTracker.setCurrentBytes(fo.getName(), fo.getBytes());
293                list.add(fo.getName());
294            }
295            return list;
296        }
297
298        private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
299                String className, JavaFileObject.Kind kind, FileObject sibling) {
300            //debug("listenForNewClassFile %s loc=%s kind=%s\n", className, location, kind);
301            if (location == CLASS_OUTPUT) {
302                state.debug(DBG_GEN, "Compiler generating class %s\n", className);
303                OuterWrap w = ((sibling instanceof SourceMemoryJavaFileObject)
304                        && (((SourceMemoryJavaFileObject) sibling).getOrigin() instanceof OuterWrap))
305                        ? (OuterWrap) ((SourceMemoryJavaFileObject) sibling).getOrigin()
306                        : null;
307                classObjs.compute(w, (k, v) -> (v == null)? new ArrayList<>() : v)
308                        .add(jfo);
309            }
310        }
311
312        @Override
313        Iterable<? extends CompilationUnitTree> cuTrees() {
314            throw new UnsupportedOperationException("Not supported.");
315        }
316    }
317
318    abstract class BaseTask {
319
320        final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
321        final JavacTaskImpl task;
322        private DiagList diags = null;
323        private final SourceHandler<?> sourceHandler;
324        final Context context = new Context();
325        private Types types;
326        private JavacMessages messages;
327        private Trees trees;
328
329        private <T>BaseTask(Stream<T> inputs,
330                //BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator,
331                SourceHandler<T> sh,
332                String... extraOptions) {
333            this.sourceHandler = sh;
334            List<String> options = new ArrayList<>(extraOptions.length + state.extraCompilerOptions.size());
335            options.addAll(Arrays.asList(extraOptions));
336            options.addAll(state.extraCompilerOptions);
337            Iterable<? extends JavaFileObject> compilationUnits = inputs
338                            .map(in -> sh.sourceToFileObject(fileManager, in))
339                            .collect(Collectors.toList());
340            this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
341                    fileManager, diagnostics, options, null,
342                    compilationUnits, context);
343        }
344
345        abstract Iterable<? extends CompilationUnitTree> cuTrees();
346
347        CompilationUnitTree firstCuTree() {
348            return cuTrees().iterator().next();
349        }
350
351        Diag diag(Diagnostic<? extends JavaFileObject> diag) {
352            return sourceHandler.diag(diag);
353        }
354
355        Context getContext() {
356            return context;
357        }
358
359        Types types() {
360            if (types == null) {
361                types = Types.instance(context);
362            }
363            return types;
364        }
365
366        JavacMessages messages() {
367            if (messages == null) {
368                messages = JavacMessages.instance(context);
369            }
370            return messages;
371        }
372
373        Trees trees() {
374            if (trees == null) {
375                trees = Trees.instance(task);
376            }
377            return trees;
378        }
379
380        // ------------------ diags functionality
381
382        DiagList getDiagnostics() {
383            if (diags == null) {
384                LinkedHashMap<String, Diag> diagMap = new LinkedHashMap<>();
385                for (Diagnostic<? extends JavaFileObject> in : diagnostics.getDiagnostics()) {
386                    Diag d = diag(in);
387                    String uniqueKey = d.getCode() + ":" + d.getPosition() + ":" + d.getMessage(PARSED_LOCALE);
388                    diagMap.put(uniqueKey, d);
389                }
390                diags = new DiagList(diagMap.values());
391            }
392            return diags;
393        }
394
395        boolean hasErrors() {
396            return getDiagnostics().hasErrors();
397        }
398
399        String shortErrorMessage() {
400            StringBuilder sb = new StringBuilder();
401            for (Diag diag : getDiagnostics()) {
402                for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
403                    if (!line.trim().startsWith("location:")) {
404                        sb.append(line);
405                    }
406                }
407            }
408            return sb.toString();
409        }
410
411        void debugPrintDiagnostics(String src) {
412            for (Diag diag : getDiagnostics()) {
413                state.debug(DBG_GEN, "ERROR --\n");
414                for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
415                    if (!line.trim().startsWith("location:")) {
416                        state.debug(DBG_GEN, "%s\n", line);
417                    }
418                }
419                int start = (int) diag.getStartPosition();
420                int end = (int) diag.getEndPosition();
421                if (src != null) {
422                    String[] srcLines = src.split("\\r?\\n");
423                    for (String line : srcLines) {
424                        state.debug(DBG_GEN, "%s\n", line);
425                    }
426
427                    StringBuilder sb = new StringBuilder();
428                    for (int i = 0; i < start; ++i) {
429                        sb.append(' ');
430                    }
431                    sb.append('^');
432                    if (end > start) {
433                        for (int i = start + 1; i < end; ++i) {
434                            sb.append('-');
435                        }
436                        sb.append('^');
437                    }
438                    state.debug(DBG_GEN, "%s\n", sb.toString());
439                }
440                state.debug(DBG_GEN, "printDiagnostics start-pos = %d ==> %d -- wrap = %s\n",
441                        diag.getStartPosition(), start, this);
442                state.debug(DBG_GEN, "Code: %s\n", diag.getCode());
443                state.debug(DBG_GEN, "Pos: %d (%d - %d) -- %s\n", diag.getPosition(),
444                        diag.getStartPosition(), diag.getEndPosition(), diag.getMessage(null));
445            }
446        }
447    }
448
449}
450