001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.java;
031:
032: import com.caucho.bytecode.ByteCodeParser;
033: import com.caucho.bytecode.JavaClass;
034: import com.caucho.bytecode.SourceDebugExtensionAttribute;
035: import com.caucho.config.*;
036: import com.caucho.loader.DynamicClassLoader;
037: import com.caucho.log.Log;
038: import com.caucho.make.Make;
039: import com.caucho.server.util.CauchoSystem;
040: import com.caucho.util.CharBuffer;
041: import com.caucho.util.L10N;
042: import com.caucho.vfs.Encoding;
043: import com.caucho.vfs.IOExceptionWrapper;
044: import com.caucho.vfs.Path;
045: import com.caucho.vfs.ReadStream;
046: import com.caucho.vfs.WriteStream;
047:
048: import java.io.IOException;
049: import java.util.ArrayList;
050: import java.util.Arrays;
051: import java.util.logging.Level;
052: import java.util.logging.Logger;
053: import java.util.regex.Pattern;
054:
055: /**
056: * Compiles Java source, returning the loaded class.
057: */
058: public class JavaCompiler {
059: static final L10N L = new L10N(JavaCompiler.class);
060: static final Logger log = Log.open(JavaCompiler.class);
061:
062: private static final Object LOCK = new Object();
063:
064: // Parent class loader. Used to grab the classpath.
065: private ClassLoader _loader;
066:
067: // Executable name of the compiler
068: private String _compiler;
069:
070: private String _sourceExt = ".java";
071:
072: private Path _classDir;
073: private Path _sourceDir;
074:
075: private boolean _compileParent = true;
076:
077: private String _extraClassPath;
078: private String _classPath;
079:
080: protected String _charEncoding;
081: protected ArrayList<String> _args;
082:
083: private int _maxBatch = 64;
084: protected long _maxCompileTime = 120 * 1000L;
085:
086: private JavaCompiler() {
087: }
088:
089: /**
090: * Creates a new compiler.
091: */
092: public static JavaCompiler create() {
093: return create(Thread.currentThread().getContextClassLoader());
094: }
095:
096: /**
097: * Creates a new compiler.
098: *
099: * @param loader the parent class loader for the compiler.
100: */
101: public static JavaCompiler create(ClassLoader loader) {
102: JavacConfig config = JavacConfig.getLocalConfig();
103:
104: String javac = config.getCompiler();
105:
106: JavaCompiler javaCompiler = new JavaCompiler();
107:
108: if (loader == null)
109: loader = Thread.currentThread().getContextClassLoader();
110:
111: javaCompiler.setClassLoader(loader);
112: javaCompiler.setCompiler(javac);
113: javaCompiler.setArgs(config.getArgs());
114: javaCompiler.setEncoding(config.getEncoding());
115: javaCompiler.setMaxBatch(config.getMaxBatch());
116:
117: return javaCompiler;
118: }
119:
120: /**
121: * Sets the class loader used to load the compiled class and to grab
122: * the classpath from.
123: */
124: public void setClassLoader(ClassLoader loader) {
125: _loader = loader;
126: }
127:
128: /**
129: * Sets the class loader used to load the compiled class and to grab
130: * the classpath from.
131: */
132: public ClassLoader getClassLoader() {
133: return _loader;
134: }
135:
136: /**
137: * Sets the compiler name, e.g. jikes.
138: */
139: public void setCompiler(String compiler) {
140: _compiler = compiler;
141: }
142:
143: /**
144: * Gets the compiler name, e.g. jikes.
145: */
146: public String getCompiler() {
147: if (_compiler == null)
148: _compiler = "javac";
149:
150: return _compiler;
151: }
152:
153: /**
154: * Sets the directory where compiled classes go.
155: *
156: * @param path representing the class dir.
157: */
158: public void setClassDir(Path path) {
159: try {
160: path.mkdirs();
161: } catch (IOException e) {
162: }
163:
164: _classDir = path;
165: }
166:
167: /**
168: * Returns the directory where compiled classes go.
169: */
170: Path getClassDir() {
171: if (_classDir != null)
172: return _classDir;
173: else
174: return CauchoSystem.getWorkPath();
175: }
176:
177: /**
178: * Returns the path to the class directory.
179: */
180: String getClassDirName() {
181: return getClassDir().getNativePath();
182: }
183:
184: /**
185: * Sets the directory the java source comes from.
186: *
187: * @param path representing the source dir.
188: */
189: public void setSourceDir(Path path) {
190: _sourceDir = path;
191: }
192:
193: /**
194: * Returns the directory where compiled classes go.
195: */
196: public Path getSourceDir() {
197: if (_sourceDir != null)
198: return _sourceDir;
199: else
200: return getClassDir();
201: }
202:
203: /**
204: * Returns the path to the class directory.
205: */
206: String getSourceDirName() {
207: return getSourceDir().getNativePath();
208: }
209:
210: /**
211: * Sets the source extension.
212: */
213: public void setSourceExtension(String ext) {
214: _sourceExt = ext;
215: }
216:
217: /**
218: * Gets the source extension.
219: */
220: public String getSourceExtension() {
221: return _sourceExt;
222: }
223:
224: /**
225: * Sets the class path for compilation. Normally, the classpath from
226: * the class loader will be sufficient.
227: */
228: public void setClassPath(String classPath) {
229: _classPath = classPath;
230: }
231:
232: /**
233: * Sets an extra class path for compilation.
234: */
235: public void setExtraClassPath(String classPath) {
236: _extraClassPath = classPath;
237: }
238:
239: public void setCompileParent(boolean compileParent) {
240: _compileParent = compileParent;
241: }
242:
243: /**
244: * Returns the classpath for the compiler.
245: */
246: public String getClassPath() {
247: String classPath = null;//_classPath;
248:
249: if (classPath != null)
250: return classPath;
251:
252: if (classPath == null && _loader instanceof DynamicClassLoader) {
253: classPath = ((DynamicClassLoader) _loader).getClassPath();
254: } else if (classPath == null)
255: classPath = CauchoSystem.getClassPath();
256:
257: String srcDirName = getSourceDirName();
258: String classDirName = getClassDirName();
259:
260: char sep = CauchoSystem.getPathSeparatorChar();
261:
262: if (_extraClassPath != null)
263: classPath = classPath + sep + _extraClassPath;
264:
265: // Adding the srcDir lets javac and jikes find source files
266: if (!srcDirName.equals(classDirName))
267: classPath = srcDirName + sep + classPath;
268: classPath = classDirName + sep + classPath;
269:
270: return classPath;
271: }
272:
273: /**
274: * Sets any additional arguments for the compiler.
275: */
276: public void setArgs(String argString) {
277: try {
278: if (argString != null) {
279: String[] args = Pattern.compile("[\\s,]+").split(
280: argString);
281:
282: _args = new ArrayList<String>();
283:
284: for (int i = 0; i < args.length; i++) {
285: if (!args[i].equals(""))
286: _args.add(args[i]);
287: }
288: }
289: } catch (Exception e) {
290: log.log(Level.WARNING, e.toString(), e);
291: }
292: }
293:
294: /**
295: * Returns the ArrayList of arguments.
296: */
297: public ArrayList<String> getArgs() {
298: return _args;
299: }
300:
301: /**
302: * Sets the Java encoding for the compiler.
303: */
304: public void setEncoding(String encoding) {
305: _charEncoding = encoding;
306:
307: String javaEncoding = Encoding.getJavaName(encoding);
308:
309: if ("ISO8859_1".equals(javaEncoding))
310: _charEncoding = null;
311: }
312:
313: /**
314: * Returns the encoding.
315: */
316: public String getEncoding() {
317: return _charEncoding;
318: }
319:
320: /**
321: * Returns the maximum time allowed for an external compilation.
322: */
323: public long getMaxCompileTime() {
324: return _maxCompileTime;
325: }
326:
327: /**
328: * Sets the maximum time allowed for an external compilation.
329: */
330: public void setMaxCompileTime(long maxCompileTime) {
331: _maxCompileTime = maxCompileTime;
332: }
333:
334: /**
335: * Sets the maximum time allowed for an external compilation.
336: */
337: public void setMaxBatch(int maxBatch) {
338: _maxBatch = maxBatch;
339: }
340:
341: /**
342: * Mangles the path into a valid Java class name.
343: */
344: public static String mangleName(String name) {
345: boolean toLower = CauchoSystem.isCaseInsensitive();
346:
347: CharBuffer cb = new CharBuffer();
348: cb.append("_");
349:
350: for (int i = 0; i < name.length(); i++) {
351: char ch = name.charAt(i);
352:
353: if (ch == '/' || ch == CauchoSystem.getPathSeparatorChar()) {
354: if (i == 0) {
355: } else if (cb.charAt(cb.length() - 1) != '.'
356: && (i + 1 < name.length() && name.charAt(i + 1) != '/'))
357: cb.append("._");
358: } else if (ch == '.')
359: cb.append("__");
360: else if (ch == '_')
361: cb.append("_0");
362: else if (Character.isJavaIdentifierPart(ch))
363: cb.append(toLower ? Character.toLowerCase(ch) : ch);
364: else if (ch <= 256)
365: cb.append("_2" + encodeHex(ch >> 4) + encodeHex(ch));
366: else
367: cb.append("_4" + encodeHex(ch >> 12)
368: + encodeHex(ch >> 8) + encodeHex(ch >> 4)
369: + encodeHex(ch));
370: }
371:
372: if (cb.length() == 0)
373: cb.append("_z");
374:
375: return cb.toString();
376: }
377:
378: private static char encodeHex(int i) {
379: i &= 0xf;
380:
381: if (i < 10)
382: return (char) (i + '0');
383: else
384: return (char) (i - 10 + 'a');
385: }
386:
387: public void setArgs(ArrayList<String> args) {
388: if (args == null)
389: return;
390: if (_args == null)
391: _args = new ArrayList<String>();
392:
393: _args.addAll(args);
394: }
395:
396: /**
397: * Compiles the class. className is a fully qualified Java class, e.g.
398: * work.jsp.Test
399: *
400: * @param fileName Java source name -- in VFS format
401: * @param lineMap mapping from generated class back to the source class
402: *
403: * @return compiled class
404: */
405: public void compile(String fileName, LineMap lineMap)
406: throws IOException, ClassNotFoundException {
407: compile(fileName, lineMap, false);
408: }
409:
410: /**
411: * Compiles the class. className is a fully qualified Java class, e.g.
412: * work.jsp.Test
413: *
414: * @param fileName Java source name -- in VFS format
415: * @param lineMap mapping from generated class back to the source class
416: *
417: * @return compiled class
418: */
419: public void compileIfModified(String fileName, LineMap lineMap)
420: throws IOException, ClassNotFoundException {
421: compile(fileName, lineMap, true);
422: }
423:
424: /**
425: * Compiles the class. className is a fully qualified Java class, e.g.
426: * work.jsp.Test
427: *
428: * @param fileName Java source name -- in VFS format
429: * @param lineMap mapping from generated class back to the source class
430: * @param ifModified compile only if the *.java is modified
431: *
432: * @return compiled class
433: */
434: public void compile(String fileName, LineMap lineMap,
435: boolean ifModified) throws IOException,
436: ClassNotFoundException {
437: if (_compileParent) {
438: try {
439: if (_loader instanceof Make)
440: ((Make) _loader).make();
441: } catch (RuntimeException e) {
442: throw e;
443: } catch (ClassNotFoundException e) {
444: throw e;
445: } catch (IOException e) {
446: throw e;
447: } catch (Exception e) {
448: throw ConfigException.create(e);
449: }
450: }
451:
452: int p = fileName.lastIndexOf('.');
453: String path = fileName.substring(0, p);
454: String javaName = path + _sourceExt;
455: Path javaPath = getSourceDir().lookup(javaName);
456:
457: String className = path + ".class";
458: Path classPath = getClassDir().lookup(className);
459:
460: synchronized (LOCK) {
461: if (ifModified
462: && javaPath.getLastModified() <= classPath
463: .getLastModified())
464: return;
465:
466: if (javaPath.canRead() && classPath.exists())
467: classPath.remove();
468:
469: compileInt(new String[] { fileName }, lineMap);
470:
471: // XXX: This is needed for some regressions to pass,
472: // basically the timing wouldn't work if the classpath time
473: // was selected by the compiler
474: // server/141d, server/10k0
475: // classPath.setLastModified(javaPath.getLastModified());
476: }
477: }
478:
479: /**
480: * Compiles a batch list of classes.
481: *
482: * @return compiled class
483: */
484: public void compileBatch(String[] files) throws IOException,
485: ClassNotFoundException {
486: if (_compileParent) {
487: try {
488: if (_loader instanceof Make)
489: ((Make) _loader).make();
490: } catch (Exception e) {
491: throw new IOExceptionWrapper(e);
492: }
493: }
494:
495: if (files.length == 0)
496: return;
497:
498: // only batch a number of files at a time
499:
500: int batchCount = _maxBatch;
501: if (batchCount < 0)
502: batchCount = Integer.MAX_VALUE / 2;
503: else if (batchCount == 0)
504: batchCount = 1;
505:
506: IOException exn = null;
507:
508: ArrayList<String> uniqueFiles = new ArrayList<String>();
509: for (int i = 0; i < files.length; i++) {
510: if (!uniqueFiles.contains(files[i]))
511: uniqueFiles.add(files[i]);
512: }
513: files = new String[uniqueFiles.size()];
514: uniqueFiles.toArray(files);
515:
516: synchronized (LOCK) {
517: for (int i = 0; i < files.length; i += batchCount) {
518: int len = files.length - i;
519:
520: if (batchCount < len)
521: len = batchCount;
522:
523: String[] batchFiles = new String[len];
524:
525: System.arraycopy(files, i, batchFiles, 0, len);
526:
527: Arrays.sort(batchFiles);
528:
529: try {
530: compileInt(batchFiles, null);
531: } catch (IOException e) {
532: if (exn == null)
533: exn = e;
534: else
535: log.log(Level.WARNING, e.toString(), e);
536: }
537: }
538: }
539:
540: if (exn != null)
541: throw exn;
542: }
543:
544: protected void compileInt(String[] path, LineMap lineMap)
545: throws IOException, JavaCompileException {
546: AbstractJavaCompiler compiler;
547:
548: for (int i = 0; i < path.length; i++)
549: log.config("Compiling " + path[i]);
550:
551: if (_compiler.equals("internal"))
552: compiler = new InternalCompiler(this );
553: else if (_compiler.equals("eclipse"))
554: compiler = new EclipseCompiler(this );
555: else if (_compiler.equals("groovyc"))
556: compiler = new GroovyCompiler(this );
557: else
558: compiler = new ExternalCompiler(this );
559:
560: compiler.setPath(path);
561: compiler.setLineMap(lineMap);
562:
563: // the compiler may not be well-behaved enough to use the ThreadPool
564: Thread thread = new Thread(compiler);
565:
566: thread.start();
567:
568: synchronized (compiler) {
569: long endTime = System.currentTimeMillis() + _maxCompileTime;
570:
571: while (!compiler.isDone()
572: && System.currentTimeMillis() <= endTime) {
573: try {
574: compiler.wait(endTime - System.currentTimeMillis());
575: } catch (InterruptedException e) {
576: Thread.currentThread().interrupted();
577: log.log(Level.WARNING, e.toString(), e);
578: }
579: }
580: }
581:
582: if (!compiler.isDone()) {
583: log.warning("compilation timed out");
584: thread.interrupt();
585: compiler.abort();
586: }
587:
588: Throwable exn = compiler.getException();
589:
590: if (exn == null) {
591: } else if (exn instanceof IOException)
592: throw (IOException) exn;
593: else if (exn instanceof JavaCompileException)
594: throw (JavaCompileException) exn;
595: else if (exn instanceof RuntimeException)
596: throw (RuntimeException) exn;
597: else if (exn instanceof Error)
598: throw (Error) exn;
599: else
600: throw new IOExceptionWrapper(exn);
601:
602: for (int i = 0; i < path.length; i++) {
603: Path javaPath = getSourceDir().lookup(path[i]);
604:
605: if (!path[i].endsWith(".java"))
606: continue;
607:
608: String className = path[i].substring(0,
609: path[i].length() - 5)
610: + ".class";
611: Path classPath = getClassDir().lookup(className);
612: Path smapPath = getSourceDir().lookup(path[i] + ".smap");
613:
614: if (classPath.canRead() && smapPath.canRead())
615: mergeSmap(classPath, smapPath);
616: }
617: }
618:
619: public void mergeSmap(Path classPath, Path smapPath) {
620: try {
621: if (smapPath.getLength() >= 65536) {
622: log.warning(".smap for " + classPath.getTail()
623: + " is too large (" + smapPath.getLength()
624: + " bytes)");
625: return;
626: }
627:
628: log.fine("merging .smap for " + classPath.getTail());
629:
630: ByteCodeParser parser = new ByteCodeParser();
631: JavaClass javaClass;
632:
633: ReadStream is = classPath.openRead();
634: try {
635: javaClass = parser.parse(is);
636: } finally {
637: is.close();
638: }
639:
640: CharBuffer smap = new CharBuffer();
641:
642: is = smapPath.openRead();
643: try {
644: int ch;
645:
646: while ((ch = is.read()) >= 0) {
647: smap.append((char) ch);
648: }
649: } finally {
650: is.close();
651: }
652:
653: SourceDebugExtensionAttribute attr;
654:
655: attr = new SourceDebugExtensionAttribute(smap.toString());
656:
657: javaClass.addAttribute(attr);
658:
659: WriteStream os = classPath.openWrite();
660: try {
661: javaClass.write(os);
662: } finally {
663: os.close();
664: }
665: } catch (Exception e) {
666: log.log(Level.WARNING, e.toString(), e);
667: }
668: }
669: }
|