KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > java > ExternalCompiler


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  * Free SoftwareFoundation, Inc.
23  * 59 Temple Place, Suite 330
24  * Boston, MA 02111-1307 USA
25  *
26  * @author Scott Ferguson
27  */

28
29 package com.caucho.java;
30
31 import com.caucho.log.Log;
32 import com.caucho.server.util.CauchoSystem;
33 import com.caucho.util.Alarm;
34 import com.caucho.util.CharBuffer;
35 import com.caucho.vfs.*;
36
37 import java.io.IOException JavaDoc;
38 import java.io.InputStream JavaDoc;
39 import java.util.ArrayList JavaDoc;
40 import java.util.logging.Level JavaDoc;
41 import java.util.logging.Logger JavaDoc;
42
43 /**
44  * Compiles Java source, returning the loaded class.
45  */

46 public class ExternalCompiler extends AbstractJavaCompiler {
47   protected static final Logger JavaDoc log = Log.open(ExternalCompiler.class);
48   
49   Process JavaDoc _process;
50   String JavaDoc _userPrefix;
51   InputStream JavaDoc _errorStream;
52   InputStream JavaDoc _inputStream;
53   
54   boolean _isDead;
55   
56   public ExternalCompiler(JavaCompiler compiler)
57   {
58     super(compiler);
59   }
60
61   /**
62    * Compile the configured file.
63    *
64    * @param path the path to the java source.
65    * @param lineMap mapping from the generated source to the original files.
66    */

67   protected void compileInt(String JavaDoc []paths, LineMap lineMap)
68     throws IOException JavaDoc
69   {
70     MemoryStream tempStream = new MemoryStream();
71     WriteStream error = new WriteStream(tempStream);
72     _inputStream = null;
73     _errorStream = null;
74     boolean chdir = CauchoSystem.isUnix();
75
76     _process = null;
77
78     try {
79       String JavaDoc javac = _compiler.getCompiler();
80       String JavaDoc sourceExt = _compiler.getSourceExtension();
81
82       String JavaDoc path = paths[0];
83       int tail = path.length() - sourceExt.length();
84       String JavaDoc className = path.substring(0, tail);
85       Path classFile = _compiler.getClassDir().lookup(className + ".class");
86
87       ArrayList JavaDoc<String JavaDoc> argList = new ArrayList JavaDoc<String JavaDoc>();
88       argList.add(javac);
89
90       ArrayList JavaDoc<String JavaDoc> args = _compiler.getArgs();
91       if (args != null)
92     argList.addAll(args);
93
94       ArrayList JavaDoc<String JavaDoc> envList = new ArrayList JavaDoc();
95
96       if (javac.endsWith("jikes") || javac.endsWith("jikes.exe")) {
97         // make jikes look in the source path allowing for class dependencies
98
// argList.add("+CSO");
99
// "emacs" style error messages, understood by JavacErrorParser
100
argList.add("+E");
101
102         chdir = false;
103         
104         if (_compiler.getEncoding() != null) {
105           argList.add("-encoding");
106           argList.add(_compiler.getEncoding());
107         }
108         /*
109          * XXX: this encoding is needed for jikes to handle ISO-8859-1,
110          * but won't work on several jikes installations.
111         else {
112           argList.add("-encoding");
113           argList.add("LATIN1");
114         }
115         */

116       }
117       else if (_compiler.getEncoding() != null) {
118         String JavaDoc encoding = Encoding.getJavaName(_compiler.getEncoding());
119         argList.add("-encoding");
120         argList.add(encoding);
121       }
122
123       String JavaDoc classPath = normalizeClassPath(_compiler.getClassPath(), ! chdir);
124
125       envList.add("CLASSPATH=" + classPath);
126       
127       if (_compiler.getCompiler().endsWith("groovyc")) {
128     argList.add("--classpath");
129     argList.add(classPath);
130       }
131       else {
132     argList.add("-classpath");
133     argList.add(classPath);
134       }
135       
136       argList.add("-d");
137       argList.add(normalizePath(_compiler.getClassDirName(), ! chdir));
138
139       for (int i = 0; i < paths.length; i++) {
140     if (chdir)
141       argList.add(paths[i]);
142     else {
143       Path javaPath = _compiler.getSourceDir().lookup(paths[i]);
144       argList.add(javaPath.getNativePath());
145     }
146       }
147
148       if (log.isLoggable(Level.FINE))
149     log.fine(String.valueOf(argList));
150
151       _process = executeCompiler(argList, envList, chdir);
152       if (_process != null) {
153         _inputStream = _process.getInputStream();
154         _errorStream = _process.getErrorStream();
155       }
156
157       /*
158       Alarm alarm = null;
159       
160       if (_compiler.getMaxCompileTime() > 1000)
161         alarm = new Alarm(this, _maxCompileTime);
162       */

163
164       int status = 666;
165
166       try {
167         waitForErrors(error, _inputStream, _errorStream);
168         
169         if (_process != null) {
170           status = _process.waitFor();
171           _process = null;
172         }
173       } catch (Throwable JavaDoc e) {
174         if (_isDead)
175           throw new JavaCompileException(L.l("The compilation has timed out. You can increase the timeout value by changing the max-compile-time."));
176
177         throw new IOExceptionWrapper(e);
178       }
179       /*
180       finally {
181     
182         if (alarm != null)
183           alarm.dequeue();
184       }
185       */

186
187       if (_process != null) {
188         status = 666;
189       }
190
191       error.close();
192       tempStream.close();
193     
194       if (log.isLoggable(Level.FINE)) {
195     ReadStream read = tempStream.openRead();
196     CharBuffer cb = new CharBuffer();
197     int ch;
198
199     while ((ch = read.read()) >= 0)
200       cb.append((char) ch);
201     read.close();
202     final String JavaDoc msg = cb.toString();
203     
204     new com.caucho.loader.ClassLoaderContext(_compiler.getClassLoader()) {
205       public void run()
206       {
207         log.fine(msg);
208       }
209     };
210       }
211
212       ReadStream read = tempStream.openRead();
213       ErrorParser parser;
214         
215       // the javac error parser will work with jikes in "emacs" mode
216
parser = new JavacErrorParser();
217
218       String JavaDoc errors = parser.parseErrors(read, lineMap);
219       read.close();
220
221       if (errors != null)
222     errors = errors.trim();
223
224       if (status == 0 && classFile.getLength() > 0) {
225     if (errors != null && ! errors.equals("")) {
226       final String JavaDoc msg = errors;
227     
228       new com.caucho.loader.ClassLoaderContext(_compiler.getClassLoader()) {
229         public void run()
230         {
231           log.warning(msg);
232         }
233       };
234     }
235
236     return;
237       }
238       
239       if (errors == null || errors.equals("")) {
240     CharBuffer cb = new CharBuffer();
241           
242     if (status == 0) {
243       cb.append("Compilation for '" + className + "' did not generate a .class file.\n");
244       cb.append("Make sure the `package' matches the directory.\n");
245     }
246     else
247       cb.append("Unknown compiler error executing:\n");
248           
249     for (int i = 0; i < argList.size(); i++)
250       cb.append(" " + argList.get(i) + "\n");
251           
252     read = tempStream.openRead();
253     int ch;
254     while ((ch = read.read()) >= 0)
255       cb.append((char) ch);
256     read.close();
257     errors = cb.toString();
258       }
259       else if (errors.indexOf("command not found") >= 0) {
260     throw new JavaCompileException(L.l("Resin can't execute the compiler `{0}'. This usually means that the compiler is not in the operating system's PATH or the compiler is incorrectly specified in the configuration. You may need to add the full path to <java compiler='{0}'/>.\n\n{1}", argList.get(0), errors));
261       }
262
263       throw new JavaCompileException(errors);
264     } finally {
265       if (_inputStream != null) {
266     try {
267       _inputStream.close();
268     } catch (Throwable JavaDoc e) {
269     }
270       }
271       
272       if (_errorStream != null) {
273     try {
274       _errorStream.close();
275     } catch (Throwable JavaDoc e) {
276     }
277       }
278
279       if (_process != null) {
280         try {
281           _process.destroy();
282         } catch (Throwable JavaDoc e) {
283           log.log(Level.FINE, e.toString(), e);
284         }
285       }
286       
287       tempStream.destroy();
288     }
289   }
290
291   /**
292    * Read any errors from the process.
293    */

294   private void waitForErrors(WriteStream error,
295                              InputStream JavaDoc inputStream,
296                              InputStream JavaDoc errorStream)
297     throws IOException JavaDoc
298   {
299     byte []buffer = new byte[256];
300     int stderrLen;
301     int stdoutLen;
302
303     if (inputStream == null || errorStream == null)
304       return;
305     
306     do {
307       while ((stderrLen = errorStream.available()) > 0) {
308         stderrLen = errorStream.read(buffer, 0, buffer.length);
309         if (stderrLen <= 0)
310           break;
311
312         error.write(buffer, 0, stderrLen);
313       }
314       
315       while ((stdoutLen = inputStream.available()) > 0) {
316         stdoutLen = inputStream.read(buffer, 0, buffer.length);
317         if (stdoutLen <= 0)
318           break;
319
320         error.write(buffer, 0, stdoutLen);
321       }
322
323       if (stderrLen < 0 && stdoutLen < 0)
324         return;
325
326       if (stderrLen == 0) {
327         stderrLen = errorStream.read(buffer, 0, buffer.length);
328         if (stderrLen > 0)
329           error.write(buffer, 0, stderrLen);
330       }
331
332       if (stderrLen < 0 && stdoutLen == 0) {
333         stdoutLen = inputStream.read(buffer, 0, buffer.length);
334         if (stdoutLen > 0)
335           error.write(buffer, 0, stdoutLen);
336       }
337     } while (! _isDead && (stderrLen >= 0 || stdoutLen >= 0));
338   }
339
340   /**
341    * This callback should only occur if the compiler freezes. In that
342    * case we immediately kill the process.
343    *
344    * @param alarm the alarm we've been waiting for.
345    */

346   public void handleAlarm(Alarm alarm)
347   {
348     _isDead = true;
349     abort();
350   }
351
352   /**
353    * Aborts the compilation.
354    */

355   public void abort()
356   {
357     if (_inputStream != null) {
358       try {
359     _inputStream.close();
360       } catch (Throwable JavaDoc e) {
361       }
362     }
363       
364     if (_errorStream != null) {
365       try {
366     _errorStream.close();
367       } catch (Throwable JavaDoc e) {
368       }
369     }
370
371     if (_process != null) {
372       try {
373     _process.destroy();
374       } catch (Throwable JavaDoc e) {
375     log.log(Level.FINE, e.toString(), e);
376       }
377     }
378   }
379
380   /**
381    * Spawn the process to compile the file.
382    *
383    * @param argList compiler arguments.
384    * @param chdir if true, change to the compilation directory .
385    * @return a Java Process representing the compiler.
386    */

387   private Process JavaDoc executeCompiler(ArrayList JavaDoc<String JavaDoc> argList,
388                   ArrayList JavaDoc<String JavaDoc> envList,
389                   boolean chdir)
390     throws IOException JavaDoc
391   {
392     String JavaDoc []args;
393
394     // For unix, we can use sh to change the directory to get
395
// automatic compilation of aux files.
396
// Disabled because it's not needed with the source path?
397
if (chdir) {
398       CharBuffer cb = new CharBuffer();
399       cb.append("cd ");
400       cb.append(_compiler.getSourceDirName());
401       cb.append(";");
402       for (int i = 0; i < argList.size(); i++) {
403     cb.append(" ");
404     cb.append(argList.get(i));
405       }
406       args = new String JavaDoc[3];
407       args[0] = "/bin/sh";
408       args[1] = "-c";
409       args[2] = cb.toString();
410     }
411     else {
412       args = new String JavaDoc[argList.size()];
413       argList.toArray(args);
414     }
415     
416     String JavaDoc []envp = new String JavaDoc[envList.size()];
417     envList.toArray(envp);
418
419     if (log.isLoggable(Level.FINE)) {
420       CharBuffer cb = CharBuffer.allocate();
421       
422       for (int i = 0; i < args.length; i++) {
423         if (i != 0)
424           cb.append(" ");
425         cb.append(args[i]);
426       }
427       log.fine(cb.close());
428     }
429     
430     Runtime JavaDoc runtime = Runtime.getRuntime();
431
432     try {
433       return runtime.exec(args);
434     } catch (Exception JavaDoc e) {
435       throw new JavaCompileException(L.l("Resin can't execute the compiler `{0}'. This usually means that the compiler is not in the operating system's PATH or the compiler is incorrectly specified in the configuration. You may need to add the full path to <java compiler='{0}'/>.\n\n{1}", args[0], String.valueOf(e)));
436     }
437   }
438
439   /**
440    * Converts any relative classpath references to the full path.
441    */

442   String JavaDoc normalizeClassPath(String JavaDoc classPath, boolean generateRelative)
443   {
444     char sep = CauchoSystem.getPathSeparatorChar();
445     int head = 0;
446     int tail = 0;
447
448     CharBuffer cb = CharBuffer.allocate();
449
450     while (head < classPath.length()) {
451       tail = classPath.indexOf(sep, head);
452       if (tail < 0)
453         tail = classPath.length();
454
455       if (tail > head) {
456         String JavaDoc segment = classPath.substring(head, tail);
457
458     segment = normalizePath(segment, generateRelative);
459
460     if (segment != null) {
461       if (cb.length() != 0)
462         cb.append(sep);
463       
464       cb.append(segment);
465     }
466       }
467
468       head = tail + 1;
469     }
470
471     return cb.close();
472   }
473   /**
474    * Normalizes a path.
475    */

476   String JavaDoc normalizePath(String JavaDoc segment, boolean generateRelative)
477   {
478     if (_userPrefix == null) {
479       Path userPath = Vfs.lookup(CauchoSystem.getUserDir());
480       char sep = CauchoSystem.getFileSeparatorChar();
481       _userPrefix = userPath.getNativePath();
482       
483       if (_userPrefix.length() == 0 ||
484           _userPrefix.charAt(_userPrefix.length() - 1) != sep) {
485         _userPrefix = _userPrefix + sep;
486       }
487     }
488     
489     Path path = Vfs.lookup(segment);
490     
491     String JavaDoc nativePath = path.getNativePath();
492
493     if (! generateRelative)
494       return nativePath;
495
496     if (nativePath.startsWith(_userPrefix))
497       nativePath = nativePath.substring(_userPrefix.length());
498
499     if (nativePath.equals(""))
500       return ".";
501     else
502       return nativePath;
503   }
504
505   public static class CompilerThread implements Runnable JavaDoc {
506     private volatile boolean _isDone;
507     
508     public void run()
509     {
510       try {
511       } finally {
512     _isDone = true;
513
514     synchronized (this) {
515       notifyAll();
516     }
517       }
518     }
519   }
520 }
521
Popular Tags