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 
  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 
  65 /**
  66  * The primary interface to the compiler API.  Parsing, analysis, and
  67  * compilation to class files (in memory).
  68  * @author Robert Field
  69  */
  70 class 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                         "-proc:none"
 227                     }, extraArgs));
 228         }
 229 
 230         private <T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
 231                 String... extraOptions) {
 232             super(stream, sourceHandler, extraOptions);
 233             cuts = analyze();
 234         }
 235 
 236         private Iterable<? extends CompilationUnitTree> analyze() {
 237             try {
 238                 Iterable<? extends CompilationUnitTree> cuts = task.parse();
 239                 task.analyze();
 240                 return cuts;
 241             } catch (Exception ex) {
 242                 throw new InternalError("Exception during analyze - " + ex.getMessage(), ex);
 243             }
 244         }
 245 
 246         @Override
 247         Iterable<? extends CompilationUnitTree> cuTrees() {
 248             return cuts;
 249         }
 250 
 251         Elements getElements() {
 252             return task.getElements();
 253         }
 254 
 255         javax.lang.model.util.Types getTypes() {
 256             return task.getTypes();
 257         }
 258     }
 259 
 260     /**
 261      * Unit the wrapped snippet to class files.
 262      */
 263     class CompileTask extends BaseTask {
 264 
 265         private final Map<OuterWrap, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
 266 
 267         CompileTask(final Collection<OuterWrap> wraps) {
 268             super(wraps.stream(), new WrapSourceHandler(),
 269                     "-Xlint:unchecked", "-proc:none", "-parameters");
 270         }
 271 
 272         boolean compile() {
 273             fileManager.registerClassFileCreationListener(this::listenForNewClassFile);
 274             boolean result = task.call();
 275             fileManager.registerClassFileCreationListener(null);
 276             return result;
 277         }
 278 
 279         // Returns the list of classes generated during this compile.
 280         // Stores the mapping between class name and current compiled bytes.
 281         List<String> classList(OuterWrap w) {
 282             List<OutputMemoryJavaFileObject> l = classObjs.get(w);
 283             if (l == null) {
 284                 return Collections.emptyList();
 285             }
 286             List<String> list = new ArrayList<>();
 287             for (OutputMemoryJavaFileObject fo : l) {
 288                 state.classTracker.setCurrentBytes(fo.getName(), fo.getBytes());
 289                 list.add(fo.getName());
 290             }
 291             return list;
 292         }
 293 
 294         private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
 295                 String className, JavaFileObject.Kind kind, FileObject sibling) {
 296             //debug("listenForNewClassFile %s loc=%s kind=%s\n", className, location, kind);
 297             if (location == CLASS_OUTPUT) {
 298                 state.debug(DBG_GEN, "Compiler generating class %s\n", className);
 299                 OuterWrap w = ((sibling instanceof SourceMemoryJavaFileObject)
 300                         && (((SourceMemoryJavaFileObject) sibling).getOrigin() instanceof OuterWrap))
 301                         ? (OuterWrap) ((SourceMemoryJavaFileObject) sibling).getOrigin()
 302                         : null;
 303                 classObjs.compute(w, (k, v) -> (v == null)? new ArrayList<>() : v)
 304                         .add(jfo);
 305             }
 306         }
 307 
 308         @Override
 309         Iterable<? extends CompilationUnitTree> cuTrees() {
 310             throw new UnsupportedOperationException("Not supported.");
 311         }
 312     }
 313 
 314     abstract class BaseTask {
 315 
 316         final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
 317         final JavacTaskImpl task;
 318         private DiagList diags = null;
 319         private final SourceHandler<?> sourceHandler;
 320         private final Context context = new Context();
 321         private Types types;
 322         private JavacMessages messages;
 323         private Trees trees;
 324 
 325         private <T>BaseTask(Stream<T> inputs,
 326                 //BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator,
 327                 SourceHandler<T> sh,
 328                 String... extraOptions) {
 329             this.sourceHandler = sh;
 330             List<String> options = new ArrayList<>(extraOptions.length + state.extraCompilerOptions.size());
 331             options.addAll(Arrays.asList(extraOptions));
 332             options.addAll(state.extraCompilerOptions);
 333             Iterable<? extends JavaFileObject> compilationUnits = inputs
 334                             .map(in -> sh.sourceToFileObject(fileManager, in))
 335                             .collect(Collectors.toList());
 336             this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
 337                     fileManager, diagnostics, options, null,
 338                     compilationUnits, context);
 339         }
 340 
 341         abstract Iterable<? extends CompilationUnitTree> cuTrees();
 342 
 343         CompilationUnitTree firstCuTree() {
 344             return cuTrees().iterator().next();
 345         }
 346 
 347         Diag diag(Diagnostic<? extends JavaFileObject> diag) {
 348             return sourceHandler.diag(diag);
 349         }
 350 
 351         Context getContext() {
 352             return context;
 353         }
 354 
 355         Types types() {
 356             if (types == null) {
 357                 types = Types.instance(context);
 358             }
 359             return types;
 360         }
 361 
 362         JavacMessages messages() {
 363             if (messages == null) {
 364                 messages = JavacMessages.instance(context);
 365             }
 366             return messages;
 367         }
 368 
 369         Trees trees() {
 370             if (trees == null) {
 371                 trees = Trees.instance(task);
 372             }
 373             return trees;
 374         }
 375 
 376         // ------------------ diags functionality
 377 
 378         DiagList getDiagnostics() {
 379             if (diags == null) {
 380                 LinkedHashMap<String, Diag> diagMap = new LinkedHashMap<>();
 381                 for (Diagnostic<? extends JavaFileObject> in : diagnostics.getDiagnostics()) {
 382                     Diag d = diag(in);
 383                     String uniqueKey = d.getCode() + ":" + d.getPosition() + ":" + d.getMessage(PARSED_LOCALE);
 384                     diagMap.put(uniqueKey, d);
 385                 }
 386                 diags = new DiagList(diagMap.values());
 387             }
 388             return diags;
 389         }
 390 
 391         boolean hasErrors() {
 392             return getDiagnostics().hasErrors();
 393         }
 394 
 395         String shortErrorMessage() {
 396             StringBuilder sb = new StringBuilder();
 397             for (Diag diag : getDiagnostics()) {
 398                 for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
 399                     if (!line.trim().startsWith("location:")) {
 400                         sb.append(line);
 401                     }
 402                 }
 403             }
 404             return sb.toString();
 405         }
 406 
 407         void debugPrintDiagnostics(String src) {
 408             for (Diag diag : getDiagnostics()) {
 409                 state.debug(DBG_GEN, "ERROR --\n");
 410                 for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
 411                     if (!line.trim().startsWith("location:")) {
 412                         state.debug(DBG_GEN, "%s\n", line);
 413                     }
 414                 }
 415                 int start = (int) diag.getStartPosition();
 416                 int end = (int) diag.getEndPosition();
 417                 if (src != null) {
 418                     String[] srcLines = src.split("\\r?\\n");
 419                     for (String line : srcLines) {
 420                         state.debug(DBG_GEN, "%s\n", line);
 421                     }
 422 
 423                     StringBuilder sb = new StringBuilder();
 424                     for (int i = 0; i < start; ++i) {
 425                         sb.append(' ');
 426                     }
 427                     sb.append('^');
 428                     if (end > start) {
 429                         for (int i = start + 1; i < end; ++i) {
 430                             sb.append('-');
 431                         }
 432                         sb.append('^');
 433                     }
 434                     state.debug(DBG_GEN, "%s\n", sb.toString());
 435                 }
 436                 state.debug(DBG_GEN, "printDiagnostics start-pos = %d ==> %d -- wrap = %s\n",
 437                         diag.getStartPosition(), start, this);
 438                 state.debug(DBG_GEN, "Code: %s\n", diag.getCode());
 439                 state.debug(DBG_GEN, "Pos: %d (%d - %d) -- %s\n", diag.getPosition(),
 440                         diag.getStartPosition(), diag.getEndPosition(), diag.getMessage(null));
 441             }
 442         }
 443     }
 444 
 445 }