/* * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.sjavac.server; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.net.Socket; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Map; import java.util.concurrent.Future; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.BaseFileManager; import com.sun.tools.javac.util.StringUtils; import com.sun.tools.sjavac.comp.Dependencies; import com.sun.tools.sjavac.comp.JavaCompilerWithDeps; import com.sun.tools.sjavac.comp.SmartFileManager; import com.sun.tools.sjavac.comp.ResolveWithDeps; /** * The compiler thread maintains a JavaCompiler instance and * can receive a request from the client, perform the compilation * requested and report back the results. * * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own * risk. This code and its internal interfaces are subject to change * or deletion without notice.</b></p> */ public class CompilerThread implements Runnable { private JavacServer javacServer; private CompilerPool compilerPool; private List<Future<?>> subTasks; // Communicating over this socket. private Socket socket; // The necessary classes to do a compilation. private com.sun.tools.javac.api.JavacTool compiler; private StandardJavaFileManager fileManager; private BaseFileManager fileManagerBase; private SmartFileManager smartFileManager; private Context context; // If true, then this thread is serving a request. private boolean inUse = false; CompilerThread(CompilerPool cp) { compilerPool = cp; javacServer = cp.getJavacServer(); } /** * Execute a minor task, for example generating bytecodes and writing them to disk, * that belong to a major compiler thread task. */ public synchronized void executeSubtask(Runnable r) { subTasks.add(compilerPool.executeSubtask(this, r)); } /** * Count the number of active sub tasks. */ public synchronized int numActiveSubTasks() { int c = 0; for (Future<?> f : subTasks) { if (!f.isDone() && !f.isCancelled()) { c++; } } return c; } /** * Use this socket for the upcoming request. */ public void setSocket(Socket s) { socket = s; } /** * Prepare the compiler thread for use. It is not yet started. * It will be started by the executor service. */ public synchronized void use() { assert(!inUse); inUse = true; compiler = com.sun.tools.javac.api.JavacTool.create(); fileManager = compiler.getStandardFileManager(null, null, null); fileManagerBase = (BaseFileManager)fileManager; smartFileManager = new SmartFileManager(fileManager); context = new Context(); context.put(JavaFileManager.class, smartFileManager); ResolveWithDeps.preRegister(context); JavaCompilerWithDeps.preRegister(context, this); subTasks = new ArrayList<Future<?>>(); } /** * Prepare the compiler thread for idleness. */ public synchronized void unuse() { assert(inUse); inUse = false; compiler = null; fileManager = null; fileManagerBase = null; smartFileManager = null; context = null; subTasks = null; } /** * Expect this key on the next line read from the reader. */ private static boolean expect(BufferedReader in, String key) throws IOException { String s = in.readLine(); if (s != null && s.equals(key)) { return true; } return false; } // The request identifier, for example GENERATE_NEWBYTECODE String id = ""; public String currentRequestId() { return id; } PrintWriter stdout; PrintWriter stderr; int forcedExitCode = 0; public void logError(String msg) { stderr.println(msg); forcedExitCode = -1; } /** * Invoked by the executor service. */ public void run() { // Unique nr that identifies this request. int thisRequest = compilerPool.startRequest(); long start = System.currentTimeMillis(); int numClasses = 0; StringBuilder compiledPkgs = new StringBuilder(); use(); PrintWriter out = null; try { javacServer.log("<"+thisRequest+"> Connect from "+socket.getRemoteSocketAddress()+" activethreads="+compilerPool.numActiveRequests()); BufferedReader in = new BufferedReader(new InputStreamReader( socket.getInputStream())); out = new PrintWriter(new OutputStreamWriter( socket.getOutputStream())); if (!expect(in, JavacServer.PROTOCOL_COOKIE_VERSION)) { javacServer.log("<"+thisRequest+"> Bad protocol from ip "+socket.getRemoteSocketAddress()); return; } String cookie = in.readLine(); if (cookie == null || !cookie.equals(""+javacServer.getCookie())) { javacServer.log("<"+thisRequest+"> Bad cookie from ip "+socket.getRemoteSocketAddress()); return; } if (!expect(in, JavacServer.PROTOCOL_CWD)) { return; } String cwd = in.readLine(); if (cwd == null) return; if (!expect(in, JavacServer.PROTOCOL_ID)) { return; } id = in.readLine(); if (id == null) return; if (!expect(in, JavacServer.PROTOCOL_ARGS)) { return; } ArrayList<String> the_options = new ArrayList<String>(); ArrayList<File> the_classes = new ArrayList<File>(); Iterable<File> path = Arrays.<File> asList(new File(cwd)); for (;;) { String l = in.readLine(); if (l == null) return; if (l.equals(JavacServer.PROTOCOL_SOURCES_TO_COMPILE)) break; if (l.startsWith("--server:")) continue; if (!l.startsWith("-") && l.endsWith(".java")) { the_classes.add(new File(l)); numClasses++; } else { the_options.add(l); } continue; } // Load sources to compile Set<URI> sourcesToCompile = new HashSet<URI>(); for (;;) { String l = in.readLine(); if (l == null) return; if (l.equals(JavacServer.PROTOCOL_VISIBLE_SOURCES)) break; try { sourcesToCompile.add(new URI(l)); numClasses++; } catch (URISyntaxException e) { return; } } // Load visible sources Set<URI> visibleSources = new HashSet<URI>(); boolean fix_drive_letter_case = StringUtils.toLowerCase(System.getProperty("os.name")).startsWith("windows"); for (;;) { String l = in.readLine(); if (l == null) return; if (l.equals(JavacServer.PROTOCOL_END)) break; try { URI u = new URI(l); if (fix_drive_letter_case) { // Make sure the driver letter is lower case. String s = u.toString(); if (s.startsWith("file:/") && Character.isUpperCase(s.charAt(6))) { u = new URI("file:/"+Character.toLowerCase(s.charAt(6))+s.substring(7)); } } visibleSources.add(u); } catch (URISyntaxException e) { return; } } // A completed request has been received. // Now setup the actual compilation.... // First deal with explicit source files on cmdline and in at file. com.sun.tools.javac.util.ListBuffer<JavaFileObject> compilationUnits = new com.sun.tools.javac.util.ListBuffer<JavaFileObject>(); for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(the_classes)) { compilationUnits.append(i); } // Now deal with sources supplied as source_to_compile. com.sun.tools.javac.util.ListBuffer<File> sourcesToCompileFiles = new com.sun.tools.javac.util.ListBuffer<File>(); for (URI u : sourcesToCompile) { sourcesToCompileFiles.append(new File(u)); } for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(sourcesToCompileFiles)) { compilationUnits.append(i); } // Log the options to be used. StringBuilder options = new StringBuilder(); for (String s : the_options) { options.append(">").append(s).append("< "); } javacServer.log(id+" <"+thisRequest+"> options "+options.toString()); forcedExitCode = 0; // Create a new logger. StringWriter stdoutLog = new StringWriter(); StringWriter stderrLog = new StringWriter(); stdout = new PrintWriter(stdoutLog); stderr = new PrintWriter(stderrLog); com.sun.tools.javac.main.Main.Result rc = com.sun.tools.javac.main.Main.Result.OK; try { if (compilationUnits.size() > 0) { // Bind the new logger to the existing context. context.put(Log.outKey, stderr); Log.instance(context).setWriter(Log.WriterKind.NOTICE, stdout); Log.instance(context).setWriter(Log.WriterKind.WARNING, stderr); Log.instance(context).setWriter(Log.WriterKind.ERROR, stderr); // Process the options. com.sun.tools.javac.api.JavacTool.processOptions(context, smartFileManager, the_options); fileManagerBase.setContext(context); smartFileManager.setVisibleSources(visibleSources); smartFileManager.cleanArtifacts(); smartFileManager.setLog(stdout); Dependencies.instance(context).reset(); com.sun.tools.javac.main.Main ccompiler = new com.sun.tools.javac.main.Main("javacTask", stderr); String[] aa = the_options.toArray(new String[0]); // Do the compilation! rc = ccompiler.compile(aa, context, compilationUnits.toList(), null); while (numActiveSubTasks()>0) { try { Thread.sleep(1000); } catch (InterruptedException e) { } } smartFileManager.flush(); } } catch (Exception e) { stderr.println(e.getMessage()); forcedExitCode = -1; } // Send the response.. out.println(JavacServer.PROTOCOL_STDOUT); out.print(stdoutLog); out.println(JavacServer.PROTOCOL_STDERR); out.print(stderrLog); // The compilation is complete! And errors will have already been printed on out! out.println(JavacServer.PROTOCOL_PACKAGE_ARTIFACTS); Map<String,Set<URI>> pa = smartFileManager.getPackageArtifacts(); for (String aPkgName : pa.keySet()) { out.println("+"+aPkgName); Set<URI> as = pa.get(aPkgName); for (URI a : as) { out.println(" "+a.toString()); } } Dependencies deps = Dependencies.instance(context); out.println(JavacServer.PROTOCOL_PACKAGE_DEPENDENCIES); Map<String,Set<String>> pd = deps.getDependencies(); for (String aPkgName : pd.keySet()) { out.println("+"+aPkgName); Set<String> ds = pd.get(aPkgName); // Everything depends on java.lang if (!ds.contains(":java.lang")) ds.add(":java.lang"); for (String d : ds) { out.println(" "+d); } } out.println(JavacServer.PROTOCOL_PACKAGE_PUBLIC_APIS); Map<String,String> pp = deps.getPubapis(); for (String aPkgName : pp.keySet()) { out.println("+"+aPkgName); String ps = pp.get(aPkgName); // getPubapis added a space to each line! out.println(ps); compiledPkgs.append(aPkgName+" "); } out.println(JavacServer.PROTOCOL_SYSINFO); out.println("num_cores=" + Runtime.getRuntime().availableProcessors()); out.println("max_memory=" + Runtime.getRuntime().maxMemory()); out.println(JavacServer.PROTOCOL_RETURN_CODE); // Errors from sjavac that affect compilation status! int rcv = rc.exitCode; if (rcv == 0 && forcedExitCode != 0) { rcv = forcedExitCode; } out.println("" + rcv); out.println(JavacServer.PROTOCOL_END); out.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (out != null) out.close(); if (!socket.isClosed()) { socket.close(); } socket = null; } catch (Exception e) { javacServer.log("ERROR "+e); e.printStackTrace(); } compilerPool.stopRequest(); long duration = System.currentTimeMillis()-start; javacServer.addBuildTime(duration); float classpersec = ((float)numClasses)*(((float)1000.0)/((float)duration)); javacServer.log(id+" <"+thisRequest+"> "+compiledPkgs+" duration " + duration+ " ms num_classes="+numClasses+ " classpersec="+classpersec+" subtasks="+subTasks.size()); javacServer.flushLog(); unuse(); compilerPool.returnCompilerThread(this); } } }