001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.rmi.Remote;
024: import java.util.Vector;
025: import org.apache.tools.ant.BuildException;
026: import org.apache.tools.ant.DirectoryScanner;
027: import org.apache.tools.ant.Project;
028: import org.apache.tools.ant.taskdefs.rmic.RmicAdapter;
029: import org.apache.tools.ant.taskdefs.rmic.RmicAdapterFactory;
030: import org.apache.tools.ant.types.FilterSetCollection;
031: import org.apache.tools.ant.types.Path;
032: import org.apache.tools.ant.types.Reference;
033: import org.apache.tools.ant.util.FileNameMapper;
034: import org.apache.tools.ant.util.FileUtils;
035: import org.apache.tools.ant.util.SourceFileScanner;
036: import org.apache.tools.ant.util.facade.FacadeTaskHelper;
037:
038: /**
039: * Runs the rmic compiler against classes.</p>
040: * <p>Rmic can be run on a single class (as specified with the classname
041: * attribute) or a number of classes at once (all classes below base that
042: * are neither _Stub nor _Skel classes). If you want to rmic a single
043: * class and this class is a class nested into another class, you have to
044: * specify the classname in the form <code>Outer$$Inner</code> instead of
045: * <code>Outer.Inner</code>.</p>
046: * <p>It is possible to refine the set of files that are being rmiced. This can
047: * be done with the <i>includes</i>, <i>includesfile</i>, <i>excludes</i>,
048: * <i>excludesfile</i> and <i>defaultexcludes</i>
049: * attributes. With the <i>includes</i> or <i>includesfile</i> attribute you
050: * specify the files you want to have included by using patterns. The
051: * <i>exclude</i> or <i>excludesfile</i> attribute is used to specify
052: * the files you want to have excluded. This is also done with patterns. And
053: * finally with the <i>defaultexcludes</i> attribute, you can specify whether
054: * you want to use default exclusions or not. See the section on
055: * directory based tasks</a>, on how the
056: * inclusion/exclusion of files works, and how to write patterns.</p>
057: * <p>This task forms an implicit FileSet and
058: * supports all attributes of <code><fileset></code>
059: * (<code>dir</code> becomes <code>base</code>) as well as the nested
060: * <code><include></code>, <code><exclude></code> and
061: * <code><patternset></code> elements.</p>
062: * <p>It is possible to use different compilers. This can be selected
063: * with the "build.rmic" property or the <code>compiler</code>
064: * attribute. <a name="compilervalues">There are three choices</a>:</p>
065: * <ul>
066: * <li>sun (the standard compiler of the JDK)</li>
067: * <li>kaffe (the standard compiler of
068: * {@link <a href="http://www.kaffe.org">Kaffe</a>})</li>
069: * <li>weblogic</li>
070: * </ul>
071: *
072: * <p> The <a href="http://dione.zcu.cz/~toman40/miniRMI/">miniRMI</a>
073: * project contains a compiler implementation for this task as well,
074: * please consult miniRMI's documentation to learn how to use it.</p>
075: *
076: * @since Ant 1.1
077: *
078: * @ant.task category="java"
079: */
080:
081: public class Rmic extends MatchingTask {
082:
083: /** rmic failed message */
084: public static final String ERROR_RMIC_FAILED = "Rmic failed; see the compiler error output for details.";
085:
086: private File baseDir;
087: private String classname;
088: private File sourceBase;
089: private String stubVersion;
090: private Path compileClasspath;
091: private Path extDirs;
092: private boolean verify = false;
093: private boolean filtering = false;
094:
095: private boolean iiop = false;
096: private String iiopOpts;
097: private boolean idl = false;
098: private String idlOpts;
099: private boolean debug = false;
100: private boolean includeAntRuntime = true;
101: private boolean includeJavaRuntime = false;
102:
103: private Vector compileList = new Vector();
104:
105: private ClassLoader loader = null;
106:
107: private FacadeTaskHelper facade;
108: /** unable to verify message */
109: public static final String ERROR_UNABLE_TO_VERIFY_CLASS = "Unable to verify class ";
110: /** could not be found message */
111: public static final String ERROR_NOT_FOUND = ". It could not be found.";
112: /** not defined message */
113: public static final String ERROR_NOT_DEFINED = ". It is not defined.";
114: /** loaded error message */
115: public static final String ERROR_LOADING_CAUSED_EXCEPTION = ". Loading caused Exception: ";
116: /** base not exists message */
117: public static final String ERROR_NO_BASE_EXISTS = "base does not exist: ";
118: /** base not a directory message */
119: public static final String ERROR_NOT_A_DIR = "base is not a directory:";
120: /** base attribute not set message */
121: public static final String ERROR_BASE_NOT_SET = "base attribute must be set!";
122:
123: private static final FileUtils FILE_UTILS = FileUtils
124: .getFileUtils();
125:
126: /**
127: * Constructor for Rmic.
128: */
129: public Rmic() {
130: facade = new FacadeTaskHelper(
131: RmicAdapterFactory.DEFAULT_COMPILER);
132: }
133:
134: /**
135: * Sets the location to store the compiled files; required
136: * @param base the location to store the compiled files
137: */
138: public void setBase(File base) {
139: this .baseDir = base;
140: }
141:
142: /**
143: * Gets the base directory to output generated class.
144: * @return the location of the compiled files
145: */
146:
147: public File getBase() {
148: return this .baseDir;
149: }
150:
151: /**
152: * Sets the class to run <code>rmic</code> against;
153: * optional
154: * @param classname the name of the class for rmic to create code for
155: */
156: public void setClassname(String classname) {
157: this .classname = classname;
158: }
159:
160: /**
161: * Gets the class name to compile.
162: * @return the name of the class to compile
163: */
164: public String getClassname() {
165: return classname;
166: }
167:
168: /**
169: * optional directory to save generated source files to.
170: * @param sourceBase the directory to save source files to.
171: */
172: public void setSourceBase(File sourceBase) {
173: this .sourceBase = sourceBase;
174: }
175:
176: /**
177: * Gets the source dirs to find the source java files.
178: * @return sourceBase the directory containing the source files.
179: */
180: public File getSourceBase() {
181: return sourceBase;
182: }
183:
184: /**
185: * Specify the JDK version for the generated stub code.
186: * Specify "1.1" to pass the "-v1.1" option to rmic.</td>
187: * @param stubVersion the JDK version
188: */
189: public void setStubVersion(String stubVersion) {
190: this .stubVersion = stubVersion;
191: }
192:
193: /**
194: * Gets the JDK version for the generated stub code.
195: * @return stubVersion
196: */
197: public String getStubVersion() {
198: return stubVersion;
199: }
200:
201: /**
202: * Sets token filtering [optional], default=false
203: * @param filter turn on token filtering
204: */
205: public void setFiltering(boolean filter) {
206: this .filtering = filter;
207: }
208:
209: /**
210: * Gets whether token filtering is set
211: * @return filtering
212: */
213: public boolean getFiltering() {
214: return filtering;
215: }
216:
217: /**
218: * Generate debug info (passes -g to rmic);
219: * optional, defaults to false
220: * @param debug turn on debug info
221: */
222: public void setDebug(boolean debug) {
223: this .debug = debug;
224: }
225:
226: /**
227: * Gets the debug flag.
228: * @return debug
229: */
230: public boolean getDebug() {
231: return debug;
232: }
233:
234: /**
235: * Set the classpath to be used for this compilation.
236: * @param classpath the classpath used for this compilation
237: */
238: public void setClasspath(Path classpath) {
239: if (compileClasspath == null) {
240: compileClasspath = classpath;
241: } else {
242: compileClasspath.append(classpath);
243: }
244: }
245:
246: /**
247: * Creates a nested classpath element.
248: * @return classpath
249: */
250: public Path createClasspath() {
251: if (compileClasspath == null) {
252: compileClasspath = new Path(getProject());
253: }
254: return compileClasspath.createPath();
255: }
256:
257: /**
258: * Adds to the classpath a reference to
259: * a <path> defined elsewhere.
260: * @param pathRef the reference to add to the classpath
261: */
262: public void setClasspathRef(Reference pathRef) {
263: createClasspath().setRefid(pathRef);
264: }
265:
266: /**
267: * Gets the classpath.
268: * @return the classpath
269: */
270: public Path getClasspath() {
271: return compileClasspath;
272: }
273:
274: /**
275: * Flag to enable verification so that the classes
276: * found by the directory match are
277: * checked to see if they implement java.rmi.Remote.
278: * optional; This defaults to false if not set.
279: * @param verify turn on verification for classes
280: */
281:
282: public void setVerify(boolean verify) {
283: this .verify = verify;
284: }
285:
286: /**
287: * Get verify flag.
288: * @return verify
289: */
290: public boolean getVerify() {
291: return verify;
292: }
293:
294: /**
295: * Indicates that IIOP compatible stubs should
296: * be generated; optional, defaults to false
297: * if not set.
298: * @param iiop generate IIOP compatible stubs
299: */
300: public void setIiop(boolean iiop) {
301: this .iiop = iiop;
302: }
303:
304: /**
305: * Gets iiop flags.
306: * @return iiop
307: */
308: public boolean getIiop() {
309: return iiop;
310: }
311:
312: /**
313: * Set additional arguments for iiop
314: * @param iiopOpts additional arguments for iiop
315: */
316: public void setIiopopts(String iiopOpts) {
317: this .iiopOpts = iiopOpts;
318: }
319:
320: /**
321: * Gets additional arguments for iiop.
322: * @return iiopOpts
323: */
324: public String getIiopopts() {
325: return iiopOpts;
326: }
327:
328: /**
329: * Indicates that IDL output should be
330: * generated. This defaults to false
331: * if not set.
332: * @param idl generate IDL output
333: */
334: public void setIdl(boolean idl) {
335: this .idl = idl;
336: }
337:
338: /**
339: * Gets IDL flags.
340: * @return the idl flag
341: */
342: public boolean getIdl() {
343: return idl;
344: }
345:
346: /**
347: * pass additional arguments for IDL compile
348: * @param idlOpts additional IDL arguments
349: */
350: public void setIdlopts(String idlOpts) {
351: this .idlOpts = idlOpts;
352: }
353:
354: /**
355: * Gets additional arguments for idl compile.
356: * @return the idl options
357: */
358: public String getIdlopts() {
359: return idlOpts;
360: }
361:
362: /**
363: * Gets file list to compile.
364: * @return the list of files to compile.
365: */
366: public Vector getFileList() {
367: return compileList;
368: }
369:
370: /**
371: * Sets whether or not to include ant's own classpath in this task's
372: * classpath.
373: * Optional; default is <code>true</code>.
374: * @param include if true include ant's classpath
375: */
376: public void setIncludeantruntime(boolean include) {
377: includeAntRuntime = include;
378: }
379:
380: /**
381: * Gets whether or not the ant classpath is to be included in the
382: * task's classpath.
383: * @return true if ant's classpath is to be included
384: */
385: public boolean getIncludeantruntime() {
386: return includeAntRuntime;
387: }
388:
389: /**
390: * task's classpath.
391: * Enables or disables including the default run-time
392: * libraries from the executing VM; optional,
393: * defaults to false
394: * @param include if true include default run-time libraries
395: */
396: public void setIncludejavaruntime(boolean include) {
397: includeJavaRuntime = include;
398: }
399:
400: /**
401: * Gets whether or not the java runtime should be included in this
402: * task's classpath.
403: * @return true if default run-time libraries are included
404: */
405: public boolean getIncludejavaruntime() {
406: return includeJavaRuntime;
407: }
408:
409: /**
410: * Sets the extension directories that will be used during the
411: * compilation; optional.
412: * @param extDirs the extension directories to be used
413: */
414: public void setExtdirs(Path extDirs) {
415: if (this .extDirs == null) {
416: this .extDirs = extDirs;
417: } else {
418: this .extDirs.append(extDirs);
419: }
420: }
421:
422: /**
423: * Maybe creates a nested extdirs element.
424: * @return path object to be configured with the extension directories
425: */
426: public Path createExtdirs() {
427: if (extDirs == null) {
428: extDirs = new Path(getProject());
429: }
430: return extDirs.createPath();
431: }
432:
433: /**
434: * Gets the extension directories that will be used during the
435: * compilation.
436: * @return the extension directories to be used
437: */
438: public Path getExtdirs() {
439: return extDirs;
440: }
441:
442: /**
443: * @return the compile list.
444: */
445: public Vector getCompileList() {
446: return compileList;
447: }
448:
449: /**
450: * Sets the compiler implementation to use; optional,
451: * defaults to the value of the <code>build.rmic</code> property,
452: * or failing that, default compiler for the current VM
453: * @param compiler the compiler implemention to use
454: * @since Ant 1.5
455: */
456: public void setCompiler(String compiler) {
457: if (compiler.length() > 0) {
458: facade.setImplementation(compiler);
459: }
460: }
461:
462: /**
463: * get the name of the current compiler
464: * @return the name of the compiler
465: * @since Ant 1.5
466: */
467: public String getCompiler() {
468: facade.setMagicValue(getProject().getProperty("build.rmic"));
469: return facade.getImplementation();
470: }
471:
472: /**
473: * Adds an implementation specific command line argument.
474: * @return an object to be configured with a command line argument
475: * @since Ant 1.5
476: */
477: public ImplementationSpecificArgument createCompilerArg() {
478: ImplementationSpecificArgument arg = new ImplementationSpecificArgument();
479: facade.addImplementationArgument(arg);
480: return arg;
481: }
482:
483: /**
484: * Get the additional implementation specific command line arguments.
485: * @return array of command line arguments, guaranteed to be non-null.
486: * @since Ant 1.5
487: */
488: public String[] getCurrentCompilerArgs() {
489: getCompiler();
490: return facade.getArgs();
491: }
492:
493: /**
494: * execute by creating an instance of an implementation
495: * class and getting to do the work
496: * @throws org.apache.tools.ant.BuildException
497: * if there's a problem with baseDir or RMIC
498: */
499: public void execute() throws BuildException {
500: if (baseDir == null) {
501: throw new BuildException(ERROR_BASE_NOT_SET, getLocation());
502: }
503: if (!baseDir.exists()) {
504: throw new BuildException(ERROR_NO_BASE_EXISTS + baseDir,
505: getLocation());
506: }
507: if (!baseDir.isDirectory()) {
508: throw new BuildException(ERROR_NOT_A_DIR + baseDir,
509: getLocation());
510: }
511: if (verify) {
512: log("Verify has been turned on.", Project.MSG_VERBOSE);
513: }
514:
515: RmicAdapter adapter = RmicAdapterFactory.getRmic(getCompiler(),
516: this );
517:
518: // now we need to populate the compiler adapter
519: adapter.setRmic(this );
520:
521: Path classpath = adapter.getClasspath();
522: loader = getProject().createClassLoader(classpath);
523:
524: try {
525: // scan base dirs to build up compile lists only if a
526: // specific classname is not given
527: if (classname == null) {
528: DirectoryScanner ds = this .getDirectoryScanner(baseDir);
529: String[] files = ds.getIncludedFiles();
530: scanDir(baseDir, files, adapter.getMapper());
531: } else {
532: // otherwise perform a timestamp comparison - at least
533: scanDir(baseDir, new String[] { classname.replace('.',
534: File.separatorChar)
535: + ".class" }, adapter.getMapper());
536: }
537:
538: int fileCount = compileList.size();
539: if (fileCount > 0) {
540: log("RMI Compiling " + fileCount + " class"
541: + (fileCount > 1 ? "es" : "") + " to "
542: + baseDir, Project.MSG_INFO);
543:
544: // finally, lets execute the compiler!!
545: if (!adapter.execute()) {
546: throw new BuildException(ERROR_RMIC_FAILED,
547: getLocation());
548: }
549: }
550:
551: /*
552: * Move the generated source file to the base directory. If
553: * base directory and sourcebase are the same, the generated
554: * sources are already in place.
555: */
556: if (null != sourceBase && !baseDir.equals(sourceBase)
557: && fileCount > 0) {
558: if (idl) {
559: log("Cannot determine sourcefiles in idl mode, ",
560: Project.MSG_WARN);
561: log("sourcebase attribute will be ignored.",
562: Project.MSG_WARN);
563: } else {
564: for (int j = 0; j < fileCount; j++) {
565: moveGeneratedFile(baseDir, sourceBase,
566: (String) compileList.elementAt(j),
567: adapter);
568: }
569: }
570: }
571: } finally {
572: compileList.removeAllElements();
573: }
574: }
575:
576: /**
577: * Move the generated source file(s) to the base directory
578: *
579: * @throws org.apache.tools.ant.BuildException When error
580: * copying/removing files.
581: */
582: private void moveGeneratedFile(File baseDir, File sourceBaseFile,
583: String classname, RmicAdapter adapter)
584: throws BuildException {
585:
586: String classFileName = classname.replace('.',
587: File.separatorChar)
588: + ".class";
589: String[] generatedFiles = adapter.getMapper().mapFileName(
590: classFileName);
591:
592: for (int i = 0; i < generatedFiles.length; i++) {
593: final String generatedFile = generatedFiles[i];
594: if (!generatedFile.endsWith(".class")) {
595: // don't know how to handle that - a IDL file doesn't
596: // have a corresponding Java source for example.
597: continue;
598: }
599:
600: final int pos = generatedFile.length() - ".class".length();
601: String sourceFileName = generatedFile.substring(0, pos)
602: + ".java";
603:
604: File oldFile = new File(baseDir, sourceFileName);
605: if (!oldFile.exists()) {
606: // no source file generated, nothing to move
607: continue;
608: }
609:
610: File newFile = new File(sourceBaseFile, sourceFileName);
611: try {
612: if (filtering) {
613: FILE_UTILS.copyFile(oldFile, newFile,
614: new FilterSetCollection(getProject()
615: .getGlobalFilterSet()));
616: } else {
617: FILE_UTILS.copyFile(oldFile, newFile);
618: }
619: oldFile.delete();
620: } catch (IOException ioe) {
621: String msg = "Failed to copy " + oldFile + " to "
622: + newFile + " due to " + ioe.getMessage();
623: throw new BuildException(msg, ioe, getLocation());
624: }
625: }
626: }
627:
628: /**
629: * Scans the directory looking for class files to be compiled.
630: * The result is returned in the class variable compileList.
631: * @param baseDir the base direction
632: * @param files the list of files to scan
633: * @param mapper the mapper of files to target files
634: */
635: protected void scanDir(File baseDir, String[] files,
636: FileNameMapper mapper) {
637:
638: String[] newFiles = files;
639: if (idl) {
640: log(
641: "will leave uptodate test to rmic implementation in idl mode.",
642: Project.MSG_VERBOSE);
643: } else if (iiop && iiopOpts != null
644: && iiopOpts.indexOf("-always") > -1) {
645: log(
646: "no uptodate test as -always option has been specified",
647: Project.MSG_VERBOSE);
648: } else {
649: SourceFileScanner sfs = new SourceFileScanner(this );
650: newFiles = sfs.restrict(files, baseDir, baseDir, mapper);
651: }
652:
653: for (int i = 0; i < newFiles.length; i++) {
654: String name = newFiles[i].replace(File.separatorChar, '.');
655: name = name.substring(0, name.lastIndexOf(".class"));
656: compileList.addElement(name);
657: }
658: }
659:
660: /**
661: * Load named class and test whether it can be rmic'ed
662: * @param classname the name of the class to be tested
663: * @return true if the class can be rmic'ed
664: */
665: public boolean isValidRmiRemote(String classname) {
666: try {
667: Class testClass = loader.loadClass(classname);
668: // One cannot RMIC an interface for "classic" RMI (JRMP)
669: if (testClass.isInterface() && !iiop && !idl) {
670: return false;
671: }
672: return isValidRmiRemote(testClass);
673: } catch (ClassNotFoundException e) {
674: log(ERROR_UNABLE_TO_VERIFY_CLASS + classname
675: + ERROR_NOT_FOUND, Project.MSG_WARN);
676: } catch (NoClassDefFoundError e) {
677: log(ERROR_UNABLE_TO_VERIFY_CLASS + classname
678: + ERROR_NOT_DEFINED, Project.MSG_WARN);
679: } catch (Throwable t) {
680: log(ERROR_UNABLE_TO_VERIFY_CLASS + classname
681: + ERROR_LOADING_CAUSED_EXCEPTION + t.getMessage(),
682: Project.MSG_WARN);
683: }
684: // we only get here if an exception has been thrown
685: return false;
686: }
687:
688: /**
689: * Returns the topmost interface that extends Remote for a given
690: * class - if one exists.
691: * @param testClass the class to be tested
692: * @return the topmost interface that extends Remote, or null if there
693: * is none.
694: */
695: public Class getRemoteInterface(Class testClass) {
696: if (Remote.class.isAssignableFrom(testClass)) {
697: Class[] interfaces = testClass.getInterfaces();
698: if (interfaces != null) {
699: for (int i = 0; i < interfaces.length; i++) {
700: if (Remote.class.isAssignableFrom(interfaces[i])) {
701: return interfaces[i];
702: }
703: }
704: }
705: }
706: return null;
707: }
708:
709: /**
710: * Check to see if the class or (super)interfaces implement
711: * java.rmi.Remote.
712: */
713: private boolean isValidRmiRemote(Class testClass) {
714: return getRemoteInterface(testClass) != null;
715: }
716:
717: /**
718: * Classloader for the user-specified classpath.
719: * @return the classloader
720: */
721: public ClassLoader getLoader() {
722: return loader;
723: }
724:
725: /**
726: * Adds an "compiler" attribute to Commandline$Attribute used to
727: * filter command line attributes based on the current
728: * implementation.
729: */
730: public class ImplementationSpecificArgument
731: extends
732: org.apache.tools.ant.util.facade.ImplementationSpecificArgument {
733:
734: /**
735: * Only pass the specified argument if the
736: * chosen compiler implementation matches the
737: * value of this attribute. Legal values are
738: * the same as those in the above list of
739: * valid compilers.)
740: * @param impl the compiler to be used.
741: */
742: public void setCompiler(String impl) {
743: super.setImplementation(impl);
744: }
745: }
746:
747: }
|