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.util.HashMap;
28 import java.util.Map;
29 import java.util.logging.Logger;
30
31 import net.sf.statcvs.util.CvsLogUtils;
32 import net.sf.statcvs.util.LookaheadReader;
33
34 /**
35 * Parses the information of one file from a CVS logfile
36 * {@link net.sf.statcvs.util.LookaheadReader}. A {@link Builder} must be
37 * specified which constructs some representation of that file. The lookahead
38 * reader must be positioned on the first line of the file's section in the
39 * log ("RCS file: ...").
40 *
41 * @author Anja Jentzsch
42 * @author Richard Cyganiak
43 * @author Tammo van Lessen
44 * @version $Id: CvsFileBlockParser.java,v 1.47 2008/04/02 11:22:15 benoitx Exp $
45 */
46 public class CvsFileBlockParser {
47 private static Logger logger = Logger.getLogger(CvsFileBlockParser.class.getName());
48 private final LookaheadReader logReader;
49 private final CvsLogBuilder builder;
50 private boolean isLogWithoutSymbolicNames = false;
51 private final boolean isFirstFile;
52 private final Map revBySymNames = new HashMap();
53
54 /**
55 * Default Constructor CvsFileBlockParser.
56 * @param logReader reader
57 * @param builder a <tt>Builder</tt> for the creation process
58 * @param isFirstFile Is this the first file of the log?
59 */
60 public CvsFileBlockParser(final LookaheadReader logReader, final CvsLogBuilder builder, final boolean isFirstFile) {
61 this.logReader = logReader;
62 this.builder = builder;
63 this.isFirstFile = isFirstFile;
64 }
65
66 /**
67 * Parses one file from the input reader.
68 *
69 * @throws LogSyntaxException on syntax error
70 * @throws IOException on read/write error
71 */
72 public void parse() throws LogSyntaxException, IOException {
73 final String rcsFile = parseSingleLine(this.logReader.getCurrentLine(), "RCS file: ");
74 final String workingFile = parseSingleLine(this.logReader.nextLine(), "Working file: ");
75 final boolean isInAttic = CvsLogUtils.isInAttic(rcsFile, workingFile);
76 requireLine(this.logReader.nextLine(), "head:");
77 requireLine(this.logReader.nextLine(), "branch:");
78 requireLine(this.logReader.nextLine(), "locks:");
79 parseLocksAndAccessList();
80 parseSymbolicNames();
81 final String keywordSubst = parseSingleLine(this.logReader.getCurrentLine(), "keyword substitution: ");
82 boolean isBinary = false;
83 try {
84 isBinary = CvsLogUtils.isBinaryKeywordSubst(keywordSubst);
85 } catch (final IllegalArgumentException unknownKeywordSubst) {
86 logger.warning("unknown keyword substitution '" + keywordSubst + "' in line " + this.logReader.getLineNumber());
87 }
88 requireLine(this.logReader.nextLine(), "total revisions:");
89 parseDescription();
90 if (this.isFirstFile) {
91 this.builder.buildModule(CvsLogUtils.getModuleName(rcsFile, workingFile));
92 }
93 this.builder.buildFile(workingFile, isBinary, isInAttic, this.revBySymNames);
94 if (!CvsRevisionParser.FILE_DELIMITER.equals(this.logReader.getCurrentLine())) {
95 new CvsRevisionParser(this.logReader, this.builder).parse();
96 }
97 }
98
99 /**
100 * Returns <tt>true</tt> if the log was generated
101 * with the "-N" switch of "cvs log"
102 *
103 * @return Returns <tt>true</tt> if the log was generated
104 * with the "-N" switch of "cvs log"
105 */
106 public boolean isLogWithoutSymbolicNames() {
107 return this.isLogWithoutSymbolicNames;
108 }
109
110 private String parseSingleLine(final String line, final String lineStart) throws LogSyntaxException {
111
112 if (!line.startsWith(lineStart)) {
113 throw new LogSyntaxException("line " + this.logReader.getLineNumber() + ": expected '" + lineStart + "' but found '" + line + "'");
114 }
115
116 return line.substring(lineStart.length());
117 }
118
119 private void requireLine(final String line, final String lineStart) throws LogSyntaxException {
120
121 parseSingleLine(line, lineStart);
122 }
123
124 private void parseSymbolicNames() throws IOException {
125 if (this.logReader.getCurrentLine().startsWith("keyword substitution: ")) {
126 return;
127 }
128 String line;
129 if (this.logReader.getCurrentLine().equals("symbolic names:")) {
130 line = this.logReader.nextLine();
131 } else {
132 this.isLogWithoutSymbolicNames = true;
133 line = this.logReader.getCurrentLine();
134 }
135 while (line != null && !line.startsWith("keyword substitution: ")) {
136 final int firstColon = line.indexOf(':');
137 final String tagName = line.substring(1, firstColon);
138 final String tagRevision = line.substring(firstColon + 2);
139 this.revBySymNames.put(tagName, tagRevision);
140 line = this.logReader.nextLine();
141 }
142 }
143
144 private void parseLocksAndAccessList() throws IOException {
145 while (!"access list:".equals(this.logReader.nextLine())) {
146
147 }
148 String line;
149 do {
150 line = this.logReader.nextLine();
151
152 } while (!line.equals("symbolic names:") && !line.startsWith("keyword substitution: "));
153 }
154
155 private void parseDescription() throws LogSyntaxException, IOException {
156 final String line = this.logReader.nextLine();
157 if (line.equals(CvsRevisionParser.FILE_DELIMITER)) {
158 throw new LogSyntaxException("line " + this.logReader.getLineNumber() + ": missing description; please don't use the -h switch of 'cvs log'!");
159 }
160 requireLine(this.logReader.getCurrentLine(), "description:");
161 while (!isDescriptionDelimiter(this.logReader.nextLine())) {
162
163 }
164 }
165
166 private boolean isDescriptionDelimiter(final String line) {
167 return CvsRevisionParser.REVISION_DELIMITER.equals(line) || CvsRevisionParser.FILE_DELIMITER.equals(line);
168 }
169 }