001: /*
002: * Hammurapi
003: * Automated Java code review system.
004: * Copyright (C) 2004 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.org
021: * e-Mail: support@hammurapi.biz
022: */
023: package org.hammurapi;
024:
025: import java.io.File;
026: import java.io.FileInputStream;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.text.SimpleDateFormat;
030: import java.util.Collection;
031: import java.util.Date;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.LinkedList;
035: import java.util.List;
036: import java.util.Set;
037: import java.util.zip.ZipEntry;
038: import java.util.zip.ZipOutputStream;
039:
040: import javax.xml.parsers.DocumentBuilderFactory;
041: import javax.xml.parsers.FactoryConfigurationError;
042: import javax.xml.parsers.ParserConfigurationException;
043: import javax.xml.transform.TransformerException;
044:
045: import org.apache.commons.cli.CommandLine;
046: import org.apache.commons.cli.CommandLineParser;
047: import org.apache.commons.cli.HelpFormatter;
048: import org.apache.commons.cli.Option;
049: import org.apache.commons.cli.OptionBuilder;
050: import org.apache.commons.cli.Options;
051: import org.apache.commons.cli.ParseException;
052: import org.apache.commons.cli.PosixParser;
053: import org.apache.tools.ant.BuildException;
054: import org.apache.tools.ant.DirectoryScanner;
055: import org.apache.tools.ant.Project;
056: import org.apache.tools.ant.Task;
057: import org.apache.tools.ant.types.FileSet;
058: import org.apache.tools.ant.types.Path;
059: import org.w3c.dom.Document;
060: import org.w3c.dom.Element;
061:
062: import com.pavelvlasov.xml.dom.DOMUtils;
063:
064: /**
065: * Packages source files and classpath entries for Hammurapi review.
066: * <section name="Example" suppress-description="yes">
067: If you copy content of Hammurapi lib directory to ant lib directory then you can
068: invoke Hammurapi in the following way:
069: <pre><taskdef name="har" classname="org.hammurapi.HammurapiArchiver" /><br/></pre>
070: or, if you didn't copy jar files to Ant lib directory, use this syntax:
071: <pre><taskdef name="har" classname="org.hammurapi.HammurapiArchiver"><br/>
072: <tab/><classpath><br/>
073: <tab/><tab/><fileset dir="${hammurapi.home}/lib" includes="*.jar"/><br/>
074: <tab/></classpath><br/>
075: </taskdef><br/></pre>
076: </section>
077: * @ant.element name="har" display-name="Packager for automatic code review task"
078: * @author Pavel Vlasov
079: * @version $Revision: 1.8 $
080: */
081: public class HammurapiArchiver extends Task {
082: static final String DATE_FORMAT = "yyyy/MM/dd HH:mm:ss";
083:
084: private Boolean force;
085:
086: /**
087: * Force review even if the file is not changed
088: * @param force
089: * @ant.non-required
090: */
091: public void setForce(boolean force) {
092: this .force = force ? Boolean.TRUE : Boolean.FALSE;
093: }
094:
095: private Boolean forceOnWarnings;
096:
097: /**
098: * Force review of files with warnings, even if the file is not changed.
099: * @ant.non-required
100: */
101: public void setForceOnWarnings(boolean forceOnWarnings) {
102: this .forceOnWarnings = forceOnWarnings ? Boolean.TRUE
103: : Boolean.FALSE;
104: }
105:
106: private String title;
107:
108: /**
109: * Review title
110: * @ant.non-required
111: * @param title
112: */
113: public void setTitle(String title) {
114: this .title = title;
115: }
116:
117: private File output;
118:
119: /**
120: * Output archive
121: * @ant.required
122: * @param output
123: */
124: public void setOutput(File output) {
125: this .output = output;
126: }
127:
128: /**
129: * Classpath for loading classes. Classes and jars from the classpath
130: * are packaged into archive to be used during review.
131: * @ant:non-required
132: */
133: private Path classPath;
134:
135: public void setClassPath(Path classPath) {
136: if (this .classPath == null) {
137: this .classPath = classPath;
138: } else {
139: this .classPath.append(classPath);
140: }
141: }
142:
143: private Date baseLine;
144:
145: /**
146: * Date of baseline report
147: * @ant.non-required
148: * @param baseLine
149: */
150: public void setBaseLine(Date baseLine) {
151: this .baseLine = baseLine;
152: }
153:
154: private String hostId;
155:
156: /**
157: * Host id to differentiate archives created on different machines.
158: * @ant.non-required
159: */
160: public void setHostId(String hostId) {
161: this .hostId = hostId;
162: }
163:
164: //Anu 20050701 start : Added for baselining attribute
165: private String baselining;
166:
167: public void setBaselining(String baselining) {
168: this .baselining = baselining;
169: }
170:
171: /**
172: * Maybe creates a nested classpath element.
173: * @ant:non-required
174: */
175: public Path createClasspath() {
176: if (classPath == null) {
177: classPath = new Path(project);
178: }
179: return classPath.createPath();
180: }
181:
182: private String uniquilize(String name, Set names) {
183: int idx = name.lastIndexOf('.');
184: String newName = name;
185: String ext = "";
186: if (idx != -1) {
187: ext = name.substring(idx);
188: name = name.substring(0, idx);
189: }
190:
191: for (int i = 0; names.contains(newName.toLowerCase()); i++) {
192: newName = name + "_"
193: + Integer.toString(i, Character.MAX_RADIX) + ext;
194: }
195: names.add(newName.toLowerCase());
196: return newName;
197: }
198:
199: private int zipFile(File in, ZipOutputStream out, String entryName)
200: throws IOException {
201: if (in.isFile()) {
202: log("Zipping file " + in.getAbsolutePath() + " as "
203: + entryName, Project.MSG_VERBOSE);
204: out.putNextEntry(new ZipEntry(entryName));
205: byte[] buf = new byte[4096];
206: int l;
207: FileInputStream fis = new FileInputStream(in);
208: while ((l = fis.read(buf)) != -1) {
209: out.write(buf, 0, l);
210: }
211: fis.close();
212: out.closeEntry();
213: return 1;
214: } else if (in.isDirectory()) {
215: int ret = 0;
216: File[] entries = in.listFiles();
217: if (entries != null && entries.length > 0) {
218: log("Zipping directory " + in.getAbsolutePath()
219: + " as " + entryName + "/", Project.MSG_VERBOSE);
220: out.putNextEntry(new ZipEntry(entryName + "/"));
221: for (int i = 0; i < entries.length; i++) {
222: ret += zipFile(entries[i], out, entryName + "/"
223: + entries[i].getName());
224: }
225: out.closeEntry();
226: }
227: return ret;
228: }
229:
230: return 0;
231: }
232:
233: public void execute() throws BuildException {
234: try {
235: ZipOutputStream zos = new ZipOutputStream(
236: new FileOutputStream(output));
237: Set entryNames = new HashSet();
238:
239: Document config = DocumentBuilderFactory.newInstance()
240: .newDocumentBuilder().newDocument();
241: Element root = config.createElement("hammurapi-archive");
242: config.appendChild(root);
243:
244: if (force != null) {
245: root.setAttribute("force", force.booleanValue() ? "yes"
246: : "no");
247: }
248:
249: if (forceOnWarnings != null) {
250: root.setAttribute("force-on-warnings", forceOnWarnings
251: .booleanValue() ? "yes" : "no");
252: }
253:
254: if (title != null) {
255: root.setAttribute("title", title);
256: }
257:
258: if (reviewDescription != null) {
259: root.setAttribute("review-description",
260: reviewDescription);
261: }
262:
263: if (baseLine != null) {
264: root.setAttribute("baseline", new SimpleDateFormat(
265: DATE_FORMAT).format(baseLine));
266: }
267:
268: if (hostId != null) {
269: root.setAttribute("host-id", hostId);
270: }
271:
272: //Anu 20060701 : Baselining added
273: if (baselining != null) {
274: root.setAttribute("baselining", baselining);
275: }
276:
277: Element classPathElement = config
278: .createElement("classpath");
279: root.appendChild(classPathElement);
280:
281: if (classPath != null) {
282: String[] path = classPath.list();
283: for (int i = 0; i < path.length; i++) {
284: File cpEntry = new File(path[i]);
285: if (cpEntry.exists()
286: && (cpEntry.isFile() || cpEntry
287: .isDirectory())) {
288: String name = uniquilize("lib/"
289: + cpEntry.getName(), entryNames);
290: if (zipFile(cpEntry, zos, name) > 0) {
291: Element pathElement = config
292: .createElement("path");
293: classPathElement.appendChild(pathElement);
294: pathElement.appendChild(config
295: .createTextNode(name));
296: }
297: } else {
298: log("Classpath entry "
299: + cpEntry.getAbsolutePath()
300: + " does not exist",
301: Project.MSG_VERBOSE);
302: }
303: }
304: }
305:
306: Element sourcesElement = config.createElement("sources");
307: root.appendChild(sourcesElement);
308:
309: Iterator it = srcFileSets.iterator();
310: while (it.hasNext()) {
311: HammurapiFileSet fs = (HammurapiFileSet) it.next();
312: fs.setDefaultIncludes();
313: DirectoryScanner scanner = fs
314: .getDirectoryScanner(project);
315: String name = uniquilize("source/"
316: + scanner.getBasedir().getName(), entryNames);
317: Element sourceElement = config.createElement("source");
318: sourcesElement.appendChild(sourceElement);
319: sourceElement.appendChild(config.createTextNode(name));
320: String[] files = scanner.getIncludedFiles();
321: for (int i = 0; i < files.length; i++) {
322: zipFile(new File(scanner.getBasedir(), files[i]),
323: zos, name
324: + "/"
325: + files[i].replace(
326: File.separatorChar, '/'));
327: }
328: }
329:
330: /**
331: * For command-line interface
332: */
333: it = srcFiles.iterator();
334: while (it.hasNext()) {
335: String name = uniquilize("source/source", entryNames);
336: File file = (File) it.next();
337: String entryName = name + "/" + file.getName();
338: Element sourceElement = config.createElement("source");
339: sourcesElement.appendChild(sourceElement);
340: sourceElement.appendChild(config
341: .createTextNode(entryName));
342: zipFile(file, zos, entryName);
343: }
344:
345: ZipEntry configEntry = new ZipEntry("config.xml");
346: zos.putNextEntry(configEntry);
347: DOMUtils.serialize(config, zos);
348: zos.closeEntry();
349:
350: zos.close();
351: } catch (IOException e) {
352: throw new BuildException(e.getMessage(), e);
353: } catch (ParserConfigurationException e) {
354: throw new BuildException(e.getMessage(), e);
355: } catch (FactoryConfigurationError e) {
356: throw new BuildException(e.getMessage(), e);
357: } catch (TransformerException e) {
358: throw new BuildException(e.getMessage(), e);
359: }
360: }
361:
362: /**
363: * Source files fileset.
364: * @ant.non-required
365: */
366: public FileSet createSrc() {
367: FileSet ret = new HammurapiFileSet("**/*.java");
368: srcFileSets.add(ret);
369: return ret;
370: }
371:
372: private List srcFileSets = new LinkedList();
373:
374: private Collection srcFiles = new LinkedList();
375:
376: private static void printHelpAndExit(Options options) {
377: HelpFormatter formatter = new HelpFormatter();
378: formatter
379: .printHelp(
380: "Usage: har [options] <output file> <source files/dirs>",
381: options, false);
382: System.exit(1);
383: }
384:
385: /**
386: * Use it for inspector debugging
387: * @param args
388: */
389: public static void main(String[] args) {
390: System.out
391: .println("Hammurapi 3 Copyright (C) 2004 Hammurapi Group");
392:
393: Options options = new Options();
394:
395: Option classPathOption = OptionBuilder.withArgName("classpath")
396: .hasArg().withDescription("ClassPath")
397: .isRequired(false).create("c");
398:
399: options.addOption(classPathOption);
400:
401: Option hostIdOption = OptionBuilder.withArgName("hostId")
402: .hasArg().withDescription("Host id").isRequired(false)
403: .create("H");
404:
405: options.addOption(hostIdOption);
406:
407: Option titleOption = OptionBuilder.withArgName("title")
408: .hasArg().withDescription("Report title").isRequired(
409: false).create("T");
410:
411: options.addOption(titleOption);
412:
413: Option baseLineOption = OptionBuilder.withDescription(
414: "Baseline date").withArgName("date").hasArg()
415: .isRequired(false).create("n");
416:
417: options.addOption(baseLineOption);
418:
419: Option forceOption = OptionBuilder.withDescription(
420: "Force reviews on unchanged files").isRequired(false)
421: .create("f");
422:
423: options.addOption(forceOption);
424:
425: Option forceOnWarningsOption = OptionBuilder.withDescription(
426: "Do not force reviews of files with warnings")
427: .isRequired(false).create("k");
428:
429: options.addOption(forceOnWarningsOption);
430:
431: Option descriptionOption = OptionBuilder.withDescription(
432: "Review description").withArgName("description")
433: .hasArg().isRequired(false).create("y");
434: options.addOption(descriptionOption);
435:
436: //Anu :20050701 Added baselining parameter
437: Option baseliningOption = OptionBuilder.withArgName(
438: "off|on|set").hasArg().withDescription(
439: "Baselining mode").isRequired(false).create("B");
440:
441: options.addOption(descriptionOption);
442:
443: Option helpOption = OptionBuilder.withDescription(
444: "Print this message").isRequired(false).create("h");
445: options.addOption(helpOption);
446:
447: CommandLineParser parser = new PosixParser();
448: CommandLine line = null;
449: try {
450: line = parser.parse(options, args);
451: } catch (ParseException e) {
452: System.err.println(e.getMessage());
453: System.err.flush();
454: printHelpAndExit(options);
455: }
456:
457: if (line.hasOption("h")) {
458: printHelpAndExit(options);
459: }
460:
461: HammurapiArchiver task = new HammurapiArchiver();
462: Project project = new Project();
463: task.setProject(project);
464: project.setCoreLoader(task.getClass().getClassLoader());
465:
466: String[] values = line.getOptionValues('c');
467: for (int i = 0; values != null && i < values.length; i++) {
468: task.createClasspath().append(new Path(project, values[i]));
469: }
470:
471: String[] largs = line.getArgs();
472: if (largs.length == 0) {
473: System.out.println("Output file has to be provided");
474: printHelpAndExit(options);
475: }
476:
477: if (line.hasOption('f')) {
478: task.setForce(true);
479: }
480:
481: if (line.hasOption('k')) {
482: task.setForceOnWarnings(false);
483: }
484:
485: if (line.hasOption('n')) {
486: task.setBaseLine(new Date(line.getOptionValue('n')));
487: }
488:
489: if (line.hasOption('H')) {
490: task.setHostId(line.getOptionValue('H'));
491: }
492:
493: if (line.hasOption('y')) {
494: task.setReviewDescription(line.getOptionValue('y'));
495: }
496:
497: if (line.hasOption('T')) {
498: task.setTitle(line.getOptionValue('T'));
499: }
500:
501: //Anu :20050701 Added for Baselining attribute
502: if (line.hasOption('B')) {
503: task.setBaselining(line.getOptionValue('B'));
504: }
505:
506: task.setOutput(new File(largs[0]));
507:
508: for (int i = 1; i < largs.length; i++) {
509: File file = new File(largs[i]);
510: if (file.isFile()) {
511: task.srcFiles.add(file);
512: } else if (file.isDirectory()) {
513: task.createSrc().setDir(file);
514: }
515: }
516:
517: task.setTaskName("har");
518:
519: try {
520: task.execute();
521: System.exit(0);
522: } catch (Exception e) {
523: e.printStackTrace();
524: System.exit(2);
525: }
526: }
527:
528: private String reviewDescription;
529:
530: /**
531: * Description of review, e.g. release number. Appears in history annotation.
532: * @ant.non-required
533: */
534: public void setReviewDescription(String reviewDescription) {
535: this.reviewDescription = reviewDescription;
536: }
537:
538: }
|