View Javadoc

1   /*
2       StatCvs - CVS statistics generation 
3       Copyright (C) 2002  Lukasz Pekacki <lukasz@pekacki.de>
4       http://statcvs.sf.net/
5       
6       This library is free software; you can redistribute it and/or
7       modify it under the terms of the GNU Lesser General Public
8       License as published by the Free Software Foundation; either
9       version 2.1 of the License, or (at your option) any later version.
10  
11      This library is distributed in the hope that it will be useful,
12      but WITHOUT ANY WARRANTY; without even the implied warranty of
13      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14      Lesser General Public License for more details.
15  
16      You should have received a copy of the GNU Lesser General Public
17      License along with this library; if not, write to the Free Software
18      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19      
20  	$RCSfile: CvsRevisionParser.java,v $ 
21  	Created on $Date: 2008/04/02 11:22:15 $ 
22  */
23  
24  package net.sf.statcvs.input;
25  
26  import java.io.IOException;
27  import java.text.ParseException;
28  import java.text.SimpleDateFormat;
29  import java.util.Date;
30  import java.util.Locale;
31  import java.util.StringTokenizer;
32  import java.util.logging.Logger;
33  
34  import net.sf.statcvs.util.LookaheadReader;
35  
36  /**
37   * Parses all revisions of one file.
38   * 
39   * @author Anja Jentzsch
40   * @author Richard Cyganiak
41   * @version $Id: CvsRevisionParser.java,v 1.41 2008/04/02 11:22:15 benoitx Exp $
42   */
43  public class CvsRevisionParser {
44  
45      private static Logger logger = Logger.getLogger(CvsRevisionParser.class.getName());
46  
47      /**
48       * Revision Delimiter in CVS log file
49       */
50      public static final String REVISION_DELIMITER = "----------------------------";
51      /**
52       * File Delimiter in CVS log file
53       */
54      public static final String FILE_DELIMITER = "======================================" + "=======================================";
55  
56      private static final String OLD_LOG_TIMESTAMP_FORMAT = "yyyy/MM/dd HH:mm:ss zzz";
57      private static final String NEW_LOG_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss Z";
58      private static final Locale LOG_TIMESTAMP_LOCALE = Locale.US;
59      private static SimpleDateFormat oldLogTimeFormat = new SimpleDateFormat(OLD_LOG_TIMESTAMP_FORMAT, LOG_TIMESTAMP_LOCALE);
60      private static SimpleDateFormat newLogTimeFormat = new SimpleDateFormat(NEW_LOG_TIMESTAMP_FORMAT, LOG_TIMESTAMP_LOCALE);
61      private final LookaheadReader logReader;
62      private final CvsLogBuilder builder;
63      private boolean fileDone = false;
64      private RevisionData revision;
65  
66      /**
67       * Default Constructor CvsRevisionParser.
68       * @param logReader the reader
69       * @param builder a <tt>Builder</tt> for the creation process
70       */
71      public CvsRevisionParser(final LookaheadReader logReader, final CvsLogBuilder builder) {
72          this.logReader = logReader;
73          this.builder = builder;
74      }
75  
76      /**
77       * Parses the list of revisions for one file
78       * @throws LogSyntaxException on syntax error in the log
79       * @throws IOException on read error
80       */
81      public void parse() throws LogSyntaxException, IOException {
82          this.logReader.nextLine();
83          do {
84              revision = new RevisionData();
85              parseRevision();
86              builder.buildRevision(revision);
87          } while (!fileDone);
88      }
89  
90      private void parseRevision() throws IOException, LogSyntaxException {
91          if (!isNewRevisionLine(logReader.getCurrentLine())) {
92              throw new LogSyntaxException("expected 'revision' but found '" + logReader.getCurrentLine() + "' in line " + logReader.getLineNumber());
93          }
94          final String revNo = logReader.getCurrentLine().substring("revision ".length());
95          revision.setRevisionNumber(revNo);
96          parseDateLine(this.logReader.nextLine());
97          if (this.logReader.nextLine().startsWith("branches:")) {
98              this.logReader.nextLine();
99          }
100         final StringBuffer comment = new StringBuffer();
101         while (true) {
102             final String line = logReader.getCurrentLine();
103             if (REVISION_DELIMITER.equals(line)) {
104                 final String next = this.logReader.nextLine();
105                 if (isNewRevisionLine(next)) {
106                     revision.setComment(comment.toString());
107                     return;
108                 }
109             } else if (FILE_DELIMITER.equals(line)) {
110                 if (!this.logReader.hasNextLine() || "".equals(this.logReader.nextLine())) {
111                     this.revision.setComment(comment.toString());
112                     this.fileDone = true;
113                     return;
114                 }
115             } else {
116                 this.logReader.nextLine();
117             }
118             if (comment.length() != 0) {
119                 comment.append('\n');
120             }
121             comment.append(line);
122         }
123     }
124 
125     private void parseDateLine(final String line) throws LogSyntaxException {
126 
127         // date: 2000/06/19 04:56:21;  author: somebody;  state: Exp;  lines: +114 -45
128 
129         // get the creation date
130         final int endOfDateIndex = line.indexOf(';', 6);
131         final String dateString = line.substring(6, endOfDateIndex) + " GMT";
132         final Date date = convertFromLogTime(dateString);
133         if (date == null) {
134             throw new LogSyntaxException("unexpected date format in line " + logReader.getLineNumber());
135         }
136         revision.setDate(date);
137 
138         // get the author name
139         final int endOfAuthorIndex = line.indexOf(';', endOfDateIndex + 1);
140         revision.setLoginName(line.substring(endOfDateIndex + 11, endOfAuthorIndex));
141 
142         // get the file state (because this revision might be "dead")
143         final String fileState = line.substring(endOfAuthorIndex + 10, line.indexOf(';', endOfAuthorIndex + 1));
144         if (isDeadState(fileState)) {
145             revision.setStateDead();
146             return;
147         }
148         revision.setStateExp();
149 
150         // is this an initial revision?
151         final int beginOfLinesIndex = line.indexOf("lines:", endOfAuthorIndex + 1);
152         if (beginOfLinesIndex < 0) {
153             return;
154         }
155 
156         // get lines added and lines removed
157         final StringTokenizer st = new StringTokenizer(line.substring(beginOfLinesIndex + 8));
158         final int linesAdded = Integer.parseInt(st.nextToken());
159         String removed = st.nextToken();
160         if (removed.indexOf(';') >= 0) {
161             removed = removed.substring(0, removed.indexOf(';'));
162         }
163         final int linesRemoved = -Integer.parseInt(removed);
164         revision.setLines(linesAdded, linesRemoved);
165     }
166 
167     private boolean isNewRevisionLine(final String line) {
168         return line.startsWith("revision ");
169     }
170 
171     private boolean isDeadState(final String state) {
172         if ("dead".equals(state)) {
173             return true;
174         }
175         if ("Exp".equals(state) || "Stab".equals(state) || "Rel".equals(state)) {
176             return false;
177         }
178         logger.warning("unknown file state '" + state + "' at line " + this.logReader.getLineNumber());
179         return false;
180     }
181 
182     /**
183      * Returns a date from a given modTime String of a cvs logfile
184      * @param modTime modTime String of a cvs logfile
185      * @return Date date from a given modTime String of a cvs logfile
186      */
187     private static Date convertFromLogTime(final String modTime) {
188         try {
189             return oldLogTimeFormat.parse(modTime);
190         } catch (final ParseException e) {
191             // try other format
192         }
193         try {
194             return newLogTimeFormat.parse(modTime);
195         } catch (final ParseException e) {
196             return null;
197         }
198     }
199 }