You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by sb...@apache.org on 2001/08/01 12:22:18 UTC
cvs commit: jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata MMetricsStreamHandler.java MMetrics.java MAuditStreamHandler.java MAudit.java AbstractMetamataTask.java
sbailliez 01/08/01 03:22:18
Added: src/main/org/apache/tools/ant/taskdefs/optional/metamata
MMetricsStreamHandler.java MMetrics.java
MAuditStreamHandler.java MAudit.java
AbstractMetamataTask.java
Log:
New Tasks for Metamata Audit and Metamata Metrics.
Since Metamata was acquired by Webgain, Quality Analyzer 2.0 is also compatible with them.
I'm using them for a while and they were requested by Garrick Olson, Garrick.Olson@Aceva.com
Revision Changes Path
1.1 jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java
Index: MMetricsStreamHandler.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.metamata;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.sax.*;
import java.util.*;
import java.io.*;
import java.text.*;
import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
/**
* A handy metrics handler. Most of this code was done only with the
* screenshots on the documentation since the evaluation version as
* of this writing does not allow to save metrics or to run it via
* command line.
* <p>
* This class can be used to transform a text file or to process the
* output stream directly.
*
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
*/
public class MMetricsStreamHandler implements ExecuteStreamHandler {
/** CLASS construct, it should be named something like 'MyClass' */
protected final static String CLASS = "class";
/** package construct, it should be look like 'com.mycompany.something' */
protected final static String PACKAGE = "package";
/** FILE construct, it should look like something 'MyClass.java' or 'MyClass.class' */
protected final static String FILE = "file";
/** METHOD construct, it should looke like something 'doSomething(...)' or 'doSomething()' */
protected final static String METHOD = "method";
protected final static String[] ATTRIBUTES = { "name", "vg", "loc",
"dit", "noa", "nrm", "nlm", "wmc", "rfc", "dac", "fanout", "cbo", "lcom", "nocl"
};
/** reader for stdout */
protected InputStream metricsOutput;
/**
* this is where the XML output will go, should mostly be a file
* the caller is responsible for flushing and closing this stream
*/
protected OutputStream xmlOutputStream;
/** metrics handler */
protected TransformerHandler metricsHandler;
/** the task */
protected Task task;
/**
* the stack where are stored the metrics element so that they we can
* know if we have to close an element or not.
*/
protected Stack stack = new Stack();
/** initialize this handler */
MMetricsStreamHandler(Task task, OutputStream xmlOut){
this.task = task;
this.xmlOutputStream = xmlOut;
}
/** Ignore. */
public void setProcessInputStream(OutputStream p1) throws IOException {
}
/** Ignore. */
public void setProcessErrorStream(InputStream p1) throws IOException {
}
/** Set the inputstream */
public void setProcessOutputStream(InputStream is) throws IOException {
metricsOutput = is;
}
public void start() throws IOException {
// create the transformer handler that will be used to serialize
// the output.
TransformerFactory factory = TransformerFactory.newInstance();
if ( !factory.getFeature(SAXTransformerFactory.FEATURE) ){
throw new IllegalStateException("Invalid Transformer factory feature");
}
try {
metricsHandler = ((SAXTransformerFactory)factory).newTransformerHandler();
metricsHandler.setResult( new StreamResult( new OutputStreamWriter(xmlOutputStream, "UTF-8")) );
Transformer transformer = metricsHandler.getTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
// start the document with a 'metrics' root
metricsHandler.startDocument();
AttributesImpl attr = new AttributesImpl();
attr.addAttribute("", "company", "company", "CDATA", "metamata");
metricsHandler.startElement("", "metrics", "metrics", attr);
// now parse the whole thing
parseOutput();
} catch (Exception e){
e.printStackTrace();
throw new IOException(e.getMessage());
}
}
/**
* Pretty dangerous business here.
*/
public void stop() {
try {
// we need to pop everything and close elements that have not been
// closed yet.
while ( stack.size() > 0){
ElementEntry elem = (ElementEntry)stack.pop();
metricsHandler.endElement("", elem.getType(), elem.getType());
}
// close the root
metricsHandler.endElement("", "metrics", "metrics");
// document is finished for good
metricsHandler.endDocument();
} catch (SAXException e){
e.printStackTrace();
throw new IllegalStateException(e.getMessage());
}
}
/** read each line and process it */
protected void parseOutput() throws IOException, SAXException {
BufferedReader br = new BufferedReader(new InputStreamReader(metricsOutput));
String line = null;
while ( (line = br.readLine()) != null ){
processLine(line);
}
}
/**
* Process a metrics line. If the metrics is invalid and that this is not
* the header line, it is display as info.
* @param line the line to process, it is normally a line full of metrics.
*/
protected void processLine(String line) throws SAXException {
if ( line.startsWith("Construct\tV(G)\tLOC\tDIT\tNOA\tNRM\tNLM\tWMC\tRFC\tDAC\tFANOUT\tCBO\tLCOM\tNOCL") ){
return;
}
try {
MetricsElement elem = MetricsElement.parse(line);
startElement(elem);
} catch (ParseException e) {
e.printStackTrace();
// invalid lines are sent to the output as information, it might be anything,
task.log(line, Project.MSG_INFO);
}
}
/**
* Start a new construct. Elements are popped until we are on the same
* parent node, then the element type is guessed and pushed on the
* stack.
* @param elem the element to process.
* @throws SAXException thrown if there is a problem when sending SAX events.
*/
protected void startElement(MetricsElement elem) throws SAXException {
// if there are elements in the stack we possibly need to close one or
// more elements previous to this one until we got its parent
int indent = elem.getIndent();
if ( stack.size() > 0 ){
ElementEntry previous = (ElementEntry)stack.peek();
// close nodes until you got the parent.
try {
while ( indent <= previous.getIndent() && stack.size() > 0){
stack.pop();
metricsHandler.endElement("", previous.getType(), previous.getType());
previous = (ElementEntry)stack.peek();
}
} catch (EmptyStackException ignored){}
}
// ok, now start the new construct
String type = getConstructType(elem);
Attributes attrs = createAttributes(elem);
metricsHandler.startElement("", type, type, attrs);
// make sure we keep track of what we did, that's history
stack.push( new ElementEntry(type, indent) );
}
/**
* return the construct type of the element. We can hardly recognize the
* type of a metrics element, so we are kind of forced to do some black
* magic based on the name and indentation to recognize the type.
* @param elem the metrics element to guess for its type.
* @return the type of the metrics element, either PACKAGE, FILE, CLASS or
* METHOD.
*/
protected String getConstructType(MetricsElement elem){
// ok no doubt, it's a file
if ( elem.isCompilationUnit() ){
return FILE;
}
// same, we're sure it's a method
if ( elem.isMethod() ){
return METHOD;
}
// if it's empty, and none of the above it should be a package
if ( stack.size() == 0 ){
return PACKAGE;
}
// ok, this is now black magic time, we will guess the type based on
// the previous type and its indent...
final ElementEntry previous = (ElementEntry)stack.peek();
final String prevType = previous.getType();
final int prevIndent = previous.getIndent();
final int indent = elem.getIndent();
// we're just under a file with a bigger indent so it's a class
if ( prevType.equals(FILE) && indent > prevIndent ){
return CLASS;
}
// we're just under a class with a greater or equals indent, it's a class
// (there might be several classes in a compilation unit and inner classes as well)
if ( prevType.equals(CLASS) && indent >= prevIndent ){
return CLASS;
}
// we assume the other are package
return PACKAGE;
}
/**
* Create all attributes of a MetricsElement skipping those who have an
* empty string
* @param elem
*/
protected Attributes createAttributes(MetricsElement elem){
AttributesImpl impl = new AttributesImpl();
int i = 0;
String name = ATTRIBUTES[i++];
impl.addAttribute("", name, name, "CDATA", elem.getName());
Enumeration metrics = elem.getMetrics();
for (; metrics.hasMoreElements(); i++){
String value = (String)metrics.nextElement();
if ( value.length() > 0 ){
name = ATTRIBUTES[i];
impl.addAttribute("", name, name, "CDATA", value);
}
}
return impl;
}
/**
* helper class to keep track of elements via its type and indent
* that's all we need to guess a type.
*/
private final static class ElementEntry {
private String type;
private int indent;
ElementEntry(String type, int indent){
this.type = type;
this.indent = indent;
}
public String getType(){
return type;
}
public int getIndent() {
return indent;
}
}
}
class MetricsElement {
private final static NumberFormat METAMATA_NF;
private final static NumberFormat NEUTRAL_NF;
static {
METAMATA_NF = NumberFormat.getInstance();
METAMATA_NF.setMaximumFractionDigits(1);
NEUTRAL_NF = NumberFormat.getInstance();
if (NEUTRAL_NF instanceof DecimalFormat) {
((DecimalFormat) NEUTRAL_NF).applyPattern("###0.###;-###0.###");
}
NEUTRAL_NF.setMaximumFractionDigits(1);
}
private int indent;
private String construct;
private Vector metrics;
MetricsElement(int indent, String construct, Vector metrics){
this.indent = indent;
this.construct = construct;
this.metrics = metrics;
}
public int getIndent(){
return indent;
}
public String getName(){
return construct;
}
public Enumeration getMetrics(){
return metrics.elements();
}
public boolean isCompilationUnit(){
return ( construct.endsWith(".java") || construct.endsWith(".class") );
}
public boolean isMethod(){
return ( construct.endsWith("(...)") || construct.endsWith("()") );
}
public static MetricsElement parse(String line) throws ParseException {
final Vector metrics = new Vector();
int pos;
// i'm using indexOf since I need to know if there are empty strings
// between tabs and I find it easier than with StringTokenizer
while ( (pos = line.indexOf('\t')) != -1 ){
String token = line.substring(0, pos);
// only parse what coudl be a valid number. ie not constructs nor no value
/*if (metrics.size() != 0 || token.length() != 0){
Number num = METAMATA_NF.parse(token); // parse with Metamata NF
token = NEUTRAL_NF.format(num.doubleValue()); // and format with a neutral NF
}*/
metrics.addElement( token );
line = line.substring(pos + 1);
}
metrics.addElement( line );
// there should be exactly 14 tokens (1 name + 13 metrics), if not, there is a problem !
if ( metrics.size() != 14 ){
throw new ParseException("Could not parse the following line as a metrics: -->" + line +"<--", -1);
}
// remove the first token it's made of the indentation string and the
// construct name, we'll need all this to figure out what type of
// construct it is since we lost all semantics :(
// (#indent[/]*)(#construct.*)
String name = (String)metrics.remove(0);
int indent = 0;
pos = name.lastIndexOf('/');
if (pos != -1){
name = name.substring(pos + 1);
indent = pos + 1; // indentation is last position of token + 1
}
return new MetricsElement(indent, name, metrics);
}
}
1.1 jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java
Index: MMetrics.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.metamata;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.*;
import org.apache.tools.ant.types.*;
import java.io.*;
import java.util.*;
/**
* Calculates global complexity and quality metrics on Java source code.
*
* You will not be able to use this task with the evaluation version since
* as of Metamata 2.0, Metrics does not support command line :-(
*
* For more information, visit the website at
* <a href="http://www.metamata.com">www.metamata.com</a>
*
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
*/
public class MMetrics extends AbstractMetamataTask {
/*
The command line options as of Metamata 2.0 are as follows:
Usage
mmetrics <option>... <path>...
Parameters
path File or directory to measure.
Options
-arguments -A <file> Includes command line arguments from file.
-classpath -cp <path> Sets class path (also source path unless one
explicitly set). Overrides METAPATH/CLASSPATH.
-compilation-units Measure compilation units.
-files Measure compilation units.
-format -f <format> Sets output format, default output file type.
-help -h Prints help and exits.
-indent -i <string> Sets string used to indent labels one level.
-methods Measure methods, types, and compilation units.
-output -o <file> Sets output file name.
-quiet -q Suppresses copyright message.
-sourcepath <path> Sets source path. Overrides SOURCEPATH.
-types Measure types and compilation units.
-verbose -v Prints all messages.
-version -V Prints version and exits.
Format Options
comma csv Format output as comma-separated text.
html htm Format output as an HTML table.
tab tab-separated tsv Format output as tab-separated text.
text txt Format output as space-aligned text.
*/
/** the granularity mode. Should be one of 'files', 'methods' and 'types'. */
protected String granularity = null;
/** the XML output file */
protected File outFile = null;
/** the location of the temporary txt report */
protected File tmpFile = createTmpFile();
protected Path path = null;
//--------------------------- PUBLIC METHODS -------------------------------
/** default constructor */
public MMetrics() {
super("com.metamata.sc.MMetrics");
}
/**
* set the granularity of the audit. Should be one of 'files', 'methods'
* or 'types'.
* @param granularity the audit reporting mode.
*/
public void setGranularity(String granularity){
this.granularity = granularity;
}
/**
* Set the output XML file
* @param file the xml file to write the XML report to.
*/
public void setTofile(File file){
this.outFile = file;
}
/**
* Set a new path (directory) to measure metrics from.
* @return the path instance to use.
*/
public Path createPath(){
if (path == null) {
path = new Path(project);
}
return path;
}
//------------------- PROTECTED / PRIVATE METHODS --------------------------
// check for existing options and outfile, all other are optional
protected void checkOptions() throws BuildException {
super.checkOptions();
if ( !"files".equals(granularity) && !"methods".equals(granularity)
&& !"types".equals(granularity) ){
throw new BuildException("Metrics reporting granularity is invalid. Must be one of 'files', 'methods', 'types'");
}
if (outFile == null){
throw new BuildException("Output XML file must be set via 'tofile' attribute.");
}
if (path == null && fileSets.size() == 0){
throw new BuildException("Must set either paths (path element) or files (fileset element)");
}
// I don't accept dirs and files at the same time, I cannot recognize the semantic in the result
if (path != null && fileSets.size() > 0){
throw new BuildException("Cannot set paths (path element) and files (fileset element) at the same time");
}
}
protected void execute0(ExecuteStreamHandler handler) throws BuildException {
super.execute0(handler);
transformFile();
}
/**
* transform the generated file via the handler
* This function can either be called if the result is written to the output
* file via -output or we could use the handler directly on stdout if not.
* @see #createStreamHandler()
*/
protected void transformFile() throws BuildException {
FileInputStream tmpStream = null;
try {
tmpStream = new FileInputStream( tmpFile );
} catch (IOException e){
throw new BuildException("Error reading temporary file: " + tmpFile, e);
}
FileOutputStream xmlStream = null;
try {
xmlStream = new FileOutputStream(outFile);
ExecuteStreamHandler xmlHandler = new MMetricsStreamHandler(this, xmlStream);
xmlHandler.setProcessOutputStream(tmpStream);
xmlHandler.start();
xmlHandler.stop();
} catch (IOException e){
throw new BuildException("Error creating output file: " + outFile, e);
} finally {
if (xmlStream != null){
try {
xmlStream.close();
} catch (IOException ignored){}
}
if (tmpStream != null){
try {
tmpStream.close();
} catch (IOException ignored){}
}
}
}
/** cleanup the temporary txt report */
protected void cleanUp() throws BuildException {
try {
super.cleanUp();
} finally {
if (tmpFile != null){
tmpFile.delete();
tmpFile = null;
}
}
}
/**
* if the report is transform via a temporary txt file we should use a
* a normal logger here, otherwise we could use the metrics handler
* directly to capture and transform the output on stdout to XML.
*/
protected ExecuteStreamHandler createStreamHandler(){
// write the report directtly to an XML stream
// return new MMetricsStreamHandler(this, xmlStream);
return new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO);
}
protected Vector getOptions(){
Vector options = new Vector(512);
// there is a bug in Metamata 2.0 build 37. The sourcepath argument does
// not work. So we will use the sourcepath prepended to classpath. (order
// is important since Metamata looks at .class and .java)
if (sourcePath != null){
sourcePath.append(classPath); // srcpath is prepended
classPath = sourcePath;
sourcePath = null; // prevent from using -sourcepath
}
// don't forget to modify the pattern if you change the options reporting
if (classPath != null){
options.addElement("-classpath");
options.addElement(classPath);
}
options.addElement( "-output" );
options.addElement( tmpFile.toString() );
options.addElement( "-" + granularity);
// display the metamata copyright
// options.addElement( "-quiet");
options.addElement( "-format");
// need this because that's what the handler is using, it's
// way easier to process than any other separator
options.addElement( "tab");
// specify a / as the indent character, used by the handler.
options.addElement( "-i");
options.addElement( "/");
// directories
String[] dirs = path.list();
for (int i = 0; i < dirs.length; i++){
options.addElement( dirs[i] );
}
// files next.
addAllVector(options, includedFiles.keys());
return options;
}
}
1.1 jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java
Index: MAuditStreamHandler.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.metamata;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.*;
import org.apache.tools.ant.types.*;
import org.apache.tools.ant.util.regexp.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.util.DOMElementWriter;
import org.w3c.dom.*;
import java.io.*;
import java.util.*;
import javax.xml.parsers.*;
/**
* This is a very bad stream handler for the MAudit task.
* All report to stdout that does not match a specific report pattern is dumped
* to the Ant output as warn level. The report that match the pattern is stored
* in a map with the key being the filepath that caused the error report.
* <p>
* The limitation with the choosen implementation is clear:
* <ul>
* <li>it does not handle multiline report( message that has \n ). the part until
* the \n will be stored and the other part (which will not match the pattern)
* will go to Ant output in Warn level.
* <li>it does not report error that goes to stderr.
* </ul>
*
* @author <a href="sbailliez@imediation.com">Stephane Bailliez</a>
*/
class MAuditStreamHandler implements ExecuteStreamHandler {
protected MAudit task;
/** reader for stdout */
protected BufferedReader br;
/** matcher that will be used to extract the info from the line */
protected RegexpMatcher matcher;
/**
* this is where the XML output will go, should mostly be a file
* the caller is responsible for flushing and closing this stream
*/
protected OutputStream xmlOut = null;
/**
* the multimap. The key in the map is the filepath that caused the audit
* error and the value is a vector of MAudit.Violation entries.
*/
protected Hashtable auditedFiles = new Hashtable();
MAuditStreamHandler(MAudit task, OutputStream xmlOut){
this.task = task;
this.xmlOut = xmlOut;
/** the matcher should be the Oro one. I don't know about the other one */
matcher = (new RegexpMatcherFactory()).newRegexpMatcher();
matcher.setPattern(MAudit.AUDIT_PATTERN);
}
/** Ignore. */
public void setProcessInputStream(OutputStream os) {}
/** Ignore. */
public void setProcessErrorStream(InputStream is) {}
/** Set the inputstream */
public void setProcessOutputStream(InputStream is) throws IOException {
br = new BufferedReader(new InputStreamReader(is));
}
/** Invokes parseOutput. This will block until the end :-(*/
public void start() throws IOException {
parseOutput(br);
}
/**
* Pretty dangerous business here. It serializes what was extracted from
* the MAudit output and write it to the output.
*/
public void stop() {
// serialize the content as XML, move this to another method
// this is the only code that could be needed to be overrided
Document doc = getDocumentBuilder().newDocument();
Element rootElement = doc.createElement("classes");
Enumeration keys = auditedFiles.keys();
Hashtable filemapping = task.getFileMapping();
rootElement.setAttribute("audited", String.valueOf(filemapping.size()));
rootElement.setAttribute("reported", String.valueOf(auditedFiles.size()));
int errors = 0;
while (keys.hasMoreElements()){
String filepath = (String)keys.nextElement();
Vector v = (Vector)auditedFiles.get(filepath);
String fullclassname = (String)filemapping.get(filepath);
if (fullclassname == null) {
task.getProject().log("Could not find class mapping for " + filepath, Project.MSG_WARN);
continue;
}
int pos = fullclassname.lastIndexOf('.');
String pkg = (pos == -1) ? "" : fullclassname.substring(0, pos);
String clazzname = (pos == -1) ? fullclassname : fullclassname.substring(pos + 1);
Element clazz = doc.createElement("class");
clazz.setAttribute("package", pkg);
clazz.setAttribute("name", clazzname);
clazz.setAttribute("violations", String.valueOf(v.size()));
errors += v.size();
for (int i = 0; i < v.size(); i++){
MAudit.Violation violation = (MAudit.Violation)v.elementAt(i);
Element error = doc.createElement("violation");
error.setAttribute("line", String.valueOf(violation.line));
error.setAttribute("message", violation.error);
clazz.appendChild(error);
}
rootElement.appendChild(clazz);
}
rootElement.setAttribute("violations", String.valueOf(errors));
// now write it to the outputstream, not very nice code
if (xmlOut != null) {
Writer wri = null;
try {
wri = new OutputStreamWriter(xmlOut, "UTF-8");
wri.write("<?xml version=\"1.0\"?>\n");
(new DOMElementWriter()).write(rootElement, wri, 0, " ");
wri.flush();
} catch(IOException exc) {
task.log("Unable to write log file", Project.MSG_ERR);
} finally {
if (xmlOut != System.out && xmlOut != System.err) {
if (wri != null) {
try {
wri.close();
} catch (IOException e) {}
}
}
}
}
}
protected static DocumentBuilder getDocumentBuilder() {
try {
return DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
catch(Exception exc) {
throw new ExceptionInInitializerError(exc);
}
}
/** read each line and process it */
protected void parseOutput(BufferedReader br) throws IOException {
String line = null;
while ( (line = br.readLine()) != null ){
processLine(line);
}
}
// we suppose here that there is only one report / line.
// There will obviouslly be a problem if the message is on several lines...
protected void processLine(String line){
Vector matches = matcher.getGroups(line);
if (matches != null) {
String file = (String)matches.elementAt(1);
int lineNum = Integer.parseInt((String)matches.elementAt(2));
String msg = (String)matches.elementAt(3);
addViolationEntry(file, MAudit.createViolation(lineNum, msg) );
} else {
// this doesn't match..report it as info, it could be
// either the copyright, summary or a multiline message (damn !)
task.log(line, Project.MSG_INFO);
}
}
/** add a violation entry for the file */
protected void addViolationEntry(String file, MAudit.Violation entry){
Vector violations = (Vector)auditedFiles.get(file);
// if there is no decl for this file yet, create it.
if (violations == null){
violations = new Vector();
auditedFiles.put(file, violations);
}
violations.add( entry );
}
}
1.1 jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java
Index: MAudit.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.metamata;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.*;
import org.apache.tools.ant.types.*;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.util.regexp.*;
import java.io.*;
import java.util.*;
/**
* Metamata Audit evaluates Java code for programming errors, weaknesses, and
* style violation.
* <p>
* Metamata Audit exists in three versions:
* <ul>
* <li>The Lite version evaluates about 15 built-in rules.</li>
* <li>The Pro version evaluates about 50 built-in rules.</li>
* <li>The Enterprise version allows you to add your own customized rules via the API.</li>
* <ul>
* For more information, visit the website at
* <a href="http://www.metamata.com">www.metamata.com</a>
*
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
*/
public class MAudit extends AbstractMetamataTask {
/* As of Metamata 2.0, the command line of MAudit is as follows:
Usage
maudit <option>... <path>... [-unused <search-path>...]
Parameters
path File or directory to audit.
search-path File or directory to search for declaration uses.
Options
-arguments -A <file> Includes command line arguments from file.
-classpath -cp <path> Sets class path (also source path unless one
explicitly set). Overrides METAPATH/CLASSPATH.
-exit -x Exits after the first error.
-fix -f Automatically fixes certain errors.
-fullpath Prints full path for locations.
-help -h Prints help and exits.
-list -l Creates listing file for each audited file.
-offsets -off Offset and length for locations.
-output -o <file> Prints output to file.
-quiet -q Suppresses copyright and summary messages.
-sourcepath <path> Sets source path. Overrides SOURCEPATH.
-tab -t Prints a tab character after first argument.
-unused -u Finds declarations unused in search paths.
-verbose -v Prints all messages.
-version -V Prints version and exits.
*/
//---------------------- PUBLIC METHODS ------------------------------------
/** pattern used by maudit to report the error for a file */
/** RE does not seems to support regexp pattern with comments so i'm stripping it*/
// (?:file:)?((?#filepath).+):((?#line)\\d+)\\s*:\\s+((?#message).*)
static final String AUDIT_PATTERN = "(?:file:)?(.+):(\\d+)\\s*:\\s+(.*)";
protected File outFile = null;
protected Path searchPath = null;
protected boolean fix = false;
protected boolean list = false;
protected boolean unused = false;
/** default constructor */
public MAudit() {
super("com.metamata.gui.rc.MAudit");
}
/** set the destination file which should be an xml file */
public void setTofile(File outFile){
this.outFile = outFile;
}
public void setFix(boolean flag){
this.fix = flag;
}
public void setList(boolean flag){
this.list = flag;
}
public void setUnused(boolean flag){
this.unused = flag;
}
public Path createSearchpath(){
if (searchPath == null){
searchPath = new Path(project);
}
return searchPath;
}
protected Vector getOptions(){
Vector options = new Vector(512);
// there is a bug in Metamata 2.0 build 37. The sourcepath argument does
// not work. So we will use the sourcepath prepended to classpath. (order
// is important since Metamata looks at .class and .java)
if (sourcePath != null){
sourcePath.append(classPath); // srcpath is prepended
classPath = sourcePath;
sourcePath = null; // prevent from using -sourcepath
}
// don't forget to modify the pattern if you change the options reporting
if (classPath != null){
options.addElement("-classpath");
options.addElement(classPath.toString());
}
// suppress copyright msg when running, we will let it so that this
// will be the only output to the console if in xml mode
// options.addElement("-quiet");
if (fix){
options.addElement("-fix");
}
options.addElement("-fullpath");
// generate .maudit files much more detailed than the report
// I don't like it very much, I think it could be interesting
// to get all .maudit files and include them in the XML.
if (list){
options.addElement("-list");
}
if (sourcePath != null){
options.addElement("-sourcepath");
options.addElement(sourcePath.toString());
}
if (unused){
options.addElement("-unused");
options.addElement(searchPath.toString());
}
addAllVector(options, includedFiles.keys());
return options;
}
protected void checkOptions() throws BuildException {
super.checkOptions();
if (unused && searchPath == null){
throw new BuildException("'searchpath' element must be set when looking for 'unused' declarations.");
}
if (!unused && searchPath != null){
log("'searchpath' element ignored. 'unused' attribute is disabled.", Project.MSG_WARN);
}
}
protected ExecuteStreamHandler createStreamHandler() throws BuildException {
ExecuteStreamHandler handler = null;
// if we didn't specify a file, then use a screen report
if (outFile == null){
handler = new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_INFO);
} else {
try {
//XXX
OutputStream out = new FileOutputStream( outFile );
handler = new MAuditStreamHandler(this, out);
} catch (IOException e){
throw new BuildException(e);
}
}
return handler;
}
protected void cleanUp() throws BuildException {
super.cleanUp();
// at this point if -list is used, we should move
// the .maudit file since we cannot choose their location :(
// the .maudit files match the .java files
// we'll use includedFiles to get the .maudit files.
/*if (out != null){
// close it if not closed by the handler...
}*/
}
/** the inner class used to report violation information */
static final class Violation {
int line;
String error;
}
/** handy factory to create a violation */
static final Violation createViolation(int line, String msg){
Violation violation = new Violation();
violation.line = line;
violation.error = msg;
return violation;
}
}
1.1 jakarta-ant/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java
Index: AbstractMetamataTask.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.metamata;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.*;
import org.apache.tools.ant.types.*;
import org.apache.tools.ant.DirectoryScanner;
import java.io.*;
import java.util.*;
/**
* Somewhat abstract framework to be used for other metama 2.0 tasks.
* This should include, audit, metrics, cover and mparse.
*
* For more information, visit the website at
* <a href="http://www.metamata.com">www.metamata.com</a>
*
* @author <a href="mailto:sbailliez@imediation.com">Stephane Bailliez</a>
*/
public abstract class AbstractMetamataTask extends Task{
//--------------------------- ATTRIBUTES -----------------------------------
/**
* The user classpath to be provided. It matches the -classpath of the
* command line. The classpath must includes both the <tt>.class</tt> and the
* <tt>.java</tt> files for accurate audit.
*/
protected Path classPath = null;
/** the path to the source file */
protected Path sourcePath = null;
/**
* Metamata home directory. It will be passed as a <tt>metamata.home</tt> property
* and should normally matches the environment property <tt>META_HOME</tt>
* set by the Metamata installer.
*/
protected File metamataHome = null;
/** the command line used to run MAudit */
protected CommandlineJava cmdl = new CommandlineJava();
/** the set of files to be audited */
protected Vector fileSets = new Vector();
/** the options file where are stored the command line options */
protected File optionsFile = null;
// this is used to keep track of which files were included. It will
// be set when calling scanFileSets();
protected Hashtable includedFiles = null;
public AbstractMetamataTask(){
}
/** initialize the task with the classname of the task to run */
protected AbstractMetamataTask(String className) {
cmdl.setVm("java");
cmdl.setClassname(className);
}
/** the metamata.home property to run all tasks. */
public void setMetamatahome(final File metamataHome){
this.metamataHome = metamataHome;
}
/** user classpath */
public Path createClasspath() {
if (classPath == null) {
classPath = new Path(project);
}
return classPath;
}
/** create the source path for this task */
public Path createSourcepath(){
if (sourcePath == null){
sourcePath = new Path(project);
}
return sourcePath;
}
/** Creates a nested jvmarg element. */
public Commandline.Argument createJvmarg() {
return cmdl.createVmArgument();
}
/** -mx or -Xmx depending on VM version */
public void setMaxmemory(String max){
if (Project.getJavaVersion().startsWith("1.1")) {
createJvmarg().setValue("-mx" + max);
} else {
createJvmarg().setValue("-Xmx" + max);
}
}
/** The java files or directory to be audited */
public void addFileSet(FileSet fs) {
fileSets.addElement(fs);
}
/** execute the command line */
public void execute() throws BuildException {
try {
setUp();
ExecuteStreamHandler handler = createStreamHandler();
execute0(handler);
} finally {
cleanUp();
}
}
//--------------------- PRIVATE/PROTECTED METHODS --------------------------
/** check the options and build the command line */
protected void setUp() throws BuildException {
checkOptions();
// set the classpath as the jar file
File jar = getMetamataJar(metamataHome);
final Path classPath = cmdl.createClasspath(project);
classPath.createPathElement().setLocation(jar);
// set the metamata.home property
final Commandline.Argument vmArgs = cmdl.createVmArgument();
vmArgs.setValue("-Dmetamata.home=" + metamataHome.getAbsolutePath() );
// retrieve all the files we want to scan
includedFiles = scanFileSets();
log(includedFiles.size() + " files added for audit", Project.MSG_VERBOSE);
// write all the options to a temp file and use it ro run the process
Vector options = getOptions();
optionsFile = createTmpFile();
generateOptionsFile(optionsFile, options);
Commandline.Argument args = cmdl.createArgument();
args.setLine("-arguments " + optionsFile.getAbsolutePath());
}
/**
* create a stream handler that will be used to get the output since
* metamata tools do not report with convenient files such as XML.
*/
protected abstract ExecuteStreamHandler createStreamHandler();
/** execute the process with a specific handler */
protected void execute0(ExecuteStreamHandler handler) throws BuildException {
final Execute process = new Execute(handler);
log(cmdl.toString(), Project.MSG_VERBOSE);
process.setCommandline(cmdl.getCommandline());
try {
if (process.execute() != 0) {
throw new BuildException("Metamata task failed.");
}
} catch (IOException e){
throw new BuildException("Failed to launch Metamata task: " + e);
}
}
/** clean up all the mess that we did with temporary objects */
protected void cleanUp(){
if (optionsFile != null){
optionsFile.delete();
optionsFile = null;
}
}
/** return the location of the jar file used to run */
protected final File getMetamataJar(File home){
return new File(home.getAbsoluteFile(), "lib/metamata.jar");
}
/** validate options set */
protected void checkOptions() throws BuildException {
// do some validation first
if (metamataHome == null || !metamataHome.exists()){
throw new BuildException("'metamatahome' must point to Metamata home directory.");
}
metamataHome = project.resolveFile(metamataHome.getPath());
File jar = getMetamataJar(metamataHome);
if (!jar.exists()){
throw new BuildException( jar + " does not exist. Check your metamata installation.");
}
}
/** return all options of the command line as string elements */
protected abstract Vector getOptions();
protected void generateOptionsFile(File tofile, Vector options) throws BuildException {
FileWriter fw = null;
try {
fw = new FileWriter(tofile);
PrintWriter pw = new PrintWriter(fw);
final int size = options.size();
for (int i = 0; i < size; i++){
pw.println( options.elementAt(i) );
}
pw.flush();
} catch (IOException e){
throw new BuildException("Error while writing options file " + tofile, e);
} finally {
if (fw != null){
try {
fw.close();
} catch (IOException ignored){}
}
}
}
protected Hashtable getFileMapping(){
return includedFiles;
}
/**
* convenient method for JDK 1.1. Will copy all elements from src to dest
*/
protected static final void addAllVector(Vector dest, Enumeration files){
while (files.hasMoreElements()) {
dest.addElement( files.nextElement() );
}
}
protected final static File createTmpFile(){
// must be compatible with JDK 1.1 !!!!
final long rand = (new Random(System.currentTimeMillis())).nextLong();
File file = new File("metamata" + rand + ".tmp");
return file;
}
/**
* @return the list of .java files (as their absolute path) that should
* be audited.
*/
protected Hashtable scanFileSets(){
Hashtable files = new Hashtable();
for (int i = 0; i < fileSets.size(); i++){
FileSet fs = (FileSet) fileSets.elementAt(i);
DirectoryScanner ds = fs.getDirectoryScanner(project);
ds.scan();
String[] f = ds.getIncludedFiles();
log(i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE);
for (int j = 0; j < f.length; j++){
String pathname = f[j];
if ( pathname.endsWith(".java") ){
File file = new File( ds.getBasedir(), pathname);
// file = project.resolveFile(file.getAbsolutePath());
String classname = pathname.substring(0, pathname.length()-".java".length());
classname = classname.replace(File.separatorChar, '.');
files.put( file.getAbsolutePath(), classname ); // it's a java file, add it.
}
}
}
return files;
}
}