/*
* GeoTools - The Open Source Java GIS Tookit
* http://geotools.org
*
* (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.maven.tools;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Inserts the @source tag into class header javadocs. Only modifies source files that
* meet the following criteria:
* <ul>
* <li> Contain a public class or interface
* <li> Already have a class header javadoc comment block
* <li> Do not already have the @source tag present
* </ul>
*
* Adapted from the CommentUpdater class previously in this package that was written
* by Martin Desruisseaux.
*
* @author Michael Bedward
* @source $URL$
* @version $Id$
*/
public class InsertSourceTag {
private final Pattern findSVNLine = Pattern.compile(".+\\/(trunk|tags|branches)\\/.*\\.java");
private final Pattern findCommentStart = Pattern.compile("^\\s*\\Q/**\\E");
private final Pattern findCommentEnd = Pattern.compile("\\Q*/\\E");
private final Pattern findSourceTag = Pattern.compile("^(\\s|\\*)*\\Q@source\\E");
private final Pattern findVersionTag = Pattern.compile("^(\\s|\\*)*\\Q@version\\E");
private final Pattern findClass = Pattern.compile("\\s*public[a-zA-Z\\s]+(class|interface)");
private final String lineSeparator = System.getProperty("line.separator", "\n");
/**
* Main method. Takes the name of the file or directory to process from the
* first command line argument provided (only the first is examined). If a
* directory, all child directories and java source files will be processed.
* <p>
* Note: local backup files are <b>not</b> saved by this program.
*/
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("usage: InsertSourceTag fileOrDirName");
}
File file = new File(args[0]);
if (!file.exists()) {
System.out.println("Can't find " + file);
return;
}
InsertSourceTag me = new InsertSourceTag();
me.process(file);
}
/**
* Process the given file or directory. If a directory, this method will
* be called recursively for all child directories and files.
*
* @param file the file or directory to be processed
*/
private void process(File file) {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
process(child);
}
} else {
if (file.getName().endsWith(".java")) {
try {
System.out.println(file.getPath());
processFile(file);
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}
}
}
/**
* This method performs the task of searching the file for a public class or interface
* and its associated javadoc comment block. If found, the comment block is searched
* for a source tag which, if absent, will be generated and inserted into the file.
*
* @param file file to process
* @return true if the source tag was inserted into the file; false otherwise
*
* @throws FileNotFoundException
* @throws IOException
*/
private boolean processFile(File file) throws FileNotFoundException, IOException {
List<String> buffer = new ArrayList<String>();
boolean inCommentBlock = false;
boolean nonEmptyLines = false;
boolean completedSearch = false;
int commentStartLine = -1;
int commentEndLine = -1;
int sourceTagLine = -1;
Matcher matcher = null;
String text;
String sourceTagText;
/**
* Find the svn repo path: trunk, tags or branches
*/
matcher = findSVNLine.matcher(file.getAbsolutePath());
if (matcher.matches()) {
int pos = matcher.start(1);
StringBuilder sb = new StringBuilder(" * @source $URL: ");
sb.append("http://svn.somewhere.foo/org/geotools/");
sb.append(file.getAbsolutePath().substring(pos));
sb.append(" $");
sourceTagText = sb.toString();
} else {
// don't process this file
return false;
}
LineNumberReader reader = new LineNumberReader( new FileReader(file) );
/**
* We want the first line of text to be line 0 so
* that line numbers match buffer indices
*/
reader.setLineNumber(-1);
while ( (text = reader.readLine()) != null ) {
buffer.add(text);
if (completedSearch) {
continue;
}
if (inCommentBlock) {
matcher = findCommentEnd.matcher(text);
if (matcher.find()) {
inCommentBlock = false;
commentEndLine = reader.getLineNumber();
}
} else {
matcher = findCommentStart.matcher(text);
if (matcher.find()) {
inCommentBlock = true;
nonEmptyLines = false;
commentStartLine = reader.getLineNumber();
} else {
matcher = findClass.matcher(text);
if (matcher.find()) {
/*
* If no javadoc comment block preceded the class header
* there is nothing to do
*/
if (commentStartLine < 0) {
return false;
}
/* If there were any non-blank lines between the comment and
* the class header we will act safely and not modify the file
*/
if (nonEmptyLines) {
return false;
}
/*
* Check if the source tag already exists
*/
for (int i = commentStartLine; i <= commentEndLine; i++) {
matcher = findSourceTag.matcher(buffer.get(i));
if (matcher.find()) {
return false;
}
}
/*
* Check if the version tag exists. If it does we
* will place the source tag on the line before it
*/
for (int i = commentStartLine; i <= commentEndLine; i++) {
matcher = findVersionTag.matcher(buffer.get(i));
if (matcher.find()) {
sourceTagLine = i;
break;
}
}
if (sourceTagLine < 0) {
sourceTagLine = commentEndLine;
}
completedSearch = true;
} else {
/**
* Not a comment line or the class header. Check if it is
* a non-emptyLine
*/
if (text.trim().length() > 0) {
nonEmptyLines = true;
}
}
}
}
}
reader.close();
/*
* Close the input file and call the writing method
* that will insert the source tag
*/
if (completedSearch) {
return writeFile(file, buffer, sourceTagLine, sourceTagText);
}
return false;
}
/**
* Writes the file with a newly generated source tag in the class header
* javadocs
*
* @param file the file to write
* @param buffer file contents
* @param sourceTagLine line number for the new source tag
* @param sourceTag text for the new source tag
*
* @return always returns true
*
* @throws IOException
*/
private boolean writeFile(File file, List<String> buffer, int sourceTagLine, String sourceTag)
throws IOException {
FileWriter writer = new FileWriter(file);
for (int i = 0; i < buffer.size(); i++) {
if (i == sourceTagLine) {
writer.write(" *" + lineSeparator);
writer.write(sourceTag);
writer.write(lineSeparator);
}
writer.write(buffer.get(i));
writer.write(lineSeparator);
}
writer.close();
return true;
}
}