/*
* 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.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
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.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.pavelvlasov.xml.dom.DOMUtils;
/**
* Packages source files and classpath entries for Hammurapi review.
* <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="har" classname="org.hammurapi.HammurapiArchiver" /><br/></pre>
or, if you didn't copy jar files to Ant lib directory, use this syntax:
<pre><taskdef name="har" classname="org.hammurapi.HammurapiArchiver"><br/>
<tab/><classpath><br/>
<tab/><tab/><fileset dir="${hammurapi.home}/lib" includes="*.jar"/><br/>
<tab/></classpath><br/>
</taskdef><br/></pre>
</section>
* @ant.element name="har" display-name="Packager for automatic code review task"
* @author Pavel Vlasov
* @version $Revision: 1.8 $
*/
public class HammurapiArchiver extends Task {
static final String DATE_FORMAT="yyyy/MM/dd HH:mm:ss";
private Boolean force;
/**
* Force review even if the file is not changed
* @param force
* @ant.non-required
*/
public void setForce(boolean force) {
this.force=force ? Boolean.TRUE : Boolean.FALSE;
}
private Boolean forceOnWarnings;
/**
* Force review of files with warnings, even if the file is not changed.
* @ant.non-required
*/
public void setForceOnWarnings(boolean forceOnWarnings) {
this.forceOnWarnings=forceOnWarnings ? Boolean.TRUE : Boolean.FALSE;
}
private String title;
/**
* Review title
* @ant.non-required
* @param title
*/
public void setTitle(String title) {
this.title=title;
}
private File output;
/**
* Output archive
* @ant.required
* @param output
*/
public void setOutput(File output) {
this.output=output;
}
/**
* Classpath for loading classes. Classes and jars from the classpath
* are packaged into archive to be used during review.
* @ant:non-required
*/
private Path classPath;
public void setClassPath(Path classPath) {
if (this.classPath == null) {
this.classPath = classPath;
} else {
this.classPath.append(classPath);
}
}
private Date baseLine;
/**
* Date of baseline report
* @ant.non-required
* @param baseLine
*/
public void setBaseLine(Date baseLine) {
this.baseLine=baseLine;
}
private String hostId;
/**
* Host id to differentiate archives created on different machines.
* @ant.non-required
*/
public void setHostId(String hostId) {
this.hostId=hostId;
}
//Anu 20050701 start : Added for baselining attribute
private String baselining;
public void setBaselining(String baselining){
this.baselining=baselining;
}
/**
* Maybe creates a nested classpath element.
* @ant:non-required
*/
public Path createClasspath() {
if (classPath == null) {
classPath = new Path(project);
}
return classPath.createPath();
}
private String uniquilize(String name, Set names) {
int idx=name.lastIndexOf('.');
String newName = name;
String ext="";
if (idx!=-1) {
ext=name.substring(idx);
name=name.substring(0, idx);
}
for (int i=0; names.contains(newName.toLowerCase()); i++) {
newName=name+"_"+Integer.toString(i, Character.MAX_RADIX)+ext;
}
names.add(newName.toLowerCase());
return newName;
}
private int zipFile(File in, ZipOutputStream out, String entryName) throws IOException {
if (in.isFile()) {
log("Zipping file "+in.getAbsolutePath()+" as "+entryName, Project.MSG_VERBOSE);
out.putNextEntry(new ZipEntry(entryName));
byte[] buf=new byte[4096];
int l;
FileInputStream fis=new FileInputStream(in);
while ((l=fis.read(buf))!=-1) {
out.write(buf, 0, l);
}
fis.close();
out.closeEntry();
return 1;
} else if (in.isDirectory()) {
int ret=0;
File[] entries=in.listFiles();
if (entries!=null && entries.length>0) {
log("Zipping directory "+in.getAbsolutePath()+" as "+entryName+"/", Project.MSG_VERBOSE);
out.putNextEntry(new ZipEntry(entryName+"/"));
for (int i=0; i<entries.length; i++) {
ret+=zipFile(entries[i], out, entryName+"/"+entries[i].getName());
}
out.closeEntry();
}
return ret;
}
return 0;
}
public void execute() throws BuildException {
try {
ZipOutputStream zos=new ZipOutputStream(new FileOutputStream(output));
Set entryNames=new HashSet();
Document config=DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element root=config.createElement("hammurapi-archive");
config.appendChild(root);
if (force!=null) {
root.setAttribute("force", force.booleanValue() ? "yes" : "no");
}
if (forceOnWarnings!=null) {
root.setAttribute("force-on-warnings", forceOnWarnings.booleanValue() ? "yes" : "no");
}
if (title!=null) {
root.setAttribute("title", title);
}
if (reviewDescription!=null) {
root.setAttribute("review-description", reviewDescription);
}
if (baseLine!=null) {
root.setAttribute("baseline", new SimpleDateFormat(DATE_FORMAT).format(baseLine));
}
if (hostId!=null) {
root.setAttribute("host-id", hostId);
}
//Anu 20060701 : Baselining added
if (baselining!=null) {
root.setAttribute("baselining", baselining);
}
Element classPathElement=config.createElement("classpath");
root.appendChild(classPathElement);
if (classPath!=null) {
String[] path=classPath.list();
for (int i=0; i<path.length; i++) {
File cpEntry=new File(path[i]);
if (cpEntry.exists() && (cpEntry.isFile() || cpEntry.isDirectory())) {
String name=uniquilize("lib/"+cpEntry.getName(), entryNames);
if (zipFile(cpEntry, zos, name)>0) {
Element pathElement=config.createElement("path");
classPathElement.appendChild(pathElement);
pathElement.appendChild(config.createTextNode(name));
}
} else {
log("Classpath entry "+cpEntry.getAbsolutePath()+" does not exist", Project.MSG_VERBOSE);
}
}
}
Element sourcesElement=config.createElement("sources");
root.appendChild(sourcesElement);
Iterator it=srcFileSets.iterator();
while (it.hasNext()) {
HammurapiFileSet fs=(HammurapiFileSet) it.next();
fs.setDefaultIncludes();
DirectoryScanner scanner=fs.getDirectoryScanner(project);
String name=uniquilize("source/"+scanner.getBasedir().getName(), entryNames);
Element sourceElement=config.createElement("source");
sourcesElement.appendChild(sourceElement);
sourceElement.appendChild(config.createTextNode(name));
String[] files=scanner.getIncludedFiles();
for (int i=0; i<files.length; i++) {
zipFile(new File(scanner.getBasedir(), files[i]), zos, name+"/"+files[i].replace(File.separatorChar, '/'));
}
}
/**
* For command-line interface
*/
it=srcFiles.iterator();
while (it.hasNext()) {
String name=uniquilize("source/source", entryNames);
File file = (File) it.next();
String entryName = name+"/"+file.getName();
Element sourceElement=config.createElement("source");
sourcesElement.appendChild(sourceElement);
sourceElement.appendChild(config.createTextNode(entryName));
zipFile(file, zos, entryName);
}
ZipEntry configEntry=new ZipEntry("config.xml");
zos.putNextEntry(configEntry);
DOMUtils.serialize(config, zos);
zos.closeEntry();
zos.close();
} catch (IOException e) {
throw new BuildException(e.getMessage(), e);
} catch (ParserConfigurationException e) {
throw new BuildException(e.getMessage(), e);
} catch (FactoryConfigurationError e) {
throw new BuildException(e.getMessage(), e);
} catch (TransformerException e) {
throw new BuildException(e.getMessage(), e);
}
}
/**
* Source files fileset.
* @ant.non-required
*/
public FileSet createSrc() {
FileSet ret=new HammurapiFileSet("**/*.java");
srcFileSets.add(ret);
return ret;
}
private List srcFileSets = new LinkedList();
private Collection srcFiles=new LinkedList();
private static void printHelpAndExit(Options options) {
HelpFormatter formatter=new HelpFormatter();
formatter.printHelp("Usage: har [options] <output file> <source files/dirs>", options, false);
System.exit(1);
}
/**
* 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();
Option classPathOption=OptionBuilder
.withArgName("classpath")
.hasArg()
.withDescription("ClassPath")
.isRequired(false)
.create("c");
options.addOption(classPathOption);
Option hostIdOption=OptionBuilder
.withArgName("hostId")
.hasArg()
.withDescription("Host id")
.isRequired(false)
.create("H");
options.addOption(hostIdOption);
Option titleOption=OptionBuilder
.withArgName("title")
.hasArg()
.withDescription("Report title")
.isRequired(false)
.create("T");
options.addOption(titleOption);
Option baseLineOption=OptionBuilder
.withDescription("Baseline date")
.withArgName("date")
.hasArg()
.isRequired(false)
.create("n");
options.addOption(baseLineOption);
Option forceOption=OptionBuilder
.withDescription("Force reviews on unchanged files")
.isRequired(false)
.create("f");
options.addOption(forceOption);
Option forceOnWarningsOption=OptionBuilder
.withDescription("Do not force reviews of files with warnings")
.isRequired(false)
.create("k");
options.addOption(forceOnWarningsOption);
Option descriptionOption=OptionBuilder
.withDescription("Review description")
.withArgName("description")
.hasArg()
.isRequired(false)
.create("y");
options.addOption(descriptionOption);
//Anu :20050701 Added baselining parameter
Option baseliningOption=OptionBuilder
.withArgName("off|on|set")
.hasArg()
.withDescription("Baselining mode")
.isRequired(false)
.create("B");
options.addOption(descriptionOption);
Option helpOption=OptionBuilder.withDescription("Print this message").isRequired(false).create("h");
options.addOption(helpOption);
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);
}
HammurapiArchiver task=new HammurapiArchiver();
Project project = new Project();
task.setProject(project);
project.setCoreLoader(task.getClass().getClassLoader());
String[] values=line.getOptionValues('c');
for (int i=0; values!=null && i<values.length; i++) {
task.createClasspath().append(new Path(project, values[i]));
}
String[] largs=line.getArgs();
if (largs.length==0) {
System.out.println("Output file has to be provided");
printHelpAndExit(options);
}
if (line.hasOption('f')) {
task.setForce(true);
}
if (line.hasOption('k')) {
task.setForceOnWarnings(false);
}
if (line.hasOption('n')) {
task.setBaseLine(new Date(line.getOptionValue('n')));
}
if (line.hasOption('H')) {
task.setHostId(line.getOptionValue('H'));
}
if (line.hasOption('y')) {
task.setReviewDescription(line.getOptionValue('y'));
}
if (line.hasOption('T')) {
task.setTitle(line.getOptionValue('T'));
}
//Anu :20050701 Added for Baselining attribute
if (line.hasOption('B')) {
task.setBaselining(line.getOptionValue('B'));
}
task.setOutput(new File(largs[0]));
for (int i=1; i<largs.length; i++) {
File file = new File(largs[i]);
if (file.isFile()) {
task.srcFiles.add(file);
} else if (file.isDirectory()) {
task.createSrc().setDir(file);
}
}
task.setTaskName("har");
try {
task.execute();
System.exit(0);
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
private String reviewDescription;
/**
* Description of review, e.g. release number. Appears in history annotation.
* @ant.non-required
*/
public void setReviewDescription(String reviewDescription) {
this.reviewDescription=reviewDescription;
}
}
|