src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java
changeset 47216 71c04702a3d5
parent 43586 cc7a4eb79b29
child 47268 48ec75306997
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     1 /*
       
     2  * Copyright (c) 2014, 2017, 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 
       
    26 package jdk.jshell;
       
    27 
       
    28 import com.sun.source.tree.CompilationUnitTree;
       
    29 import com.sun.source.tree.Tree;
       
    30 import com.sun.source.util.Trees;
       
    31 import com.sun.tools.javac.api.JavacTaskImpl;
       
    32 import com.sun.tools.javac.api.JavacTool;
       
    33 import com.sun.tools.javac.util.Context;
       
    34 import java.util.ArrayList;
       
    35 import java.util.Arrays;
       
    36 import java.util.List;
       
    37 import javax.tools.Diagnostic;
       
    38 import javax.tools.DiagnosticCollector;
       
    39 import javax.tools.JavaCompiler;
       
    40 import javax.tools.JavaFileManager;
       
    41 import javax.tools.JavaFileObject;
       
    42 import javax.tools.ToolProvider;
       
    43 import static jdk.jshell.Util.*;
       
    44 import com.sun.source.tree.ImportTree;
       
    45 import com.sun.tools.javac.code.Types;
       
    46 import com.sun.tools.javac.util.JavacMessages;
       
    47 import jdk.jshell.MemoryFileManager.OutputMemoryJavaFileObject;
       
    48 import java.util.Collections;
       
    49 import java.util.Locale;
       
    50 import static javax.tools.StandardLocation.CLASS_OUTPUT;
       
    51 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
       
    52 import java.io.File;
       
    53 import java.util.Collection;
       
    54 import java.util.HashMap;
       
    55 import java.util.LinkedHashMap;
       
    56 import java.util.Map;
       
    57 import java.util.stream.Collectors;
       
    58 import static java.util.stream.Collectors.toList;
       
    59 import java.util.stream.Stream;
       
    60 import javax.lang.model.util.Elements;
       
    61 import javax.tools.FileObject;
       
    62 import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
       
    63 import java.lang.Runtime.Version;
       
    64 import com.sun.source.tree.Tree.Kind;
       
    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  */
       
    71 class 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.compareToIgnoreOptional(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     // Parse a snippet and return our parse task handler
       
   105     ParseTask parse(final String source) {
       
   106         ParseTask pt = state.taskFactory.new ParseTask(source, false);
       
   107         if (!pt.units().isEmpty()
       
   108                 && pt.units().get(0).getKind() == Kind.EXPRESSION_STATEMENT
       
   109                 && pt.getDiagnostics().hasOtherThanNotStatementErrors()) {
       
   110             // It failed, it may be an expression being incorrectly
       
   111             // parsed as having a leading type variable, example:   a < b
       
   112             // Try forcing interpretation as an expression
       
   113             ParseTask ept = state.taskFactory.new ParseTask(source, true);
       
   114             if (!ept.getDiagnostics().hasOtherThanNotStatementErrors()) {
       
   115                 return ept;
       
   116             }
       
   117         }
       
   118         return pt;
       
   119     }
       
   120 
       
   121     private interface SourceHandler<T> {
       
   122 
       
   123         JavaFileObject sourceToFileObject(MemoryFileManager fm, T t);
       
   124 
       
   125         Diag diag(Diagnostic<? extends JavaFileObject> d);
       
   126     }
       
   127 
       
   128     private class StringSourceHandler implements SourceHandler<String> {
       
   129 
       
   130         @Override
       
   131         public JavaFileObject sourceToFileObject(MemoryFileManager fm, String src) {
       
   132             return fm.createSourceFileObject(src, "$NeverUsedName$", src);
       
   133         }
       
   134 
       
   135         @Override
       
   136         public Diag diag(final Diagnostic<? extends JavaFileObject> d) {
       
   137             return new Diag() {
       
   138 
       
   139                 @Override
       
   140                 public boolean isError() {
       
   141                     return d.getKind() == Diagnostic.Kind.ERROR;
       
   142                 }
       
   143 
       
   144                 @Override
       
   145                 public long getPosition() {
       
   146                     return d.getPosition();
       
   147                 }
       
   148 
       
   149                 @Override
       
   150                 public long getStartPosition() {
       
   151                     return d.getStartPosition();
       
   152                 }
       
   153 
       
   154                 @Override
       
   155                 public long getEndPosition() {
       
   156                     return d.getEndPosition();
       
   157                 }
       
   158 
       
   159                 @Override
       
   160                 public String getCode() {
       
   161                     return d.getCode();
       
   162                 }
       
   163 
       
   164                 @Override
       
   165                 public String getMessage(Locale locale) {
       
   166                     return expunge(d.getMessage(locale));
       
   167                 }
       
   168             };
       
   169         }
       
   170     }
       
   171 
       
   172     private class WrapSourceHandler implements SourceHandler<OuterWrap> {
       
   173 
       
   174         @Override
       
   175         public JavaFileObject sourceToFileObject(MemoryFileManager fm, OuterWrap w) {
       
   176             return fm.createSourceFileObject(w, w.classFullName(), w.wrapped());
       
   177         }
       
   178 
       
   179         @Override
       
   180         public Diag diag(Diagnostic<? extends JavaFileObject> d) {
       
   181             SourceMemoryJavaFileObject smjfo = (SourceMemoryJavaFileObject) d.getSource();
       
   182             if (smjfo == null) {
       
   183                 // Handle failure that doesn't preserve mapping
       
   184                 return new StringSourceHandler().diag(d);
       
   185             }
       
   186             OuterWrap w = (OuterWrap) smjfo.getOrigin();
       
   187             return w.wrapDiag(d);
       
   188         }
       
   189     }
       
   190 
       
   191     /**
       
   192      * Parse a snippet of code (as a String) using the parser subclass.  Return
       
   193      * the parse tree (and errors).
       
   194      */
       
   195     class ParseTask extends BaseTask {
       
   196 
       
   197         private final Iterable<? extends CompilationUnitTree> cuts;
       
   198         private final List<? extends Tree> units;
       
   199 
       
   200         ParseTask(final String source, final boolean forceExpression) {
       
   201             super(Stream.of(source),
       
   202                     new StringSourceHandler(),
       
   203                     "-XDallowStringFolding=false", "-proc:none");
       
   204             ReplParserFactory.preRegister(getContext(), forceExpression);
       
   205             cuts = parse();
       
   206             units = Util.stream(cuts)
       
   207                     .flatMap(cut -> {
       
   208                         List<? extends ImportTree> imps = cut.getImports();
       
   209                         return (!imps.isEmpty() ? imps : cut.getTypeDecls()).stream();
       
   210                     })
       
   211                     .collect(toList());
       
   212         }
       
   213 
       
   214         private Iterable<? extends CompilationUnitTree> parse() {
       
   215             try {
       
   216                 return task.parse();
       
   217             } catch (Exception ex) {
       
   218                 throw new InternalError("Exception during parse - " + ex.getMessage(), ex);
       
   219             }
       
   220         }
       
   221 
       
   222         List<? extends Tree> units() {
       
   223             return units;
       
   224         }
       
   225 
       
   226         @Override
       
   227         Iterable<? extends CompilationUnitTree> cuTrees() {
       
   228             return cuts;
       
   229         }
       
   230     }
       
   231 
       
   232     /**
       
   233      * Run the normal "analyze()" pass of the compiler over the wrapped snippet.
       
   234      */
       
   235     class AnalyzeTask extends BaseTask {
       
   236 
       
   237         private final Iterable<? extends CompilationUnitTree> cuts;
       
   238 
       
   239         AnalyzeTask(final OuterWrap wrap, String... extraArgs) {
       
   240             this(Collections.singletonList(wrap), extraArgs);
       
   241         }
       
   242 
       
   243         AnalyzeTask(final Collection<OuterWrap> wraps, String... extraArgs) {
       
   244             this(wraps.stream(),
       
   245                     new WrapSourceHandler(),
       
   246                     Util.join(new String[] {
       
   247                         "--should-stop:at=FLOW", "-Xlint:unchecked",
       
   248                         "-proc:none"
       
   249                     }, extraArgs));
       
   250         }
       
   251 
       
   252         private <T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
       
   253                 String... extraOptions) {
       
   254             super(stream, sourceHandler, extraOptions);
       
   255             cuts = analyze();
       
   256         }
       
   257 
       
   258         private Iterable<? extends CompilationUnitTree> analyze() {
       
   259             try {
       
   260                 Iterable<? extends CompilationUnitTree> cuts = task.parse();
       
   261                 task.analyze();
       
   262                 return cuts;
       
   263             } catch (Exception ex) {
       
   264                 throw new InternalError("Exception during analyze - " + ex.getMessage(), ex);
       
   265             }
       
   266         }
       
   267 
       
   268         @Override
       
   269         Iterable<? extends CompilationUnitTree> cuTrees() {
       
   270             return cuts;
       
   271         }
       
   272 
       
   273         Elements getElements() {
       
   274             return task.getElements();
       
   275         }
       
   276 
       
   277         javax.lang.model.util.Types getTypes() {
       
   278             return task.getTypes();
       
   279         }
       
   280     }
       
   281 
       
   282     /**
       
   283      * Unit the wrapped snippet to class files.
       
   284      */
       
   285     class CompileTask extends BaseTask {
       
   286 
       
   287         private final Map<OuterWrap, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
       
   288 
       
   289         CompileTask(final Collection<OuterWrap> wraps) {
       
   290             super(wraps.stream(), new WrapSourceHandler(),
       
   291                     "-Xlint:unchecked", "-proc:none", "-parameters");
       
   292         }
       
   293 
       
   294         boolean compile() {
       
   295             fileManager.registerClassFileCreationListener(this::listenForNewClassFile);
       
   296             boolean result = task.call();
       
   297             fileManager.registerClassFileCreationListener(null);
       
   298             return result;
       
   299         }
       
   300 
       
   301         // Returns the list of classes generated during this compile.
       
   302         // Stores the mapping between class name and current compiled bytes.
       
   303         List<String> classList(OuterWrap w) {
       
   304             List<OutputMemoryJavaFileObject> l = classObjs.get(w);
       
   305             if (l == null) {
       
   306                 return Collections.emptyList();
       
   307             }
       
   308             List<String> list = new ArrayList<>();
       
   309             for (OutputMemoryJavaFileObject fo : l) {
       
   310                 state.classTracker.setCurrentBytes(fo.getName(), fo.getBytes());
       
   311                 list.add(fo.getName());
       
   312             }
       
   313             return list;
       
   314         }
       
   315 
       
   316         private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
       
   317                 String className, JavaFileObject.Kind kind, FileObject sibling) {
       
   318             //debug("listenForNewClassFile %s loc=%s kind=%s\n", className, location, kind);
       
   319             if (location == CLASS_OUTPUT) {
       
   320                 state.debug(DBG_GEN, "Compiler generating class %s\n", className);
       
   321                 OuterWrap w = ((sibling instanceof SourceMemoryJavaFileObject)
       
   322                         && (((SourceMemoryJavaFileObject) sibling).getOrigin() instanceof OuterWrap))
       
   323                         ? (OuterWrap) ((SourceMemoryJavaFileObject) sibling).getOrigin()
       
   324                         : null;
       
   325                 classObjs.compute(w, (k, v) -> (v == null)? new ArrayList<>() : v)
       
   326                         .add(jfo);
       
   327             }
       
   328         }
       
   329 
       
   330         @Override
       
   331         Iterable<? extends CompilationUnitTree> cuTrees() {
       
   332             throw new UnsupportedOperationException("Not supported.");
       
   333         }
       
   334     }
       
   335 
       
   336     abstract class BaseTask {
       
   337 
       
   338         final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
       
   339         final JavacTaskImpl task;
       
   340         private DiagList diags = null;
       
   341         private final SourceHandler<?> sourceHandler;
       
   342         final Context context = new Context();
       
   343         private Types types;
       
   344         private JavacMessages messages;
       
   345         private Trees trees;
       
   346 
       
   347         private <T>BaseTask(Stream<T> inputs,
       
   348                 //BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator,
       
   349                 SourceHandler<T> sh,
       
   350                 String... extraOptions) {
       
   351             this.sourceHandler = sh;
       
   352             List<String> options = new ArrayList<>(extraOptions.length + state.extraCompilerOptions.size());
       
   353             options.addAll(Arrays.asList(extraOptions));
       
   354             options.addAll(state.extraCompilerOptions);
       
   355             Iterable<? extends JavaFileObject> compilationUnits = inputs
       
   356                             .map(in -> sh.sourceToFileObject(fileManager, in))
       
   357                             .collect(Collectors.toList());
       
   358             this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
       
   359                     fileManager, diagnostics, options, null,
       
   360                     compilationUnits, context);
       
   361         }
       
   362 
       
   363         abstract Iterable<? extends CompilationUnitTree> cuTrees();
       
   364 
       
   365         CompilationUnitTree firstCuTree() {
       
   366             return cuTrees().iterator().next();
       
   367         }
       
   368 
       
   369         Diag diag(Diagnostic<? extends JavaFileObject> diag) {
       
   370             return sourceHandler.diag(diag);
       
   371         }
       
   372 
       
   373         Context getContext() {
       
   374             return context;
       
   375         }
       
   376 
       
   377         Types types() {
       
   378             if (types == null) {
       
   379                 types = Types.instance(context);
       
   380             }
       
   381             return types;
       
   382         }
       
   383 
       
   384         JavacMessages messages() {
       
   385             if (messages == null) {
       
   386                 messages = JavacMessages.instance(context);
       
   387             }
       
   388             return messages;
       
   389         }
       
   390 
       
   391         Trees trees() {
       
   392             if (trees == null) {
       
   393                 trees = Trees.instance(task);
       
   394             }
       
   395             return trees;
       
   396         }
       
   397 
       
   398         // ------------------ diags functionality
       
   399 
       
   400         DiagList getDiagnostics() {
       
   401             if (diags == null) {
       
   402                 LinkedHashMap<String, Diag> diagMap = new LinkedHashMap<>();
       
   403                 for (Diagnostic<? extends JavaFileObject> in : diagnostics.getDiagnostics()) {
       
   404                     Diag d = diag(in);
       
   405                     String uniqueKey = d.getCode() + ":" + d.getPosition() + ":" + d.getMessage(PARSED_LOCALE);
       
   406                     diagMap.put(uniqueKey, d);
       
   407                 }
       
   408                 diags = new DiagList(diagMap.values());
       
   409             }
       
   410             return diags;
       
   411         }
       
   412 
       
   413         boolean hasErrors() {
       
   414             return getDiagnostics().hasErrors();
       
   415         }
       
   416 
       
   417         String shortErrorMessage() {
       
   418             StringBuilder sb = new StringBuilder();
       
   419             for (Diag diag : getDiagnostics()) {
       
   420                 for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
       
   421                     if (!line.trim().startsWith("location:")) {
       
   422                         sb.append(line);
       
   423                     }
       
   424                 }
       
   425             }
       
   426             return sb.toString();
       
   427         }
       
   428 
       
   429         void debugPrintDiagnostics(String src) {
       
   430             for (Diag diag : getDiagnostics()) {
       
   431                 state.debug(DBG_GEN, "ERROR --\n");
       
   432                 for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
       
   433                     if (!line.trim().startsWith("location:")) {
       
   434                         state.debug(DBG_GEN, "%s\n", line);
       
   435                     }
       
   436                 }
       
   437                 int start = (int) diag.getStartPosition();
       
   438                 int end = (int) diag.getEndPosition();
       
   439                 if (src != null) {
       
   440                     String[] srcLines = src.split("\\r?\\n");
       
   441                     for (String line : srcLines) {
       
   442                         state.debug(DBG_GEN, "%s\n", line);
       
   443                     }
       
   444 
       
   445                     StringBuilder sb = new StringBuilder();
       
   446                     for (int i = 0; i < start; ++i) {
       
   447                         sb.append(' ');
       
   448                     }
       
   449                     sb.append('^');
       
   450                     if (end > start) {
       
   451                         for (int i = start + 1; i < end; ++i) {
       
   452                             sb.append('-');
       
   453                         }
       
   454                         sb.append('^');
       
   455                     }
       
   456                     state.debug(DBG_GEN, "%s\n", sb.toString());
       
   457                 }
       
   458                 state.debug(DBG_GEN, "printDiagnostics start-pos = %d ==> %d -- wrap = %s\n",
       
   459                         diag.getStartPosition(), start, this);
       
   460                 state.debug(DBG_GEN, "Code: %s\n", diag.getCode());
       
   461                 state.debug(DBG_GEN, "Pos: %d (%d - %d) -- %s\n", diag.getPosition(),
       
   462                         diag.getStartPosition(), diag.getEndPosition(), diag.getMessage(null));
       
   463             }
       
   464         }
       
   465     }
       
   466 
       
   467 }