1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
128
129
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
139 final int endOfAuthorIndex = line.indexOf(';', endOfDateIndex + 1);
140 revision.setLoginName(line.substring(endOfDateIndex + 11, endOfAuthorIndex));
141
142
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
151 final int beginOfLinesIndex = line.indexOf("lines:", endOfAuthorIndex + 1);
152 if (beginOfLinesIndex < 0) {
153 return;
154 }
155
156
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
192 }
193 try {
194 return newLogTimeFormat.parse(modTime);
195 } catch (final ParseException e) {
196 return null;
197 }
198 }
199 }