001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library 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 GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.maven.tools;
017:
018: // J2SE dependencies
019: import java.io.File;
020: import java.io.FileFilter;
021: import java.io.FileReader;
022: import java.io.FileWriter;
023: import java.io.IOException;
024: import java.io.LineNumberReader;
025: import java.io.Writer;
026:
027: /**
028: * Add a javadoc tag at the end of class comment in a set of source files.
029: * The default implementation add the {@code @source URL} tag. The algorithm
030: * used in this class has limited capabilities and expects comments formatted
031: * in a "classic" fashion:
032: * <p>
033: * <ul>
034: * <li>Javadoc comments just before the first "{@code class}" keyword found in the source file.</li>
035: * <li>"slash-star" style comment at the begining of the first comment line.</li>
036: * <li>"star-slash" style comment at the end of the last comment line.</li>
037: * </ul>
038: *
039: * @version $Id: CommentUpdater.java 20606 2006-07-18 14:31:51Z jgarnett $
040: * @author Martin Desruisseaux
041: */
042: public class CommentUpdater implements FileFilter {
043: /**
044: * The sequence of characters starting a comment.
045: * Must be first on a line.
046: */
047: private static final String START_COMMENT = "/**";
048:
049: /**
050: * The sequence of characters ending a comment.
051: * Must be last on a line.
052: */
053: private static final String END_COMMENT = "*/";
054:
055: /**
056: * The javadoc tag to add.
057: */
058: private final String tag = "@source";
059:
060: /**
061: * The value to add after the javadoc to add.
062: */
063: private final String value = "\u0024URL\u0024";
064:
065: /**
066: * If this {@code nearTag} is found, then {@link #tag} will be inserted the line
067: * before or after.
068: */
069: private final String nearTag = "@version";
070:
071: /**
072: * If {@code true}, then {@link #tag} will be inserted before {@link #nearTag}.
073: * If {@code true}, then {@link #tag} will be inserted after {@link #nearTag}.
074: */
075: private final boolean insertBeforeNearTag = true;
076:
077: /**
078: * Creates an updater for the default javadoc tag.
079: */
080: public CommentUpdater() {
081: }
082:
083: /**
084: * Process all files specified on the command line. If the specified files are directories,
085: * all {@code .java} files found in those directories and sub-directories will be processed.
086: *
087: * @param args List of files or directories to process.
088: * @throws IOException if an I/O operation failed.
089: */
090: public static void main(final String[] args) throws IOException {
091: final CommentUpdater processor = new CommentUpdater();
092: for (int i = 0; i < args.length; i++) {
093: final int count = processor.processAll(new File(args[i]));
094: System.out.print(count);
095: System.out.println(" modified files.");
096: }
097: }
098:
099: /**
100: * Tests whether or not the specified abstract pathname should be processed.
101: */
102: public boolean accept(final File pathname) {
103: return pathname.isDirectory()
104: || (pathname.isFile() && pathname.getName().endsWith(
105: ".java"));
106: }
107:
108: /**
109: * If the specified file is a directory, then process all {@code .java} files in this
110: * directory. Otherwise, unconditionnaly process the specified file a regular file.
111: *
112: * @param file The directory to process.
113: * @return The number of file modified.
114: * @throws IOException if an I/O operation failed.
115: */
116: private int processAll(final File file) throws IOException {
117: if (file.isDirectory()) {
118: int count = 0;
119: final File[] files = file.listFiles(this );
120: for (int i = 0; i < files.length; i++) {
121: count += processAll(files[i]);
122: }
123: return count;
124: } else {
125: return process(file) ? 1 : 0;
126: }
127: }
128:
129: /**
130: * Process the specified file.
131: *
132: * @param file The file to process.
133: * @return {@code true} if the file has been modified.
134: * @throws IOException if an I/O operation failed.
135: */
136: protected boolean process(final File file) throws IOException {
137: final LineNumberReader in = new LineNumberReader(
138: new FileReader(file));
139: final StringBuffer buffer = new StringBuffer();
140: final String lineSeparator = System.getProperty(
141: "line.separator", "\n");
142: String message = null;
143: int insertAt = 0;
144: int startComment = 0;
145: int lastComment = 0;
146: int endComment = 0;
147: boolean isComment = false;
148: boolean success = false;
149: String line;
150: scan: while ((line = in.readLine()) != null) {
151: /*
152: * Search for the begining of a comment block. The comments must start at the
153: * begining of the line (except for whitespace), otherwise we stop the process
154: * as a safety.
155: */
156: if (!isComment) {
157: int i = line.indexOf(START_COMMENT);
158: if (i >= 0) {
159: while (--i >= 0) {
160: if (!Character.isWhitespace(line.charAt(i))) {
161: // This simple algorithm doesn't know how to process such file.
162: message = START_COMMENT
163: + " should appears at the begining of the line.";
164: break scan;
165: }
166: }
167: startComment = buffer.length();
168: isComment = true;
169: }
170: }
171: /*
172: * Search for javadoc tag we want to add. If this javadoc tag is found, then
173: * we will left this file unchanged.
174: */
175: buffer.append(line);
176: buffer.append(lineSeparator);
177: final int length = line.length();
178: if (isComment) {
179: int i = line.indexOf(tag);
180: if (i >= 0) {
181: i += tag.length();
182: if (i >= length
183: || !Character.isLetter(line.charAt(i))) {
184: message = tag + " tag already presents.";
185: break scan;
186: }
187: }
188: i = line.indexOf(nearTag);
189: if (i >= 0) {
190: i += nearTag.length();
191: if (i >= length
192: || !Character.isLetter(line.charAt(i))) {
193: insertAt = buffer.length();
194: if (insertBeforeNearTag) {
195: insertAt -= (length + lineSeparator
196: .length());
197: }
198: }
199: }
200: /*
201: * Search for the end of a comment block. The comments must finish at the
202: * end of the line (except for whitespace), otherwise we stop the process
203: * as a safety.
204: */
205: i = line.indexOf(END_COMMENT);
206: if (i >= 0) {
207: i += END_COMMENT.length();
208: while (i < length) {
209: if (!Character.isWhitespace(line.charAt(i++))) {
210: // This simple algorithm doesn't know how to process such file.
211: message = END_COMMENT
212: + " should appears at the end of the line.";
213: break scan;
214: }
215: }
216: endComment = buffer.length();
217: isComment = false;
218: } else {
219: // Position before the line that close the comments.
220: lastComment = buffer.length();
221: }
222: continue scan;
223: }
224: /*
225: * From this point, we know that we are not in a comment block and the previous
226: * comments (if any) didn't had the javadoc tag that we want to add. Search for
227: * the "class" word on the line.
228: */
229: int lower = 0;
230: String word;
231: do {
232: while (lower < length
233: && Character.isWhitespace(line.charAt(lower)))
234: lower++;
235: int upper = lower;
236: while (upper < length
237: && Character.isLetter(line.charAt(upper)))
238: upper++;
239: if (lower == upper) {
240: // The first character found is not a letter.
241: continue scan;
242: }
243: if (upper < length
244: && !Character.isWhitespace(line.charAt(upper))) {
245: // The word is not followed by a space. The class name was expected.
246: continue scan;
247: }
248: word = line.substring(lower, upper);
249: lower = upper; // Will be the lower index for the next iteration.
250: } while (!word.equals("class") && !word.equals("interface"));
251: /*
252: * We have now found the position where to inserts our javadoc tag. Process to the
253: * insertion now, and then just copies all remaining lines without any processing.
254: */
255: if (insertAt <= startComment || insertAt >= endComment) {
256: if (lastComment <= startComment
257: || lastComment >= endComment) {
258: message = "No comments found for this class.";
259: break scan;
260: }
261: insertAt = lastComment;
262: }
263: final String insert = " * " + tag + ' ' + value
264: + lineSeparator;
265: buffer.insert(insertAt, insert);
266: while ((line = in.readLine()) != null) {
267: buffer.append(line);
268: buffer.append(lineSeparator);
269: }
270: success = true;
271: break;
272: }
273: /*
274: * Closes the input stream and log a message, if any.
275: * If a javadoc tag has been added, overwrite the file now.
276: */
277: if (message != null) {
278: System.out.print(file);
279: System.out.print(':');
280: System.out.println(in.getLineNumber());
281: System.out.println(message);
282: System.out.println();
283: }
284: in.close();
285: if (success) {
286: final Writer out = new FileWriter(file);
287: out.write(buffer.toString());
288: out.close();
289: }
290: return success;
291: }
292: }
|