source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 23.2 KB
Line 
1/*
2 * Copyright 2001-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.apache.tools.ant.taskdefs.optional.jdepend;
19
20import java.io.File;
21import java.io.FileWriter;
22import java.io.IOException;
23import java.io.PrintWriter;
24import java.lang.reflect.Constructor;
25import java.lang.reflect.Method;
26import java.util.Vector;
27import java.util.Enumeration;
28import org.apache.tools.ant.BuildException;
29import org.apache.tools.ant.Project;
30import org.apache.tools.ant.Task;
31import org.apache.tools.ant.taskdefs.Execute;
32import org.apache.tools.ant.taskdefs.ExecuteWatchdog;
33import org.apache.tools.ant.taskdefs.LogStreamHandler;
34import org.apache.tools.ant.types.Commandline;
35import org.apache.tools.ant.types.CommandlineJava;
36import org.apache.tools.ant.types.EnumeratedAttribute;
37import org.apache.tools.ant.types.Path;
38import org.apache.tools.ant.types.PatternSet;
39import org.apache.tools.ant.types.Reference;
40import org.apache.tools.ant.util.LoaderUtils;
41
42/**
43 * Runs JDepend tests.
44 *
45 * <p>JDepend is a tool to generate design quality metrics for each Java package.
46 * It has been initially created by Mike Clark. JDepend can be found at <a
47 * href="http://www.clarkware.com/software/JDepend.html">http://www.clarkware.com/software/JDepend.html</a>.
48 *
49 * The current implementation spawn a new Java VM.
50 *
51 */
52public class JDependTask extends Task {
53 //private CommandlineJava commandline = new CommandlineJava();
54
55 // required attributes
56 private Path sourcesPath; // Deprecated!
57 private Path classesPath; // Use this going forward
58
59 // optional attributes
60 private File outputFile;
61 private File dir;
62 private Path compileClasspath;
63 private boolean haltonerror = false;
64 private boolean fork = false;
65 private Long timeout = null;
66
67 private String jvm = null;
68 private String format = "text";
69 private PatternSet defaultPatterns = new PatternSet();
70
71 private static Constructor packageFilterC;
72 private static Method setFilter;
73
74 private boolean includeRuntime = false;
75 private Path runtimeClasses = null;
76
77 static {
78 try {
79 Class packageFilter =
80 Class.forName("jdepend.framework.PackageFilter");
81 packageFilterC =
82 packageFilter.getConstructor(new Class[] {java.util.Collection.class});
83 setFilter =
84 jdepend.textui.JDepend.class.getDeclaredMethod("setFilter",
85 new Class[] {packageFilter});
86 } catch (Throwable t) {
87 if (setFilter == null) {
88 packageFilterC = null;
89 }
90 }
91 }
92
93 /**
94 * If true,
95 * include jdepend.jar in the forked VM.
96 *
97 * @param b include ant run time yes or no
98 * @since Ant 1.6
99 */
100 public void setIncluderuntime(boolean b) {
101 includeRuntime = b;
102 }
103
104 /**
105 * Set the timeout value (in milliseconds).
106 *
107 * <p>If the operation is running for more than this value, the jdepend
108 * will be canceled. (works only when in 'fork' mode).</p>
109 * @param value the maximum time (in milliseconds) allowed before
110 * declaring the test as 'timed-out'
111 * @see #setFork(boolean)
112 */
113 public void setTimeout(Long value) {
114 timeout = value;
115 }
116
117 /**
118 * @return the timeout value
119 */
120 public Long getTimeout() {
121 return timeout;
122 }
123
124 /**
125 * The output file name.
126 *
127 * @param outputFile the output file name
128 */
129 public void setOutputFile(File outputFile) {
130 this.outputFile = outputFile;
131 }
132
133 /**
134 * @return the output file name
135 */
136 public File getOutputFile() {
137 return outputFile;
138 }
139
140 /**
141 * Whether or not to halt on failure. Default: false.
142 * @param haltonerror the value to set
143 */
144 public void setHaltonerror(boolean haltonerror) {
145 this.haltonerror = haltonerror;
146 }
147
148 /**
149 * @return the value of the haltonerror attribute
150 */
151 public boolean getHaltonerror() {
152 return haltonerror;
153 }
154
155 /**
156 * If true, forks into a new JVM. Default: false.
157 *
158 * @param value <tt>true</tt> if a JVM should be forked,
159 * otherwise <tt>false<tt>
160 */
161 public void setFork(boolean value) {
162 fork = value;
163 }
164
165 /**
166 * @return the value of the fork attribute
167 */
168 public boolean getFork() {
169 return fork;
170 }
171
172 /**
173 * The command used to invoke a forked Java Virtual Machine.
174 *
175 * Default is <tt>java</tt>. Ignored if no JVM is forked.
176 * @param value the new VM to use instead of <tt>java</tt>
177 * @see #setFork(boolean)
178 */
179 public void setJvm(String value) {
180 jvm = value;
181
182 }
183
184 /**
185 * Adds a path to source code to analyze.
186 * @return a source path
187 * @deprecated
188 */
189 public Path createSourcespath() {
190 if (sourcesPath == null) {
191 sourcesPath = new Path(getProject());
192 }
193 return sourcesPath.createPath();
194 }
195
196 /**
197 * Gets the sourcepath.
198 * @return the sources path
199 * @deprecated
200 *
201 */
202 public Path getSourcespath() {
203 return sourcesPath;
204 }
205
206 /**
207 * Adds a path to class code to analyze.
208 * @return a classes path
209 */
210 public Path createClassespath() {
211 if (classesPath == null) {
212 classesPath = new Path(getProject());
213 }
214 return classesPath.createPath();
215 }
216
217 /**
218 * Gets the classespath.
219 * @return the classes path
220 */
221 public Path getClassespath() {
222 return classesPath;
223 }
224
225 /**
226 * The directory to invoke the VM in. Ignored if no JVM is forked.
227 * @param dir the directory to invoke the JVM from.
228 * @see #setFork(boolean)
229 */
230 public void setDir(File dir) {
231 this.dir = dir;
232 }
233
234 /**
235 * @return the dir attribute
236 */
237 public File getDir() {
238 return dir;
239 }
240
241 /**
242 * Set the classpath to be used for this compilation.
243 * @param classpath a class path to be used
244 */
245 public void setClasspath(Path classpath) {
246 if (compileClasspath == null) {
247 compileClasspath = classpath;
248 } else {
249 compileClasspath.append(classpath);
250 }
251 }
252
253 /**
254 * Gets the classpath to be used for this compilation.
255 * @return the class path used for compilation
256 */
257 public Path getClasspath() {
258 return compileClasspath;
259 }
260
261 /**
262 * Adds a path to the classpath.
263 * @return a classpath
264 */
265 public Path createClasspath() {
266 if (compileClasspath == null) {
267 compileClasspath = new Path(getProject());
268 }
269 return compileClasspath.createPath();
270 }
271
272 /**
273 * Create a new JVM argument. Ignored if no JVM is forked.
274 * @param commandline the commandline to create the argument on
275 * @return create a new JVM argument so that any argument can
276 * be passed to the JVM.
277 * @see #setFork(boolean)
278 */
279 public Commandline.Argument createJvmarg(CommandlineJava commandline) {
280 return commandline.createVmArgument();
281 }
282
283 /**
284 * Adds a reference to a classpath defined elsewhere.
285 * @param r a classpath reference
286 */
287 public void setClasspathRef(Reference r) {
288 createClasspath().setRefid(r);
289 }
290
291 /**
292 * add a name entry on the exclude list
293 * @return a pattern for the excludes
294 */
295 public PatternSet.NameEntry createExclude() {
296 return defaultPatterns.createExclude();
297 }
298
299 /**
300 * @return the excludes patterns
301 */
302 public PatternSet getExcludes() {
303 return defaultPatterns;
304 }
305
306 /**
307 * The format to write the output in, "xml" or "text".
308 *
309 * @param ea xml or text
310 */
311 public void setFormat(FormatAttribute ea) {
312 format = ea.getValue();
313 }
314
315 /**
316 * A class for the enumerated attribute format,
317 * values are xml and text.
318 * @see EnumeratedAttribute
319 */
320 public static class FormatAttribute extends EnumeratedAttribute {
321 private String [] formats = new String[]{"xml", "text"};
322
323 /**
324 * @return the enumerated values
325 */
326 public String[] getValues() {
327 return formats;
328 }
329 }
330
331 /**
332 * No problems with this test.
333 */
334 private static final int SUCCESS = 0;
335 /**
336 * An error occurred.
337 */
338 private static final int ERRORS = 1;
339
340 /**
341 * Search for the given resource and add the directory or archive
342 * that contains it to the classpath.
343 *
344 * <p>Doesn't work for archives in JDK 1.1 as the URL returned by
345 * getResource doesn't contain the name of the archive.</p>
346 *
347 * @param resource resource that one wants to lookup
348 * @since Ant 1.6
349 */
350 private void addClasspathEntry(String resource) {
351 /*
352 * pre Ant 1.6 this method used to call getClass().getResource
353 * while Ant 1.6 will call ClassLoader.getResource().
354 *
355 * The difference is that Class.getResource expects a leading
356 * slash for "absolute" resources and will strip it before
357 * delegating to ClassLoader.getResource - so we now have to
358 * emulate Class's behavior.
359 */
360 if (resource.startsWith("/")) {
361 resource = resource.substring(1);
362 } else {
363 resource = "org/apache/tools/ant/taskdefs/optional/jdepend/"
364 + resource;
365 }
366
367 File f = LoaderUtils.getResourceSource(getClass().getClassLoader(),
368 resource);
369 if (f != null) {
370 log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG);
371 runtimeClasses.createPath().setLocation(f);
372 } else {
373 log("Couldn\'t find " + resource, Project.MSG_DEBUG);
374 }
375 }
376
377 /**
378 * execute the task
379 *
380 * @exception BuildException if an error occurs
381 */
382 public void execute() throws BuildException {
383
384 CommandlineJava commandline = new CommandlineJava();
385
386 if ("text".equals(format)) {
387 commandline.setClassname("jdepend.textui.JDepend");
388 } else
389 if ("xml".equals(format)) {
390 commandline.setClassname("jdepend.xmlui.JDepend");
391 }
392
393 if (jvm != null) {
394 commandline.setVm(jvm);
395 }
396 if (getSourcespath() == null && getClassespath() == null) {
397 throw new BuildException("Missing classespath required argument");
398 } else if (getClassespath() == null) {
399 String msg =
400 "sourcespath is deprecated in JDepend >= 2.5 "
401 + "- please convert to classespath";
402 log(msg);
403 }
404
405 // execute the test and get the return code
406 int exitValue = JDependTask.ERRORS;
407 boolean wasKilled = false;
408 if (!getFork()) {
409 exitValue = executeInVM(commandline);
410 } else {
411 ExecuteWatchdog watchdog = createWatchdog();
412 exitValue = executeAsForked(commandline, watchdog);
413 // null watchdog means no timeout, you'd better not check with null
414 if (watchdog != null) {
415 wasKilled = watchdog.killedProcess();
416 }
417 }
418
419 // if there is an error/failure and that it should halt, stop
420 // everything otherwise just log a statement
421 boolean errorOccurred = exitValue == JDependTask.ERRORS || wasKilled;
422
423 if (errorOccurred) {
424 String errorMessage = "JDepend FAILED"
425 + (wasKilled ? " - Timed out" : "");
426
427 if (getHaltonerror()) {
428 throw new BuildException(errorMessage, getLocation());
429 } else {
430 log(errorMessage, Project.MSG_ERR);
431 }
432 }
433 }
434
435 // this comment extract from JUnit Task may also apply here
436 // "in VM is not very nice since it could probably hang the
437 // whole build. IMHO this method should be avoided and it would be best
438 // to remove it in future versions. TBD. (SBa)"
439
440 /**
441 * Execute inside VM.
442 *
443 * @param commandline the command line
444 * @return the return value of the mvm
445 * @exception BuildException if an error occurs
446 */
447 public int executeInVM(CommandlineJava commandline) throws BuildException {
448 jdepend.textui.JDepend jdepend;
449
450 if ("xml".equals(format)) {
451 jdepend = new jdepend.xmlui.JDepend();
452 } else {
453 jdepend = new jdepend.textui.JDepend();
454 }
455
456 FileWriter fw = null;
457 if (getOutputFile() != null) {
458 try {
459 fw = new FileWriter(getOutputFile().getPath());
460 } catch (IOException e) {
461 String msg = "JDepend Failed when creating the output file: "
462 + e.getMessage();
463 log(msg);
464 throw new BuildException(msg);
465 }
466 jdepend.setWriter(new PrintWriter(fw));
467 log("Output to be stored in " + getOutputFile().getPath());
468 }
469
470
471 try {
472 if (getClassespath() != null) {
473 // This is the new, better way - use classespath instead
474 // of sourcespath. The code is currently the same - you
475 // need class files in a directory to use this - jar files
476 // coming soon....
477 String[] classesPath = getClassespath().list();
478 for (int i = 0; i < classesPath.length; i++) {
479 File f = new File(classesPath[i]);
480 // not necessary as JDepend would fail, but why loose
481 // some time?
482 if (!f.exists() || !f.isDirectory()) {
483 String msg = "\""
484 + f.getPath()
485 + "\" does not represent a valid"
486 + " directory. JDepend would fail.";
487 log(msg);
488 throw new BuildException(msg);
489 }
490 try {
491 jdepend.addDirectory(f.getPath());
492 } catch (IOException e) {
493 String msg =
494 "JDepend Failed when adding a class directory: "
495 + e.getMessage();
496 log(msg);
497 throw new BuildException(msg);
498 }
499 }
500
501 } else if (getSourcespath() != null) {
502
503 // This is the old way and is deprecated - classespath is
504 // the right way to do this and is above
505 String[] sourcesPath = getSourcespath().list();
506 for (int i = 0; i < sourcesPath.length; i++) {
507 File f = new File(sourcesPath[i]);
508
509 // not necessary as JDepend would fail, but why loose
510 // some time?
511 if (!f.exists() || !f.isDirectory()) {
512 String msg = "\""
513 + f.getPath()
514 + "\" does not represent a valid"
515 + " directory. JDepend would fail.";
516 log(msg);
517 throw new BuildException(msg);
518 }
519 try {
520 jdepend.addDirectory(f.getPath());
521 } catch (IOException e) {
522 String msg =
523 "JDepend Failed when adding a source directory: "
524 + e.getMessage();
525 log(msg);
526 throw new BuildException(msg);
527 }
528 }
529 }
530
531 // This bit turns <exclude> child tags into patters to ignore
532 String[] patterns = defaultPatterns.getExcludePatterns(getProject());
533 if (patterns != null && patterns.length > 0) {
534 if (setFilter != null) {
535 Vector v = new Vector();
536 for (int i = 0; i < patterns.length; i++) {
537 v.addElement(patterns[i]);
538 }
539 try {
540 Object o = packageFilterC.newInstance(new Object[] {v});
541 setFilter.invoke(jdepend, new Object[] {o});
542 } catch (Throwable e) {
543 log("excludes will be ignored as JDepend doesn't like me: "
544 + e.getMessage(), Project.MSG_WARN);
545 }
546 } else {
547 log("Sorry, your version of JDepend doesn't support excludes",
548 Project.MSG_WARN);
549 }
550 }
551
552 jdepend.analyze();
553 } finally {
554 if (fw != null) {
555 try {
556 fw.close();
557 } catch (Throwable t) {
558 // Ignore
559 }
560 }
561 }
562 return SUCCESS;
563 }
564
565
566 /**
567 * Execute the task by forking a new JVM. The command will block until
568 * it finishes. To know if the process was destroyed or not, use the
569 * <tt>killedProcess()</tt> method of the watchdog class.
570 * @param commandline the commandline for forked jvm
571 * @param watchdog the watchdog in charge of cancelling the test if it
572 * exceeds a certain amount of time. Can be <tt>null</tt>.
573 * @return the result of running the jdepend
574 * @throws BuildException in case of error
575 */
576 // JL: comment extracted from JUnitTask (and slightly modified)
577 public int executeAsForked(CommandlineJava commandline,
578 ExecuteWatchdog watchdog) throws BuildException {
579 runtimeClasses = new Path(getProject());
580 addClasspathEntry("/jdepend/textui/JDepend.class");
581
582 // if not set, auto-create the ClassPath from the project
583 createClasspath();
584
585 // not sure whether this test is needed but cost nothing to put.
586 // hope it will be reviewed by anybody competent
587 if (getClasspath().toString().length() > 0) {
588 createJvmarg(commandline).setValue("-classpath");
589 createJvmarg(commandline).setValue(getClasspath().toString());
590 }
591
592 if (includeRuntime) {
593 Vector v = Execute.getProcEnvironment();
594 Enumeration e = v.elements();
595 while (e.hasMoreElements()) {
596 String s = (String) e.nextElement();
597 if (s.startsWith("CLASSPATH=")) {
598 commandline.createClasspath(getProject()).createPath()
599 .append(new Path(getProject(),
600 s.substring("CLASSPATH=".length()
601 )));
602 }
603 }
604 log("Implicitly adding " + runtimeClasses + " to CLASSPATH",
605 Project.MSG_VERBOSE);
606 commandline.createClasspath(getProject()).createPath()
607 .append(runtimeClasses);
608 }
609
610 if (getOutputFile() != null) {
611 // having a space between the file and its path causes commandline
612 // to add quotes around the argument thus making JDepend not taking
613 // it into account. Thus we split it in two
614 commandline.createArgument().setValue("-file");
615 commandline.createArgument().setValue(outputFile.getPath());
616 // we have to find a cleaner way to put this output
617 }
618
619 if (getSourcespath() != null) {
620 // This is deprecated - use classespath in the future
621 String[] sourcesPath = getSourcespath().list();
622 for (int i = 0; i < sourcesPath.length; i++) {
623 File f = new File(sourcesPath[i]);
624
625 // not necessary as JDepend would fail, but why loose
626 // some time?
627 if (!f.exists() || !f.isDirectory()) {
628 throw new BuildException("\"" + f.getPath()
629 + "\" does not represent a valid"
630 + " directory. JDepend would"
631 + " fail.");
632 }
633 commandline.createArgument().setValue(f.getPath());
634 }
635 }
636
637 if (getClassespath() != null) {
638 // This is the new way - use classespath - code is the
639 // same for now
640 String[] classesPath = getClassespath().list();
641 for (int i = 0; i < classesPath.length; i++) {
642 File f = new File(classesPath[i]);
643 // not necessary as JDepend would fail, but why loose
644 // some time?
645 if (!f.exists() || !f.isDirectory()) {
646 throw new BuildException("\"" + f.getPath()
647 + "\" does not represent a valid"
648 + " directory. JDepend would"
649 + " fail.");
650 }
651 commandline.createArgument().setValue(f.getPath());
652 }
653 }
654
655 Execute execute = new Execute(new LogStreamHandler(this,
656 Project.MSG_INFO, Project.MSG_WARN), watchdog);
657 execute.setCommandline(commandline.getCommandline());
658 if (getDir() != null) {
659 execute.setWorkingDirectory(getDir());
660 execute.setAntRun(getProject());
661 }
662
663 if (getOutputFile() != null) {
664 log("Output to be stored in " + getOutputFile().getPath());
665 }
666 log(commandline.describeCommand(), Project.MSG_VERBOSE);
667 try {
668 return execute.execute();
669 } catch (IOException e) {
670 throw new BuildException("Process fork failed.", e, getLocation());
671 }
672 }
673
674 /**
675 * @return <tt>null</tt> if there is a timeout value, otherwise the
676 * watchdog instance.
677 * @throws BuildException in case of error
678 */
679 protected ExecuteWatchdog createWatchdog() throws BuildException {
680 if (getTimeout() == null) {
681 return null;
682 }
683 return new ExecuteWatchdog(getTimeout().longValue());
684 }
685}
Note: See TracBrowser for help on using the repository browser.