/*
* Hammurapi
* Automated Java code review system.
* Copyright (C) 2004 Hammurapi Group
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* URL: http://www.hammurapi.org
* e-Mail: support@hammurapi.biz
*/
package org.hammurapi;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.hammurapi.results.CompositeResults;
import org.hammurapi.results.persistent.jdbc.ResultsFactory;
import org.hammurapi.results.persistent.jdbc.ResultsFactoryConfig;
import org.w3c.dom.Element;
import com.pavelvlasov.ant.ConnectionEntry;
import com.pavelvlasov.config.Component;
import com.pavelvlasov.config.ConfigurationException;
import com.pavelvlasov.jsel.JselException;
import com.pavelvlasov.jsel.Repository;
import com.pavelvlasov.jsel.RevisionMapper;
import com.pavelvlasov.jsel.impl.DbRepositoryImpl;
import com.pavelvlasov.jsel.impl.RepositoryConfig;
import com.pavelvlasov.jsel.impl.WarningSink;
import com.pavelvlasov.logging.AntLogger;
import com.pavelvlasov.logging.Logger;
import com.pavelvlasov.metrics.MeasurementCategoryFactory;
import com.pavelvlasov.metrics.TimeIntervalCategory;
import com.pavelvlasov.persistence.CompositeStorage;
import com.pavelvlasov.persistence.FileStorage;
import com.pavelvlasov.persistence.MemoryStorage;
import com.pavelvlasov.render.RenderingException;
import com.pavelvlasov.review.SimpleSourceMarker;
import com.pavelvlasov.review.SourceMarker;
import com.pavelvlasov.review.SourceMarkerComparator;
import com.pavelvlasov.sql.ConnectionPerThreadDataSource;
import com.pavelvlasov.sql.DataAccessObject;
import com.pavelvlasov.sql.JdbcStorage;
import com.pavelvlasov.sql.SQLProcessor;
import com.pavelvlasov.sql.Transaction;
import com.pavelvlasov.sql.hypersonic.HypersonicServerDataSource;
import com.pavelvlasov.sql.hypersonic.HypersonicStandaloneDataSource;
import com.pavelvlasov.sql.hypersonic.HypersonicTmpDataSource;
import com.pavelvlasov.util.VisitorStack;
import com.pavelvlasov.util.VisitorStackSource;
/**
* Performs automatic code reviews.
* <section name="Example" suppress-description="yes">
If you copy content of Hammurapi lib directory to ant lib directory then you can
invoke Hammurapi in the following way:
<pre>
<taskdef name="hammurapi" classname="org.hammurapi.HammurapiTask" /><br/>
<br/>
<hammurapi><br/>
<tab/><src dir="src"/><br/>
<tab/><output dir="review"/><br/>
</hammurapi></pre>
or, if you didn't copy jar files to Ant lib directory, use this syntax:
<pre>
<taskdef name="hammurapi" classname="org.hammurapi.HammurapiTask"><br/>
<tab/><classpath><br/>
<tab/><tab/><fileset dir="${hammurapi.home}/lib" includes="*.jar"/><br/>
<tab/></classpath><br/>
</taskdef><br/>
<br/>
<hammurapi><br/>
<tab/><src dir="src"/><br/>
<tab/><output dir="review"/><br/>
</hammurapi>
</pre>
</section>
* @ant.element name="hammurapi" display-name="Automatic code review task"
* @author Pavel Vlasov
* @version $Revision: 1.25 $
*/
public class HammurapiTask extends TaskBase {
/**
* Helper class to start/stop violation filters
* @author Pavel Vlasov
* @revision $Revision: 1.25 $
*/
public class ViolationFilterVisitor {
public void visit(Repository repo) throws ConfigurationException {
// Initializing violation filters
Iterator vfit=violationFilters.iterator();
while (vfit.hasNext()) {
Object vf=vfit.next();
if (vf instanceof Component) {
((Component) vf).start();
}
}
}
public void leave(Repository repo) throws ConfigurationException {
// Stopping violation filters
Iterator vfit=violationFilters.iterator();
while (vfit.hasNext()) {
Object vf=vfit.next();
if (vf instanceof Component) {
((Component) vf).stop();
}
}
}
}
private static final TimeIntervalCategory tic=MeasurementCategoryFactory.getTimeIntervalCategory(HammurapiTask.class);
private boolean wrap=false;
private boolean cleanup=true;
boolean skipIntactPackages=false;
private boolean forceOnWaivers;
/**
* Cleanup old reviews info after review.
* Defaults to 'true'.
* @ant.non-required
* @param cleanup
*/
public void setCleanup(boolean cleanup) {
this.cleanup = cleanup;
}
/**
* Do not generate summary pages for packages and summary
* if no files were changed in package/summary.
* Set it to 'true' to improve performance if you don not use
* 'New' marker on modified files.
* @ant.non-required
* @param skipIntactPackages
*/
public void setSkipIntactPackages(boolean skipIntactPackages) {
this.skipIntactPackages = skipIntactPackages;
}
/**
* Force review of compilation units for which waivers are available.
* Default is true.
* @ant.non-required.
*/
public void setForceOnWaivers(boolean forceOnWaivers) {
this.forceOnWaivers=forceOnWaivers;
}
boolean isForceOnWaivers() {
return forceOnWaivers;
}
private Date baseLine;
/**
* Date of baseline report
* @ant.non-required
* @param baseLine
*/
public void setBaseLine(Date baseLine) {
this.baseLine=baseLine;
}
private String hostId;
public void execute() throws BuildException {
long started=System.currentTimeMillis();
if (!suppressLogo) {
log("Hammurapi 3 Copyright (C) 2004 Hammurapi Group");
}
File archiveTmpDir=processArchive();
try {
Logger logger=new AntLogger(this);
final VisitorStack[] visitorStack={null};
final VisitorStackSource visitorStackSource=new VisitorStackSource() {
public VisitorStack getVisitorStack() {
return visitorStack[0];
}
};
final SessionImpl reviewSession=new SessionImpl();
InspectorSet inspectorSet=new InspectorSet(
new InspectorContextFactory() {
public InspectorContext newContext(InspectorDescriptor descriptor, Logger logger) {
return new InspectorContextImpl(
descriptor,
logger,
visitorStackSource,
reviewSession,
violationFilters);
}
},
logger);
if (embeddedInspectors) {
log("Loading embedded inspectors", Project.MSG_VERBOSE);
loadEmbeddedInspectors(inspectorSet);
}
log("Loading inspectors", Project.MSG_VERBOSE);
Iterator it=inspectors.iterator();
while (it.hasNext()) {
Object o=it.next();
if (o instanceof InspectorSource) {
((InspectorSource) o).loadInspectors(inspectorSet);
} else {
InspectorEntry inspectorEntry = (InspectorEntry) o;
inspectorSet.addDescriptor(inspectorEntry);
inspectorSet.addInspectorSourceInfo(
new InspectorSourceInfo(
"Inline inspector "+inspectorEntry.getName(),
"Build file: "+inspectorEntry.getLocation().toString(),
""));
}
}
log("Inspectors loaded: "+inspectorSet.size(), Project.MSG_VERBOSE);
log("Loading waivers", Project.MSG_VERBOSE);
Date now=new Date();
WaiverSet waiverSet=new WaiverSet();
it=waivers.iterator();
while (it.hasNext()) {
((WaiverSource) it.next()).loadWaivers(waiverSet,now);
}
log("Waivers loaded: "+waiverSet.size(), Project.MSG_VERBOSE);
log("Loading listeners", Project.MSG_VERBOSE);
List listeners=new LinkedList();
it=listenerEntries.iterator();
while (it.hasNext()) {
listeners.add(((ListenerEntry) it.next()).getObject(null));
}
//Outputs
listeners.addAll(outputs);
listeners.add(new ReviewToLogListener(project));
log("Loading source files", Project.MSG_VERBOSE);
RepositoryConfig config=new RepositoryConfig();
if (classPath!=null) {
log("Loading class files to repository", Project.MSG_DEBUG);
config.setClassLoader(new AntClassLoader(project, classPath, false));
reviewSession.setClassPath(classPath.list());
}
config.setLogger(logger);
config.setCalculateDependencies(calculateDependencies);
config.setStoreSource(storeSource);
config.setEncoding(getEncoding());
it=srcFileSets.iterator();
while (it.hasNext()) {
HammurapiFileSet fs=(HammurapiFileSet) it.next();
fs.setDefaultIncludes();
DirectoryScanner scanner=fs.getDirectoryScanner(project);
config.addFile(scanner.getBasedir(), scanner.getIncludedFiles());
}
/**
* For command-line interface
*/
it=srcFiles.iterator();
while (it.hasNext()) {
config.addFile((File) it.next());
}
config.setName(title);
if (revisionMapper!=null) {
config.setRevisionMapper((RevisionMapper) revisionMapper.getObject(null));
}
ConnectionPerThreadDataSource dataSource = createDataSource(reviewSession);
reviewSession.setDatasource(dataSource);
final LinkedList repoWarnings=new LinkedList();
config.setWarningSink(new WarningSink() {
public void consume(final String source, final String message) {
repoWarnings.add(new Violation() {
public String getMessage() {
return message;
}
public InspectorDescriptor getDescriptor() {
return null;
}
SourceMarker sm=new SimpleSourceMarker(0,0,source,null);
public SourceMarker getSource() {
return sm;
}
public int compareTo(Object obj) {
if (obj instanceof Violation) {
Violation v=(Violation) obj;
int c=SourceMarkerComparator._compare(getSource(), v.getSource());
return c==0 ? getMessage().compareTo(v.getMessage()) : c;
}
return hashCode()-obj.hashCode();
}
});
}
});
config.setDataSource(dataSource);
final SQLProcessor sqlProcessor=new SQLProcessor(dataSource, null);
sqlProcessor.setTimeIntervalCategory(tic);
DbRepositoryImpl repositoryImpl = new DbRepositoryImpl(config);
Repository repository = wrap ? (Repository) repositoryImpl.getProxy() : repositoryImpl;
//new SimpleResultsFactory(waiverSet).install();
ResultsFactoryConfig rfConfig=new ResultsFactoryConfig();
rfConfig.setInspectorSet(inspectorSet);
rfConfig.setName(title);
rfConfig.setReportNumber(repository.getScanNumber());
rfConfig.setRepository(repository);
rfConfig.setSqlProcessor(sqlProcessor);
rfConfig.setHostId(hostId);
rfConfig.setBaseLine(baseLine);
rfConfig.setDescription(reviewDescription);
try {
rfConfig.setHostName(InetAddress.getLocalHost().getHostName());
} catch (Exception e) {
log("Cannot resolve host name: "+e);
}
CompositeStorage storage=new CompositeStorage();
storage.addStorage("jdbc", new JdbcStorage(sqlProcessor));
storage.addStorage("file", new FileStorage(new File(System.getProperties().getProperty("java.io.tmpdir"))));
storage.addStorage("memory", new MemoryStorage());
rfConfig.setStorage(storage);
rfConfig.setWaiverSet(waiverSet);
ResultsFactory resultsFactory=new ResultsFactory(rfConfig);
resultsFactory.install();
CompositeResults summary=org.hammurapi.results.ResultsFactory.getInstance().newCompositeResults(title);
org.hammurapi.results.ResultsFactory.getInstance().setSummary(summary);
org.hammurapi.results.ResultsFactory.pushThreadResults(summary);
Collection inspectorsPerSe=new LinkedList(inspectorSet.getInspectors());
reviewSession.setInspectors(inspectorSet);
Iterator inspectorsIt=inspectorsPerSe.iterator();
log("Inspectors mapping", Project.MSG_VERBOSE);
while (inspectorsIt.hasNext()) {
Inspector inspector=(Inspector) inspectorsIt.next();
log("\t"+inspector.getContext().getDescriptor().getName()+" -> "+inspector.getClass().getName(), Project.MSG_VERBOSE);
}
// Initializes listeners
it=listeners.iterator();
while (it.hasNext()) {
((Listener) it.next()).onBegin(inspectorSet);
}
Iterator vfit=violationFilters.iterator();
while (vfit.hasNext()) {
Object vf=vfit.next();
if (vf instanceof DataAccessObject) {
((DataAccessObject) vf).setSQLProcessor(sqlProcessor);
}
}
ResultsCollector collector = new ResultsCollector(this, inspectorSet, waiverSet, summary, listeners);
inspectorsPerSe.add(collector);
// Storing repo warnings
while (!repoWarnings.isEmpty()) {
collector.getSummary().addWarning((Violation) repoWarnings.removeFirst());
}
log("Reviewing", Project.MSG_VERBOSE);
inspectorsPerSe.add(new ViolationFilterVisitor());
SimpleReviewEngine rengine = new SimpleReviewEngine(inspectorsPerSe, this);
reviewSession.setVisitor(rengine.getVisitor());
visitorStack[0]=rengine.getVisitorStack();
rengine.review(repository);
writeWaiverStubs(waiverSet.getRejectedRequests());
ResultsFactory.getInstance().commit(System.currentTimeMillis()-started);
if (cleanup) {
repositoryImpl.cleanupOldScans();
resultsFactory.cleanupOldReports();
}
repositoryImpl.stop();
reviewSession.shutdown();
resultsFactory.shutdown();
dataSource.shutdown();
//log("SQL metrics:\n"+resultsFactory.getSQLMetrics(),Project.MSG_VERBOSE);
if (hadExceptions) {
throw new BuildException("There have been exceptions during execution. Check log output.");
}
} catch (JselException e) {
throw new BuildException(e);
} catch (HammurapiException e) {
throw new BuildException(e);
} catch (ConfigurationException e) {
throw new BuildException(e);
} catch (FileNotFoundException e) {
throw new BuildException(e);
} catch (ClassNotFoundException e) {
throw new BuildException(e);
} catch (IOException e) {
throw new BuildException(e);
} catch (SQLException e) {
throw new BuildException(e);
} catch (RenderingException e) {
throw new BuildException(e);
} finally {
if (archiveTmpDir!=null) {
deleteFile(archiveTmpDir);
}
}
}
/**
* @param reviewSession
* @param hammurapiNameMap
* @param dataSource
* @param hammurapiNameMap
* @return
* @throws ClassNotFoundException
* @throws IOException
* @throws SQLException
*/
private ConnectionPerThreadDataSource createDataSource(final SessionImpl reviewSession) throws ClassNotFoundException, IOException, SQLException {
ConnectionPerThreadDataSource dataSource;
if (database==null && server==null && connection==null) {
dataSource = new HypersonicTmpDataSource(DbRepositoryImpl.HYPERSONIC_INIT_SCRIPT);
SQLProcessor sqlProcessor=new SQLProcessor(dataSource, null);
sqlProcessor.setTimeIntervalCategory(tic);
sqlProcessor.executeScript(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(ResultsFactory.HYPERSONIC_INIT_SCRIPT)));
reviewSession.scheduleInitDb();
} else if (database!=null && server==null && connection==null) {
reviewSession.setDbProperty("type", "Hypersonic");
dataSource = new HypersonicStandaloneDataSource(
database.getAbsolutePath(),
new Transaction() {
public boolean execute(SQLProcessor processor) throws SQLException {
processor.setTimeIntervalCategory(tic);
try {
processor.executeScript(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(DbRepositoryImpl.HYPERSONIC_INIT_SCRIPT)));
processor.executeScript(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(ResultsFactory.HYPERSONIC_INIT_SCRIPT)));
} catch (IOException e) {
throw new BuildException("Cannot initialize database", e);
}
reviewSession.scheduleInitDb();
return true;
}
});
} else if (database==null && server!=null && connection==null) {
dataSource=new HypersonicServerDataSource(server.getHost(), server.getUser(), server.getPassword(), null);
} else if (database==null && server==null && connection!=null) {
dataSource=connection.getDataSource();
} else {
throw new BuildException("server nested element, connection nested element and database attribute are mutually exclusive");
}
return dataSource;
}
/**
* Host id to differentiate reports created on different machines.
* @ant.non-required
*/
public void setHostId(String hostId) {
this.hostId=hostId;
}
private ServerEntry server;
private ConnectionEntry connection;
private boolean calculateDependencies;
private boolean storeSource;
/**
* Use it for inspector debugging
* @param args
*/
public static void main(String[] args) {
System.out.println("Hammurapi 3 Copyright (C) 2004 Hammurapi Group");
Options options=new Options();
populateOptions(options);
CommandLineParser parser=new PosixParser();
CommandLine line=null;
try {
line=parser.parse(options, args);
} catch (ParseException e) {
System.err.println(e.getMessage());
System.err.flush();
printHelpAndExit(options);
}
if (line.hasOption("h")) {
printHelpAndExit(options);
}
HammurapiTask task=new HammurapiTask();
Project project = new Project();
task.setProject(project);
project.setCoreLoader(task.getClass().getClassLoader());
task.configure(options, line);
task.suppressLogo=true;
task.setTaskName("hammurapi");
try {
task.execute();
System.exit(0);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
/**
* @param options
* @param line
* @param task
* @param project
*/
protected void configure(Options options, CommandLine line) {
super.configure(options, line);
if (line.hasOption('z')) {
setCalculateDependencies(true);
}
if (line.hasOption('b')) {
setStoreSource(true);
}
if (line.hasOption('n')) {
setBaseLine(new Date(line.getOptionValue('n')));
}
//if (line.hasOption('B')) {
// setBaselining(line.getOptionValue('B'));
//}
if (line.hasOption('H')) {
setHostId(line.getOptionValue('H'));
}
if (line.hasOption('L')) {
ConnectionEntry ce=new ConnectionEntry();
ce.setDriverClass(line.getOptionValue('L'));
ce.setUrl(line.getOptionValue('N'));
ce.setUser(line.getOptionValue('j'));
ce.setPassword(line.getOptionValue('p'));
addConnection(ce);
}
if (line.hasOption('R')) {
addServer(new ServerEntry(line.getOptionValue('R'), line.getOptionValue('j'), line.getOptionValue('p')));
}
if (line.hasOption('F')) {
setForceOnWaivers(false);
}
//setWrap(line.hasOption('r'));
}
//Anu 20050701 : Method moved to TaskBase.java
// /**
// * Sets baselining mode. Possible values:
// * off (default) - no baselining, on - do not report
// * violations stored in the baseline table, set - all violations
// * from current scan are saved to the baseline table.
// * The idea is to filter out all violations in
// * preexisting code and report only new violations.
// * Not all violations can be filtered out, only thouse
// * with signatures. Significant code modifications can surface some
// * baselined violation.
// * @ant.non-required
// * @param baselineMode
// */
// public void setBaselining(String baselineMode) {
// if ("off".equals(baselineMode)) {
// // Nothing.
// } else if ("on".equals(baselineMode)) {
// violationFilters.add(new BaselineViolationFilter());
// } else if ("set".equalsIgnoreCase(baselineMode)) {
// violationFilters.add(new BaselineSetupViolationFilter());
// } else {
// throw new BuildException("Invalid baselining mode: "+baselineMode);
// }
//
// }
/**
* @param options
*/
protected static void populateOptions(Options options) {
TaskBase.populateOptions(options);
Option hostIdOption=OptionBuilder
.withArgName("hostId")
.hasArg()
.withDescription("Host id")
.isRequired(false)
.create("H");
options.addOption(hostIdOption);
//Anu 20050701 : Moved to TaskBase.java
// Option baseliningOption=OptionBuilder
// .withArgName("off|on|set")
// .hasArg()
// .withDescription("Baselining mode")
// .isRequired(false)
// .create("B");
// options.addOption(baseliningOption);
Option serverOption=OptionBuilder
.withDescription("Database server name")
.withArgName("database server")
.hasArg()
.isRequired(false)
.create("R");
options.addOption(serverOption);
Option driverClassOption=OptionBuilder
.withDescription("Database driver class")
.withArgName("class name")
.hasArg()
.isRequired(false)
.create("L");
options.addOption(driverClassOption);
Option connectionUrlOption=OptionBuilder
.withDescription("Database connection URL")
.withArgName("url")
.hasArg()
.isRequired(false)
.create("N");
options.addOption(connectionUrlOption);
Option userOption=OptionBuilder
.withDescription("Database user")
.withArgName("user name")
.hasArg()
.isRequired(false)
.create("j");
options.addOption(userOption);
Option passwordOption=OptionBuilder
.withDescription("Database password")
.withArgName("password")
.hasArg()
.isRequired(false)
.create("p");
options.addOption(passwordOption);
Option baseLineOption=OptionBuilder
.withDescription("Baseline date")
.withArgName("date")
.hasArg()
.isRequired(false)
.create("n");
options.addOption(baseLineOption);
Option calculateDependenciesOption=OptionBuilder
.withDescription("Calculate dependencies")
.isRequired(false)
.create("z");
options.addOption(calculateDependenciesOption);
Option storeSourceOption=OptionBuilder
.withDescription("Store source")
.isRequired(false)
.create("b");
options.addOption(storeSourceOption);
Option forceOnWaiversOption=OptionBuilder
.withDescription("Do not force reviews on waivers")
.isRequired(false)
.create("F");
options.addOption(forceOnWaiversOption);
}
/**
* If set to 'true' Hammurapi stores dependency information to the
* database.
* @ant.non-required
* @param b
*/
public void setCalculateDependencies(boolean calculateDependencies) {
this.calculateDependencies=calculateDependencies;
}
/**
* If set to 'true' Hammurapi stores source code to the database.
* @ant.non-required
* @param b
*/
public void setStoreSource(boolean storeSource) {
this.storeSource=storeSource;
}
/**
* Database (Hypersonic) server to use as repository.
* @param entry
* @ant.non-required
*/
public void addServer(ServerEntry server) {
this.server=server;
}
/**
* Defines database server to be used as repository.
* Mutually exclusive with server nested element and database attribute.
* @ant.non-required.
* @param connection
*/
public void addConnection(ConnectionEntry connection) {
this.connection=connection;
}
/**
* @param config
* @throws ParseException
*/
protected void setAttributes(Element config) {
super.setAttributes(config);
if (config.hasAttribute("host-id")) {
setHostId(config.getAttribute("host-id"));
}
if (config.hasAttribute("baseline")) {
try {
setBaseLine(new SimpleDateFormat(HammurapiArchiver.DATE_FORMAT).parse(config.getAttribute("baseline")));
} catch (java.text.ParseException e) {
throw new BuildException("Cannot parse baseline date", e);
}
}
}
}
|