| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.tools.ant.taskdefs.optional.jdepend; |
| |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.util.Vector; |
| import java.util.Enumeration; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.taskdefs.Execute; |
| import org.apache.tools.ant.taskdefs.ExecuteWatchdog; |
| import org.apache.tools.ant.taskdefs.LogStreamHandler; |
| import org.apache.tools.ant.types.Commandline; |
| import org.apache.tools.ant.types.CommandlineJava; |
| import org.apache.tools.ant.types.EnumeratedAttribute; |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.types.PatternSet; |
| import org.apache.tools.ant.types.Reference; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.LoaderUtils; |
| |
| /** |
| * Runs JDepend tests. |
| * |
| * <p>JDepend is a tool to generate design quality metrics for each Java package. |
| * It has been initially created by Mike Clark. JDepend can be found at <a |
| * href="http://www.clarkware.com/software/JDepend.html">http://www.clarkware.com/software/JDepend.html</a>. |
| * |
| * The current implementation spawn a new Java VM. |
| * |
| */ |
| public class JDependTask extends Task { |
| //private CommandlineJava commandline = new CommandlineJava(); |
| |
| // required attributes |
| private Path sourcesPath; // Deprecated! |
| private Path classesPath; // Use this going forward |
| |
| // optional attributes |
| private File outputFile; |
| private File dir; |
| private Path compileClasspath; |
| private boolean haltonerror = false; |
| private boolean fork = false; |
| private Long timeout = null; |
| |
| private String jvm = null; |
| private String format = "text"; |
| private PatternSet defaultPatterns = new PatternSet(); |
| |
| private static Constructor packageFilterC; |
| private static Method setFilter; |
| |
| private boolean includeRuntime = false; |
| private Path runtimeClasses = null; |
| |
| static { |
| try { |
| Class packageFilter = |
| Class.forName("jdepend.framework.PackageFilter"); |
| packageFilterC = |
| packageFilter.getConstructor(new Class[] {java.util.Collection.class}); |
| setFilter = |
| jdepend.textui.JDepend.class.getDeclaredMethod("setFilter", |
| new Class[] {packageFilter}); |
| } catch (Throwable t) { |
| if (setFilter == null) { |
| packageFilterC = null; |
| } |
| } |
| } |
| |
| /** |
| * If true, |
| * include jdepend.jar in the forked VM. |
| * |
| * @param b include ant run time yes or no |
| * @since Ant 1.6 |
| */ |
| public void setIncluderuntime(boolean b) { |
| includeRuntime = b; |
| } |
| |
| /** |
| * Set the timeout value (in milliseconds). |
| * |
| * <p>If the operation is running for more than this value, the jdepend |
| * will be canceled. (works only when in 'fork' mode).</p> |
| * @param value the maximum time (in milliseconds) allowed before |
| * declaring the test as 'timed-out' |
| * @see #setFork(boolean) |
| */ |
| public void setTimeout(Long value) { |
| timeout = value; |
| } |
| |
| /** |
| * @return the timeout value |
| */ |
| public Long getTimeout() { |
| return timeout; |
| } |
| |
| /** |
| * The output file name. |
| * |
| * @param outputFile the output file name |
| */ |
| public void setOutputFile(File outputFile) { |
| this.outputFile = outputFile; |
| } |
| |
| /** |
| * @return the output file name |
| */ |
| public File getOutputFile() { |
| return outputFile; |
| } |
| |
| /** |
| * Whether or not to halt on failure. Default: false. |
| * @param haltonerror the value to set |
| */ |
| public void setHaltonerror(boolean haltonerror) { |
| this.haltonerror = haltonerror; |
| } |
| |
| /** |
| * @return the value of the haltonerror attribute |
| */ |
| public boolean getHaltonerror() { |
| return haltonerror; |
| } |
| |
| /** |
| * If true, forks into a new JVM. Default: false. |
| * |
| * @param value <tt>true</tt> if a JVM should be forked, |
| * otherwise <tt>false<tt> |
| */ |
| public void setFork(boolean value) { |
| fork = value; |
| } |
| |
| /** |
| * @return the value of the fork attribute |
| */ |
| public boolean getFork() { |
| return fork; |
| } |
| |
| /** |
| * The command used to invoke a forked Java Virtual Machine. |
| * |
| * Default is <tt>java</tt>. Ignored if no JVM is forked. |
| * @param value the new VM to use instead of <tt>java</tt> |
| * @see #setFork(boolean) |
| */ |
| public void setJvm(String value) { |
| jvm = value; |
| |
| } |
| |
| /** |
| * Adds a path to source code to analyze. |
| * @return a source path |
| * @deprecated since 1.6.x. |
| */ |
| public Path createSourcespath() { |
| if (sourcesPath == null) { |
| sourcesPath = new Path(getProject()); |
| } |
| return sourcesPath.createPath(); |
| } |
| |
| /** |
| * Gets the sourcepath. |
| * @return the sources path |
| * @deprecated since 1.6.x. |
| */ |
| public Path getSourcespath() { |
| return sourcesPath; |
| } |
| |
| /** |
| * Adds a path to class code to analyze. |
| * @return a classes path |
| */ |
| public Path createClassespath() { |
| if (classesPath == null) { |
| classesPath = new Path(getProject()); |
| } |
| return classesPath.createPath(); |
| } |
| |
| /** |
| * Gets the classespath. |
| * @return the classes path |
| */ |
| public Path getClassespath() { |
| return classesPath; |
| } |
| |
| /** |
| * The directory to invoke the VM in. Ignored if no JVM is forked. |
| * @param dir the directory to invoke the JVM from. |
| * @see #setFork(boolean) |
| */ |
| public void setDir(File dir) { |
| this.dir = dir; |
| } |
| |
| /** |
| * @return the dir attribute |
| */ |
| public File getDir() { |
| return dir; |
| } |
| |
| /** |
| * Set the classpath to be used for this compilation. |
| * @param classpath a class path to be used |
| */ |
| public void setClasspath(Path classpath) { |
| if (compileClasspath == null) { |
| compileClasspath = classpath; |
| } else { |
| compileClasspath.append(classpath); |
| } |
| } |
| |
| /** |
| * Gets the classpath to be used for this compilation. |
| * @return the class path used for compilation |
| */ |
| public Path getClasspath() { |
| return compileClasspath; |
| } |
| |
| /** |
| * Adds a path to the classpath. |
| * @return a classpath |
| */ |
| public Path createClasspath() { |
| if (compileClasspath == null) { |
| compileClasspath = new Path(getProject()); |
| } |
| return compileClasspath.createPath(); |
| } |
| |
| /** |
| * Create a new JVM argument. Ignored if no JVM is forked. |
| * @param commandline the commandline to create the argument on |
| * @return create a new JVM argument so that any argument can |
| * be passed to the JVM. |
| * @see #setFork(boolean) |
| */ |
| public Commandline.Argument createJvmarg(CommandlineJava commandline) { |
| return commandline.createVmArgument(); |
| } |
| |
| /** |
| * Adds a reference to a classpath defined elsewhere. |
| * @param r a classpath reference |
| */ |
| public void setClasspathRef(Reference r) { |
| createClasspath().setRefid(r); |
| } |
| |
| /** |
| * add a name entry on the exclude list |
| * @return a pattern for the excludes |
| */ |
| public PatternSet.NameEntry createExclude() { |
| return defaultPatterns.createExclude(); |
| } |
| |
| /** |
| * @return the excludes patterns |
| */ |
| public PatternSet getExcludes() { |
| return defaultPatterns; |
| } |
| |
| /** |
| * The format to write the output in, "xml" or "text". |
| * |
| * @param ea xml or text |
| */ |
| public void setFormat(FormatAttribute ea) { |
| format = ea.getValue(); |
| } |
| |
| /** |
| * A class for the enumerated attribute format, |
| * values are xml and text. |
| * @see EnumeratedAttribute |
| */ |
| public static class FormatAttribute extends EnumeratedAttribute { |
| private String [] formats = new String[]{"xml", "text"}; |
| |
| /** |
| * @return the enumerated values |
| */ |
| public String[] getValues() { |
| return formats; |
| } |
| } |
| |
| /** |
| * No problems with this test. |
| */ |
| private static final int SUCCESS = 0; |
| /** |
| * An error occurred. |
| */ |
| private static final int ERRORS = 1; |
| |
| /** |
| * Search for the given resource and add the directory or archive |
| * that contains it to the classpath. |
| * |
| * <p>Doesn't work for archives in JDK 1.1 as the URL returned by |
| * getResource doesn't contain the name of the archive.</p> |
| * |
| * @param resource resource that one wants to lookup |
| * @since Ant 1.6 |
| */ |
| private void addClasspathEntry(String resource) { |
| /* |
| * pre Ant 1.6 this method used to call getClass().getResource |
| * while Ant 1.6 will call ClassLoader.getResource(). |
| * |
| * The difference is that Class.getResource expects a leading |
| * slash for "absolute" resources and will strip it before |
| * delegating to ClassLoader.getResource - so we now have to |
| * emulate Class's behavior. |
| */ |
| if (resource.startsWith("/")) { |
| resource = resource.substring(1); |
| } else { |
| resource = "org/apache/tools/ant/taskdefs/optional/jdepend/" |
| + resource; |
| } |
| |
| File f = LoaderUtils.getResourceSource(getClass().getClassLoader(), |
| resource); |
| if (f != null) { |
| log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG); |
| runtimeClasses.createPath().setLocation(f); |
| } else { |
| log("Couldn\'t find " + resource, Project.MSG_DEBUG); |
| } |
| } |
| |
| /** |
| * execute the task |
| * |
| * @exception BuildException if an error occurs |
| */ |
| public void execute() throws BuildException { |
| |
| CommandlineJava commandline = new CommandlineJava(); |
| |
| if ("text".equals(format)) { |
| commandline.setClassname("jdepend.textui.JDepend"); |
| } else |
| if ("xml".equals(format)) { |
| commandline.setClassname("jdepend.xmlui.JDepend"); |
| } |
| |
| if (jvm != null) { |
| commandline.setVm(jvm); |
| } |
| if (getSourcespath() == null && getClassespath() == null) { |
| throw new BuildException("Missing classespath required argument"); |
| } else if (getClassespath() == null) { |
| String msg = |
| "sourcespath is deprecated in JDepend >= 2.5 " |
| + "- please convert to classespath"; |
| log(msg); |
| } |
| |
| // execute the test and get the return code |
| int exitValue = JDependTask.ERRORS; |
| boolean wasKilled = false; |
| if (!getFork()) { |
| exitValue = executeInVM(commandline); |
| } else { |
| ExecuteWatchdog watchdog = createWatchdog(); |
| exitValue = executeAsForked(commandline, watchdog); |
| // null watchdog means no timeout, you'd better not check with null |
| if (watchdog != null) { |
| wasKilled = watchdog.killedProcess(); |
| } |
| } |
| |
| // if there is an error/failure and that it should halt, stop |
| // everything otherwise just log a statement |
| boolean errorOccurred = exitValue == JDependTask.ERRORS || wasKilled; |
| |
| if (errorOccurred) { |
| String errorMessage = "JDepend FAILED" |
| + (wasKilled ? " - Timed out" : ""); |
| |
| if (getHaltonerror()) { |
| throw new BuildException(errorMessage, getLocation()); |
| } else { |
| log(errorMessage, Project.MSG_ERR); |
| } |
| } |
| } |
| |
| // this comment extract from JUnit Task may also apply here |
| // "in VM is not very nice since it could probably hang the |
| // whole build. IMHO this method should be avoided and it would be best |
| // to remove it in future versions. TBD. (SBa)" |
| |
| /** |
| * Execute inside VM. |
| * |
| * @param commandline the command line |
| * @return the return value of the mvm |
| * @exception BuildException if an error occurs |
| */ |
| public int executeInVM(CommandlineJava commandline) throws BuildException { |
| jdepend.textui.JDepend jdepend; |
| |
| if ("xml".equals(format)) { |
| jdepend = new jdepend.xmlui.JDepend(); |
| } else { |
| jdepend = new jdepend.textui.JDepend(); |
| } |
| |
| FileWriter fw = null; |
| if (getOutputFile() != null) { |
| try { |
| fw = new FileWriter(getOutputFile().getPath()); |
| } catch (IOException e) { |
| String msg = "JDepend Failed when creating the output file: " |
| + e.getMessage(); |
| log(msg); |
| throw new BuildException(msg); |
| } |
| jdepend.setWriter(new PrintWriter(fw)); |
| log("Output to be stored in " + getOutputFile().getPath()); |
| } |
| |
| |
| try { |
| if (getClassespath() != null) { |
| // This is the new, better way - use classespath instead |
| // of sourcespath. The code is currently the same - you |
| // need class files in a directory to use this or jar files. |
| String[] cP = getClassespath().list(); |
| for (int i = 0; i < cP.length; i++) { |
| File f = new File(cP[i]); |
| // not necessary as JDepend would fail, but why loose |
| // some time? |
| if (!f.exists()) { |
| String msg = "\"" |
| + f.getPath() |
| + "\" does not represent a valid" |
| + " file or directory. JDepend would fail."; |
| log(msg); |
| throw new BuildException(msg); |
| } |
| try { |
| jdepend.addDirectory(f.getPath()); |
| } catch (IOException e) { |
| String msg = |
| "JDepend Failed when adding a class directory: " |
| + e.getMessage(); |
| log(msg); |
| throw new BuildException(msg); |
| } |
| } |
| |
| } else if (getSourcespath() != null) { |
| |
| // This is the old way and is deprecated - classespath is |
| // the right way to do this and is above |
| String[] sP = getSourcespath().list(); |
| for (int i = 0; i < sP.length; i++) { |
| File f = new File(sP[i]); |
| |
| // not necessary as JDepend would fail, but why loose |
| // some time? |
| if (!f.exists() || !f.isDirectory()) { |
| String msg = "\"" |
| + f.getPath() |
| + "\" does not represent a valid" |
| + " directory. JDepend would fail."; |
| log(msg); |
| throw new BuildException(msg); |
| } |
| try { |
| jdepend.addDirectory(f.getPath()); |
| } catch (IOException e) { |
| String msg = |
| "JDepend Failed when adding a source directory: " |
| + e.getMessage(); |
| log(msg); |
| throw new BuildException(msg); |
| } |
| } |
| } |
| |
| // This bit turns <exclude> child tags into patters to ignore |
| String[] patterns = defaultPatterns.getExcludePatterns(getProject()); |
| if (patterns != null && patterns.length > 0) { |
| if (setFilter != null) { |
| Vector v = new Vector(); |
| for (int i = 0; i < patterns.length; i++) { |
| v.addElement(patterns[i]); |
| } |
| try { |
| Object o = packageFilterC.newInstance(new Object[] {v}); |
| setFilter.invoke(jdepend, new Object[] {o}); |
| } catch (Throwable e) { |
| log("excludes will be ignored as JDepend doesn't like me: " |
| + e.getMessage(), Project.MSG_WARN); |
| } |
| } else { |
| log("Sorry, your version of JDepend doesn't support excludes", |
| Project.MSG_WARN); |
| } |
| } |
| |
| jdepend.analyze(); |
| } finally { |
| FileUtils.close(fw); |
| } |
| return SUCCESS; |
| } |
| |
| |
| /** |
| * Execute the task by forking a new JVM. The command will block until |
| * it finishes. To know if the process was destroyed or not, use the |
| * <tt>killedProcess()</tt> method of the watchdog class. |
| * @param commandline the commandline for forked jvm |
| * @param watchdog the watchdog in charge of cancelling the test if it |
| * exceeds a certain amount of time. Can be <tt>null</tt>. |
| * @return the result of running the jdepend |
| * @throws BuildException in case of error |
| */ |
| // JL: comment extracted from JUnitTask (and slightly modified) |
| public int executeAsForked(CommandlineJava commandline, |
| ExecuteWatchdog watchdog) throws BuildException { |
| runtimeClasses = new Path(getProject()); |
| addClasspathEntry("/jdepend/textui/JDepend.class"); |
| |
| // if not set, auto-create the ClassPath from the project |
| createClasspath(); |
| |
| // not sure whether this test is needed but cost nothing to put. |
| // hope it will be reviewed by anybody competent |
| if (getClasspath().toString().length() > 0) { |
| createJvmarg(commandline).setValue("-classpath"); |
| createJvmarg(commandline).setValue(getClasspath().toString()); |
| } |
| |
| if (includeRuntime) { |
| Vector v = Execute.getProcEnvironment(); |
| Enumeration e = v.elements(); |
| while (e.hasMoreElements()) { |
| String s = (String) e.nextElement(); |
| if (s.startsWith("CLASSPATH=")) { |
| commandline.createClasspath(getProject()).createPath() |
| .append(new Path(getProject(), |
| s.substring("CLASSPATH=".length() |
| ))); |
| } |
| } |
| log("Implicitly adding " + runtimeClasses + " to CLASSPATH", |
| Project.MSG_VERBOSE); |
| commandline.createClasspath(getProject()).createPath() |
| .append(runtimeClasses); |
| } |
| |
| if (getOutputFile() != null) { |
| // having a space between the file and its path causes commandline |
| // to add quotes around the argument thus making JDepend not taking |
| // it into account. Thus we split it in two |
| commandline.createArgument().setValue("-file"); |
| commandline.createArgument().setValue(outputFile.getPath()); |
| // we have to find a cleaner way to put this output |
| } |
| |
| if (getSourcespath() != null) { |
| // This is deprecated - use classespath in the future |
| String[] sP = getSourcespath().list(); |
| for (int i = 0; i < sP.length; i++) { |
| File f = new File(sP[i]); |
| |
| // not necessary as JDepend would fail, but why loose |
| // some time? |
| if (!f.exists() || !f.isDirectory()) { |
| throw new BuildException("\"" + f.getPath() |
| + "\" does not represent a valid" |
| + " directory. JDepend would" |
| + " fail."); |
| } |
| commandline.createArgument().setValue(f.getPath()); |
| } |
| } |
| |
| if (getClassespath() != null) { |
| // This is the new way - use classespath - code is the |
| // same for now |
| String[] cP = getClassespath().list(); |
| for (int i = 0; i < cP.length; i++) { |
| File f = new File(cP[i]); |
| // not necessary as JDepend would fail, but why loose |
| // some time? |
| if (!f.exists()) { |
| throw new BuildException("\"" + f.getPath() |
| + "\" does not represent a valid" |
| + " file or directory. JDepend would" |
| + " fail."); |
| } |
| commandline.createArgument().setValue(f.getPath()); |
| } |
| } |
| |
| Execute execute = new Execute(new LogStreamHandler(this, |
| Project.MSG_INFO, Project.MSG_WARN), watchdog); |
| execute.setCommandline(commandline.getCommandline()); |
| if (getDir() != null) { |
| execute.setWorkingDirectory(getDir()); |
| execute.setAntRun(getProject()); |
| } |
| |
| if (getOutputFile() != null) { |
| log("Output to be stored in " + getOutputFile().getPath()); |
| } |
| log(commandline.describeCommand(), Project.MSG_VERBOSE); |
| try { |
| return execute.execute(); |
| } catch (IOException e) { |
| throw new BuildException("Process fork failed.", e, getLocation()); |
| } |
| } |
| |
| /** |
| * @return <tt>null</tt> if there is a timeout value, otherwise the |
| * watchdog instance. |
| * @throws BuildException in case of error |
| */ |
| protected ExecuteWatchdog createWatchdog() throws BuildException { |
| if (getTimeout() == null) { |
| return null; |
| } |
| return new ExecuteWatchdog(getTimeout().longValue()); |
| } |
| } |