1 /*
   2  * Copyright (c) 2012, 2014, 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 com.sun.tools.sjavac.server;
  27 
  28 import java.io.BufferedReader;
  29 import java.io.File;
  30 import java.io.IOException;
  31 import java.io.InputStreamReader;
  32 import java.io.OutputStreamWriter;
  33 import java.io.PrintWriter;
  34 import java.io.StringWriter;
  35 import java.net.Socket;
  36 import java.net.URI;
  37 import java.net.URISyntaxException;
  38 import java.util.ArrayList;
  39 import java.util.Arrays;
  40 import java.util.HashSet;
  41 import java.util.List;
  42 import java.util.Map;
  43 import java.util.Set;
  44 import java.util.concurrent.Future;
  45 
  46 import javax.tools.JavaCompiler.CompilationTask;
  47 import javax.tools.JavaFileObject;
  48 import javax.tools.StandardJavaFileManager;
  49 
  50 import com.sun.tools.javac.api.JavacTaskImpl;
  51 import com.sun.tools.javac.util.Context;
  52 import com.sun.tools.javac.util.ListBuffer;
  53 import com.sun.tools.javac.util.Options;
  54 import com.sun.tools.javac.util.StringUtils;
  55 import com.sun.tools.sjavac.comp.AttrWithDeps;
  56 import com.sun.tools.sjavac.comp.Dependencies;
  57 import com.sun.tools.sjavac.comp.JavaCompilerWithDeps;
  58 import com.sun.tools.sjavac.comp.JavacServiceImpl;
  59 import com.sun.tools.sjavac.comp.ResolveWithDeps;
  60 import com.sun.tools.sjavac.comp.SmartFileManager;
  61 
  62 /**
  63  * The compiler thread maintains a JavaCompiler instance and
  64  * can receive a request from the client, perform the compilation
  65  * requested and report back the results.
  66  *
  67  *  * <p><b>This is NOT part of any supported API.
  68  * If you write code that depends on this, you do so at your own
  69  * risk.  This code and its internal interfaces are subject to change
  70  * or deletion without notice.</b></p>
  71  */
  72 public class CompilerThread implements Runnable {
  73     private JavacServer javacServer;
  74     private CompilerPool compilerPool;
  75     private JavacServiceImpl javacServiceImpl;
  76     private List<Future<?>> subTasks;
  77 
  78     // Communicating over this socket.
  79     private Socket socket;
  80 
  81     // The necessary classes to do a compilation.
  82     private com.sun.tools.javac.api.JavacTool compiler;
  83     private StandardJavaFileManager fileManager;
  84     private SmartFileManager smartFileManager;
  85     private Context context;
  86 
  87     // If true, then this thread is serving a request.
  88     private boolean inUse = false;
  89 
  90     CompilerThread(CompilerPool cp, JavacServiceImpl javacServiceImpl) {
  91         compilerPool = cp;
  92         javacServer = cp.getJavacServer();
  93         this.javacServiceImpl = javacServiceImpl;
  94     }
  95 
  96     /**
  97      * Execute a minor task, for example generating bytecodes and writing them to disk,
  98      * that belong to a major compiler thread task.
  99      */
 100     public synchronized void executeSubtask(Runnable r) {
 101         subTasks.add(compilerPool.executeSubtask(this, r));
 102     }
 103 
 104     /**
 105      * Count the number of active sub tasks.
 106      */
 107     public synchronized int numActiveSubTasks() {
 108         int c = 0;
 109         for (Future<?> f : subTasks) {
 110             if (!f.isDone() && !f.isCancelled()) {
 111                 c++;
 112             }
 113         }
 114         return c;
 115     }
 116 
 117     /**
 118      * Use this socket for the upcoming request.
 119      */
 120     public void setSocket(Socket s) {
 121         socket = s;
 122     }
 123 
 124     /**
 125      * Prepare the compiler thread for use. It is not yet started.
 126      * It will be started by the executor service.
 127      */
 128     public synchronized void use() {
 129         assert(!inUse);
 130         inUse = true;
 131         compiler = com.sun.tools.javac.api.JavacTool.create();
 132         fileManager = compiler.getStandardFileManager(null, null, null);
 133         smartFileManager = new SmartFileManager(fileManager);
 134         context = new Context();
 135         ResolveWithDeps.preRegister(context);
 136         AttrWithDeps.preRegister(context);
 137         JavaCompilerWithDeps.preRegister(context, javacServiceImpl);
 138         subTasks = new ArrayList<>();
 139     }
 140 
 141     /**
 142      * Prepare the compiler thread for idleness.
 143      */
 144     public synchronized void unuse() {
 145         assert(inUse);
 146         inUse = false;
 147         compiler = null;
 148         fileManager = null;
 149         smartFileManager = null;
 150         context = null;
 151         subTasks = null;
 152     }
 153 
 154     /**
 155      * Expect this key on the next line read from the reader.
 156      */
 157     private static boolean expect(BufferedReader in, String key) throws IOException {
 158         String s = in.readLine();
 159         if (s != null && s.equals(key)) {
 160             return true;
 161         }
 162         return false;
 163     }
 164 
 165     // The request identifier, for example GENERATE_NEWBYTECODE
 166     String id = "";
 167 
 168     public String currentRequestId() {
 169         return id;
 170     }
 171 
 172     PrintWriter stdout;
 173     PrintWriter stderr;
 174     int forcedExitCode = 0;
 175 
 176     public void logError(String msg) {
 177         stderr.println(msg);
 178         forcedExitCode = -1;
 179     }
 180 
 181     /**
 182      * Invoked by the executor service.
 183      */
 184     public void run() {
 185         // Unique nr that identifies this request.
 186         int thisRequest = compilerPool.startRequest();
 187         long start = System.currentTimeMillis();
 188         int numClasses = 0;
 189         StringBuilder compiledPkgs = new StringBuilder();
 190         use();
 191 
 192         PrintWriter out = null;
 193         try {
 194             javacServer.log("<"+thisRequest+"> Connect from "+socket.getRemoteSocketAddress()+" activethreads="+compilerPool.numActiveRequests());
 195             BufferedReader in = new BufferedReader(new InputStreamReader(
 196                                                        socket.getInputStream()));
 197             out = new PrintWriter(new OutputStreamWriter(
 198                                                   socket.getOutputStream()));
 199             if (!expect(in, JavacServer.PROTOCOL_COOKIE_VERSION)) {
 200                 javacServer.log("<"+thisRequest+"> Bad protocol from ip "+socket.getRemoteSocketAddress());
 201                 return;
 202             }
 203 
 204             String cookie = in.readLine();
 205             if (cookie == null || !cookie.equals(""+javacServer.getCookie())) {
 206                 javacServer.log("<"+thisRequest+"> Bad cookie from ip "+socket.getRemoteSocketAddress());
 207                 return;
 208             }
 209             if (!expect(in, JavacServer.PROTOCOL_CWD)) {
 210                 return;
 211             }
 212             String cwd = in.readLine();
 213             if (cwd == null)
 214                 return;
 215             if (!expect(in, JavacServer.PROTOCOL_ID)) {
 216                 return;
 217             }
 218             id = in.readLine();
 219             if (id == null)
 220                 return;
 221             if (!expect(in, JavacServer.PROTOCOL_ARGS)) {
 222                 return;
 223             }
 224             ArrayList<String> the_options = new ArrayList<>();
 225             ArrayList<File> the_classes = new ArrayList<>();
 226             Iterable<File> path = Arrays.<File> asList(new File(cwd));
 227 
 228             for (;;) {
 229                 String l = in.readLine();
 230                 if (l == null)
 231                     return;
 232                 if (l.equals(JavacServer.PROTOCOL_SOURCES_TO_COMPILE))
 233                     break;
 234                 if (l.startsWith("--server:"))
 235                     continue;
 236                 if (!l.startsWith("-") && l.endsWith(".java")) {
 237                     the_classes.add(new File(l));
 238                     numClasses++;
 239                 } else {
 240                     the_options.add(l);
 241                 }
 242                 continue;
 243             }
 244 
 245             // Load sources to compile
 246             Set<URI> sourcesToCompile = new HashSet<>();
 247             for (;;) {
 248                 String l = in.readLine();
 249                 if (l == null)
 250                     return;
 251                 if (l.equals(JavacServer.PROTOCOL_VISIBLE_SOURCES))
 252                     break;
 253                 try {
 254                     sourcesToCompile.add(new URI(l));
 255                     numClasses++;
 256                 } catch (URISyntaxException e) {
 257                     return;
 258                 }
 259             }
 260             // Load visible sources
 261             Set<URI> visibleSources = new HashSet<>();
 262             boolean fix_drive_letter_case =
 263                 StringUtils.toLowerCase(System.getProperty("os.name")).startsWith("windows");
 264             for (;;) {
 265                 String l = in.readLine();
 266                 if (l == null)
 267                     return;
 268                 if (l.equals(JavacServer.PROTOCOL_END))
 269                     break;
 270                 try {
 271                     URI u = new URI(l);
 272                     if (fix_drive_letter_case) {
 273                         // Make sure the driver letter is lower case.
 274                         String s = u.toString();
 275                         if (s.startsWith("file:/") &&
 276                             Character.isUpperCase(s.charAt(6))) {
 277                             u = new URI("file:/"+Character.toLowerCase(s.charAt(6))+s.substring(7));
 278                         }
 279                     }
 280                     visibleSources.add(u);
 281                 } catch (URISyntaxException e) {
 282                     return;
 283                 }
 284             }
 285 
 286             // A completed request has been received.
 287 
 288             // Now setup the actual compilation....
 289             // First deal with explicit source files on cmdline and in at file.
 290             ListBuffer<JavaFileObject> compilationUnits = new ListBuffer<>();
 291             for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(the_classes)) {
 292                 compilationUnits.append(i);
 293             }
 294             // Now deal with sources supplied as source_to_compile.
 295             ListBuffer<File> sourcesToCompileFiles = new ListBuffer<>();
 296             for (URI u : sourcesToCompile) {
 297                 sourcesToCompileFiles.append(new File(u));
 298             }
 299             for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(sourcesToCompileFiles)) {
 300                 compilationUnits.append(i);
 301             }
 302             // Log the options to be used.
 303             StringBuilder options = new StringBuilder();
 304             for (String s : the_options) {
 305                 options.append(">").append(s).append("< ");
 306             }
 307             javacServer.log(id+" <"+thisRequest+"> options "+options.toString());
 308 
 309             forcedExitCode = 0;
 310             // Create a new logger.
 311             StringWriter stdoutLog = new StringWriter();
 312             StringWriter stderrLog = new StringWriter();
 313             stdout = new PrintWriter(stdoutLog);
 314             stderr = new PrintWriter(stderrLog);
 315             com.sun.tools.javac.main.Main.Result rc = com.sun.tools.javac.main.Main.Result.OK;
 316             try {
 317                 if (compilationUnits.size() > 0) {
 318                     smartFileManager.setVisibleSources(visibleSources);
 319                     smartFileManager.cleanArtifacts();
 320                     smartFileManager.setLog(stdout);
 321 
 322                     // Do the compilation!
 323                     CompilationTask task = compiler.getTask(stderr, smartFileManager, null, the_options, null, compilationUnits, context);
 324                     smartFileManager.setSymbolFileEnabled(!Options.instance(context).isSet("ignore.symbol.file"));
 325                     rc = ((JavacTaskImpl) task).doCall();
 326 
 327                     while (numActiveSubTasks()>0) {
 328                         try { Thread.sleep(1000); } catch (InterruptedException e) { }
 329                     }
 330 
 331                     smartFileManager.flush();
 332                 }
 333             } catch (Exception e) {
 334                 stderr.println(e.getMessage());
 335                 forcedExitCode = -1;
 336             }
 337 
 338             // Send the response..
 339             out.println(JavacServer.PROTOCOL_STDOUT);
 340             out.print(stdoutLog);
 341             out.println(JavacServer.PROTOCOL_STDERR);
 342             out.print(stderrLog);
 343             // The compilation is complete! And errors will have already been printed on out!
 344             out.println(JavacServer.PROTOCOL_PACKAGE_ARTIFACTS);
 345             Map<String,Set<URI>> pa = smartFileManager.getPackageArtifacts();
 346             for (String aPkgName : pa.keySet()) {
 347                 out.println("+"+aPkgName);
 348                 Set<URI> as = pa.get(aPkgName);
 349                 for (URI a : as) {
 350                     out.println(" "+a.toString());
 351                 }
 352             }
 353             Dependencies deps = Dependencies.instance(context);
 354             out.println(JavacServer.PROTOCOL_PACKAGE_DEPENDENCIES);
 355             Map<String,Set<String>> pd = deps.getDependencies();
 356             for (String aPkgName : pd.keySet()) {
 357                 out.println("+"+aPkgName);
 358                 Set<String> ds = pd.get(aPkgName);
 359                     // Everything depends on java.lang
 360                     if (!ds.contains(":java.lang")) ds.add(":java.lang");
 361                 for (String d : ds) {
 362                     out.println(" "+d);
 363                 }
 364             }
 365             out.println(JavacServer.PROTOCOL_PACKAGE_PUBLIC_APIS);
 366             Map<String,String> pp = deps.getPubapis();
 367             for (String aPkgName : pp.keySet()) {
 368                 out.println("+"+aPkgName);
 369                 String ps = pp.get(aPkgName);
 370                 // getPubapis added a space to each line!
 371                 out.println(ps);
 372                 compiledPkgs.append(aPkgName+" ");
 373             }
 374             out.println(JavacServer.PROTOCOL_SYSINFO);
 375             out.println("num_cores=" + Runtime.getRuntime().availableProcessors());
 376             out.println("max_memory=" + Runtime.getRuntime().maxMemory());
 377             out.println(JavacServer.PROTOCOL_RETURN_CODE);
 378 
 379             // Errors from sjavac that affect compilation status!
 380             int rcv = rc.exitCode;
 381             if (rcv == 0 && forcedExitCode != 0) {
 382                 rcv = forcedExitCode;
 383             }
 384             out.println("" + rcv);
 385             out.println(JavacServer.PROTOCOL_END);
 386             out.flush();
 387         } catch (IOException e) {
 388             e.printStackTrace();
 389         } finally {
 390             try {
 391                 if (out != null) out.close();
 392                 if (!socket.isClosed()) {
 393                     socket.close();
 394                 }
 395                 socket = null;
 396             } catch (Exception e) {
 397                 javacServer.log("ERROR "+e);
 398                 e.printStackTrace();
 399             }
 400             compilerPool.stopRequest();
 401             long duration = System.currentTimeMillis()-start;
 402             javacServer.addBuildTime(duration);
 403             float classpersec = ((float)numClasses)*(((float)1000.0)/((float)duration));
 404             javacServer.log(id+" <"+thisRequest+"> "+compiledPkgs+" duration " + duration+ " ms    num_classes="+numClasses+
 405                              "     classpersec="+classpersec+" subtasks="+subTasks.size());
 406             javacServer.flushLog();
 407             unuse();
 408             compilerPool.returnCompilerThread(this);
 409         }
 410     }
 411 }
 412