1 /*
2 Bloof - visualize the evolution of your software project
3 Copyright ( C ) 2003 Lukasz Pekacki <lukasz@pekacki.de>
4 http://bloof.sf.net/
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along with
15 this program; if not, write to the Free Software Foundation, Inc.,
16 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18 $RCSfile: LogParser.java,v $
19 Created on $Date: 2003/09/06 08:35:09 $
20 */
21
22 package net.sf.bloof.scm.cvsplugin;
23
24 import net.sf.bloof.Bloof;
25 import net.sf.bloof.scm.FileState;
26
27 import java.io.BufferedReader;
28 import java.io.IOException;
29 import java.io.Reader;
30 import java.text.ParseException;
31 import java.util.Date;
32 import java.util.NoSuchElementException;
33 import java.util.StringTokenizer;
34 import java.util.logging.Logger;
35
36
37
38 /***
39 * Parser for a CVS log file
40 * @author Lukasz Pekacki <pekacki@users.sourceforge.net>
41 * @version $Id: LogParser.java,v 1.12 2003/09/06 08:35:09 pekacki Exp $
42 */
43 public class LogParser implements Runnable {
44
45 /***
46 * Constructs a log parser for the given input
47 * @param aLogReader Reader on the the CVS log file
48 * @param aRiter RevisonIterator that receives parsed Revisions
49 * */
50 public LogParser(Reader aLogReader, RevisionIterator aRiter) {
51 mRiter = aRiter;
52 mLogReader = new BufferedReader(aLogReader);
53 }
54 /***
55 * Gets the name of the file
56 * @param aRcsFile the full RCS name of the file
57 * @return String the name of the file
58 */
59 private String extractFileName(String aRcsFile) {
60 int beginIndex = aRcsFile.lastIndexOf(SLASH) + 1;
61 int endIndex = aRcsFile.lastIndexOf(KOMMA);
62 return aRcsFile.substring(beginIndex, endIndex);
63 }
64 /***
65 * Extracts the path to the file
66 * @param aRcsFile Path string of the RCS File
67 * @return String representing the path
68 */
69 private String extractPath(String aRcsFile) {
70 String resultPath = CvsPlugin.normalizePath(aRcsFile);
71 int endIndex = resultPath.lastIndexOf(KOMMA);
72 return resultPath.substring(0, endIndex);
73 }
74 /***
75 * Finishes parsing and closes the stream
76 * */
77 private void finishParsing() {
78 mParsingFinished = true;
79 mRiter.parserHasFinished();
80 try {
81 sLogger.fine("Closing reader");
82 mLogReader.close();
83 } catch (IOException e) {
84 throw new NoSuchElementException("Parsing log file failed. Could not close stream.");
85 }
86 }
87 private String getComment() throws IOException, LogSyntaxException {
88 StringBuffer comment = new StringBuffer();
89 String line = null;
90 boolean found = false;
91 do {
92 line = readCvsMessageLine();
93 if (line == null) {
94 finishParsing();
95 return comment.toString();
96 }
97
98 line.trim();
99 if (line.startsWith(REV_DELIM)) {
100 found = true;
101 } else if (line.matches(".*?" + FILE_DELIM)) {
102 found = true;
103 mCurrentFileHasMoreRevisionds = false;
104 } else {
105 comment.append(line);
106 }
107 } while (!found);
108 return comment.toString();
109 }
110
111 /***
112 * Returns the value of an line start tag, e.g.
113 * head: 1.2; in this case "1.2" will be returned
114 * */
115 private String getValue(String aLine, String aLineStart)
116 throws IOException, LogSyntaxException {
117
118 if (!aLine.startsWith(aLineStart)) {
119 finishParsing();
120 throw new LogSyntaxException("Expected: " + aLineStart);
121 }
122
123 return aLine.substring(aLineStart.length());
124 }
125
126 private String ignoreLineTill(String aPattern) throws IOException, LogSyntaxException {
127 String line = null;
128 do {
129 line = readCvsMessageLine();
130 if (line == null || mParsingFinished) {
131 finishParsing();
132 return null;
133 }
134
135 line.trim();
136 } while (!line.startsWith(aPattern));
137 if (line == null || mParsingFinished) {
138 finishParsing();
139 throw new LogSyntaxException("Expected pattern:" + aPattern + " not found");
140 }
141 return line;
142 }
143
144 /***
145 * Parses the next file in the log; fills the working revision
146 * with necessary entries
147 * @throws LogSyntaxException
148 * @throws IOException
149 */
150 private void parseNextFileHead() throws LogSyntaxException, IOException {
151 mLookingForNextFile = true;
152 String line = ignoreLineTill(RCS_KEY);
153 if (line == null) {
154 finishParsing();
155 return;
156 }
157 mLookingForNextFile = false;
158 String rcsFile = getValue(line, RCS_KEY);
159 line = ignoreLineTill(KEYSUBS_KEY);
160 String keywordSubs = getValue(line, KEYSUBS_KEY);
161 if (keywordSubs.equals(BINARY_KEYSUB)) {
162 parseNextFileHead();
163 return;
164 }
165 line = ignoreLineTill(REVISON_SELECTION);
166 int selectedRevs = parseSelectedRevisions(line);
167 if (selectedRevs == 0) {
168 parseNextFileHead();
169 return;
170 }
171 ignoreLineTill(REV_DELIM);
172 mCurrentFileHasMoreRevisionds = true;
173 mCurrentFileName = extractFileName(rcsFile);
174 mCurrentPath = extractPath(rcsFile);
175 }
176
177 /***
178 * Parses the next Revision and puts it into the RevisionBuffer.
179 * PRE: A file head is correctly parsed and has more revisions:
180 * mFileHasRevisions == true && mParsingFinished == false
181 * */
182 private Revision parseNextRevision() throws LogSyntaxException, IOException {
183 if (!mCurrentFileHasMoreRevisionds) {
184 parseNextFileHead();
185 }
186 if (mParsingFinished) {
187 return null;
188 }
189 Revision r = new Revision();
190 r.setPath(mCurrentPath);
191 r.setFileName(mCurrentFileName);
192 String line = ignoreLineTill(REVISION_KEY);
193 if (line == null) {
194 finishParsing();
195 throw new LogSyntaxException("Could not find Revision Line");
196 }
197 String revisionName = getValue(line, REVISION_KEY);
198 r.setRevisionName(revisionName);
199 /*
200 * ignore lines till next:
201 * <DATE>; author: <NAME>; state: <STATE>; lines: <ADDED> <REMOVED>
202 * */
203 line = ignoreLineTill(DATE_KEY);
204 int endOfDateIndex = line.indexOf(';', 6);
205 String dateString = line.substring(6, endOfDateIndex) + " GMT";
206 Date creationDate = null;
207 try {
208 creationDate = CvsPlugin.convertFromLogTime(dateString);
209 } catch (ParseException e) {
210 throw new LogSyntaxException("Unexpected date format");
211 }
212 r.setDate(creationDate);
213 // get the author name
214 int endOfAuthorIndex = line.indexOf(';', endOfDateIndex + 1);
215 r.setAuthor(line.substring(endOfDateIndex + 11, endOfAuthorIndex));
216 // get the file state ( because this revision might be "dead" )
217 String fileStateTag =
218 line.substring(endOfAuthorIndex + 10, line.indexOf(';', endOfAuthorIndex + 1));
219 if (fileStateTag.equalsIgnoreCase("dead")) {
220 r.setFileState(FileState.DEAD);
221 } else {
222 r.setFileState(FileState.LIVING);
223 }
224 int beginOfLinesIndex = line.indexOf("lines:", endOfAuthorIndex + 1);
225 if (beginOfLinesIndex >= 0) {
226 StringTokenizer st = new StringTokenizer(line.substring(beginOfLinesIndex + 8));
227 r.setAdded(Integer.parseInt(st.nextToken()));
228 r.setRemoved(-Integer.parseInt(st.nextToken()));
229 } // get revision comment and check if this revision is last revision of this file
230 r.setComment(getComment());
231 return r;
232 }
233 private int parseSelectedRevisions(String aLine) {
234 return Integer.parseInt(
235 aLine.substring(aLine.indexOf(SELECTED_REVS) + SELECTED_REVS.length()).trim());
236 }
237
238
239
240 /***
241 * Reads a line from the cvs stream, cuts status prefixed and
242 * checks for finishing prefix
243 * */
244 private String readCvsMessageLine() throws IOException {
245 String line = mLogReader.readLine();
246 //sLogger.fine("Read:"+line);
247 if (line == null) {
248 finishParsing();
249 } else {
250 line = removePrefixes(line);
251 if (mLookingForNextFile) {
252 if (line.startsWith(OK_MARKER)) {
253 finishParsing();
254 return null;
255 }
256 if (line.startsWith(ERROR_MARKER)) {
257 finishParsing();
258 Bloof.fail("Syntax Error on parsing cvs line:" + line);
259 return null;
260 }
261 return line;
262
263 } else {
264 return line;
265 }
266 }
267
268 return line;
269 }
270
271 private String removePrefixes(String aCvsLogLine) {
272 if (aCvsLogLine.startsWith(MESSAGE_PREFIX)) {
273 String returnLine = aCvsLogLine.substring(MESSAGE_PREFIX.length());
274 return returnLine;
275 }
276 if (aCvsLogLine.startsWith(E_PREFIX)) {
277 String returnLine = aCvsLogLine.substring(E_PREFIX.length());
278 return returnLine;
279 }
280 return aCvsLogLine;
281
282 }
283
284 /***
285 * @see java.lang.Runnable#run( )
286 */
287 public void run() {
288 while (!mParsingFinished) {
289 try {
290 Revision r = parseNextRevision();
291 if (r != null) {
292 mRiter.addRevision(r);
293 }
294 } catch (LogSyntaxException e) {
295 Bloof.fail("Syntax Error on parsing cvs log:" + e.toString());
296 } catch (IOException e) {
297 Bloof.fail("IO Error on parsing cvs log:" + e.toString());
298 mParsingFinished = true;
299 }
300 }
301 mRiter.parserHasFinished();
302 }
303 private static final String BINARY_KEYSUB = "b";
304 private static final String DATE_KEY = "date: ";
305 private static final String E_PREFIX = "E ";
306 /***
307 * File delimiter String
308 * */
309 public static final String FILE_DELIM =
310 "=============================================================================";
311 private static final String KEYSUBS_KEY = "keyword substitution: ";
312 private static final String KOMMA = ",";
313 private static final String MESSAGE_PREFIX = "M ";
314 private static final String OK_MARKER = "ok", ERROR_MARKER = "error";
315 private static final String RCS_KEY = "RCS file: ";
316 /***
317 * Revision delimiter String
318 * */
319 public static final String REV_DELIM = "----------------------------";
320 private static final String REVISION_KEY = "revision ";
321 private static final String REVISON_SELECTION = "total revisions: ";
322 private static final String SELECTED_REVS = "selected revisions:";
323 /***
324 * CVS logfile constants
325 */
326 private static final String SLASH = "/";
327 private static Logger sLogger = Logger.getLogger(LogParser.class.getName());
328 private boolean mCurrentFileHasMoreRevisionds = false;
329 // private ScmAccess mAccess;
330 private BufferedReader mLogReader;
331 private boolean mLookingForNextFile = true;
332 private boolean mParsingFinished = false;
333 /***
334 * Control flow members
335 * */
336 private String mCurrentFileName, mCurrentPath;
337 private RevisionIterator mRiter;
338
339 }
This page was automatically generated by Maven