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
38
39
40
41
42
43 public class CvsRevisionParser {
44
45 private static Logger logger = Logger.getLogger(CvsRevisionParser.class.getName());
46
47
48
49
50 public static final String REVISION_DELIMITER = "----------------------------";
51
52
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
68
69
70
71 public CvsRevisionParser(final LookaheadReader logReader, final CvsLogBuilder builder) {
72 this.logReader = logReader;
73 this.builder = builder;
74 }
75
76
77
78
79
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
184
185
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 }