Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
PerforceDescribeConsumer |
|
| 3.375;3,375 |
1 | package org.apache.maven.scm.provider.perforce.command.changelog; | |
2 | ||
3 | /* | |
4 | * Licensed to the Apache Software Foundation (ASF) under one | |
5 | * or more contributor license agreements. See the NOTICE file | |
6 | * distributed with this work for additional information | |
7 | * regarding copyright ownership. The ASF licenses this file | |
8 | * to you under the Apache License, Version 2.0 (the | |
9 | * "License"); you may not use this file except in compliance | |
10 | * with the License. You may obtain a copy of the License at | |
11 | * | |
12 | * http://www.apache.org/licenses/LICENSE-2.0 | |
13 | * | |
14 | * Unless required by applicable law or agreed to in writing, | |
15 | * software distributed under the License is distributed on an | |
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
17 | * KIND, either express or implied. See the License for the | |
18 | * specific language governing permissions and limitations | |
19 | * under the License. | |
20 | */ | |
21 | ||
22 | import java.util.ArrayList; | |
23 | import java.util.List; | |
24 | ||
25 | import org.apache.maven.scm.ChangeFile; | |
26 | import org.apache.maven.scm.ChangeSet; | |
27 | import org.apache.maven.scm.ScmException; | |
28 | import org.apache.maven.scm.log.ScmLogger; | |
29 | import org.apache.maven.scm.util.AbstractConsumer; | |
30 | import org.apache.regexp.RE; | |
31 | import org.apache.regexp.RESyntaxException; | |
32 | ||
33 | /** | |
34 | * Parse the tagged output from "p4 describe -s [change] [change] [...]". | |
35 | * | |
36 | * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a> | |
37 | * @author Olivier Lamy | |
38 | * @version $Id: PerforceDescribeConsumer.java 1054408 2011-01-02 14:05:25Z olamy $ | |
39 | */ | |
40 | public class PerforceDescribeConsumer | |
41 | extends AbstractConsumer | |
42 | { | |
43 | ||
44 | 1 | private List<ChangeSet> entries = new ArrayList<ChangeSet>(); |
45 | ||
46 | /** | |
47 | * State machine constant: expecting revision | |
48 | */ | |
49 | private static final int GET_REVISION = 1; | |
50 | ||
51 | /** | |
52 | * State machine constant: eat the first blank line | |
53 | */ | |
54 | private static final int GET_COMMENT_BEGIN = 2; | |
55 | ||
56 | /** | |
57 | * State machine constant: expecting comments | |
58 | */ | |
59 | private static final int GET_COMMENT = 3; | |
60 | ||
61 | /** | |
62 | * State machine constant: expecting "Affected files" | |
63 | */ | |
64 | private static final int GET_AFFECTED_FILES = 4; | |
65 | ||
66 | /** | |
67 | * State machine constant: expecting blank line | |
68 | */ | |
69 | private static final int GET_FILES_BEGIN = 5; | |
70 | ||
71 | /** | |
72 | * State machine constant: expecting files | |
73 | */ | |
74 | private static final int GET_FILE = 6; | |
75 | ||
76 | /** | |
77 | * Current status of the parser | |
78 | */ | |
79 | 1 | private int status = GET_REVISION; |
80 | ||
81 | /** | |
82 | * The current log entry being processed by the parser | |
83 | */ | |
84 | @SuppressWarnings( "unused" ) | |
85 | private String currentRevision; | |
86 | ||
87 | /** | |
88 | * The current log entry being processed by the parser | |
89 | */ | |
90 | private ChangeSet currentChange; | |
91 | ||
92 | /** | |
93 | * the current file being processed by the parser | |
94 | */ | |
95 | private String currentFile; | |
96 | ||
97 | /** | |
98 | * The location of files within the Perforce depot that we are processing | |
99 | * e.g. //depot/projects/foo/bar | |
100 | */ | |
101 | private String repoPath; | |
102 | ||
103 | private String userDatePattern; | |
104 | ||
105 | private static final String REVISION_PATTERN = "^Change (\\d+) " + // changelist number | |
106 | "by (.*)@[^ ]+ " + // author | |
107 | "on (.*)"; // date | |
108 | /** | |
109 | * The comment section ends with a blank line | |
110 | */ | |
111 | private static final String COMMENT_DELIMITER = ""; | |
112 | /** | |
113 | * The changelist ends with a blank line | |
114 | */ | |
115 | private static final String CHANGELIST_DELIMITER = ""; | |
116 | ||
117 | private static final String FILE_PATTERN = "^\\.\\.\\. (.*)#(\\d+) "; | |
118 | ||
119 | /** | |
120 | * The regular expression used to match header lines | |
121 | */ | |
122 | private RE revisionRegexp; | |
123 | ||
124 | /** | |
125 | * The regular expression used to match file paths | |
126 | */ | |
127 | private RE fileRegexp; | |
128 | ||
129 | public PerforceDescribeConsumer( String repoPath, String userDatePattern, ScmLogger logger ) | |
130 | { | |
131 | 1 | super( logger ); |
132 | ||
133 | 1 | this.repoPath = repoPath; |
134 | 1 | this.userDatePattern = userDatePattern; |
135 | ||
136 | try | |
137 | { | |
138 | 1 | revisionRegexp = new RE( REVISION_PATTERN ); |
139 | 1 | fileRegexp = new RE( FILE_PATTERN ); |
140 | } | |
141 | 0 | catch ( RESyntaxException ignored ) |
142 | { | |
143 | 0 | if ( getLogger().isErrorEnabled() ) |
144 | { | |
145 | 0 | getLogger().error( "Could not create regexps to parse Perforce descriptions", ignored ); |
146 | } | |
147 | 1 | } |
148 | 1 | } |
149 | ||
150 | // ---------------------------------------------------------------------- | |
151 | // | |
152 | // ---------------------------------------------------------------------- | |
153 | ||
154 | public List<ChangeSet> getModifications() throws ScmException | |
155 | { | |
156 | 1 | return entries; |
157 | } | |
158 | ||
159 | // ---------------------------------------------------------------------- | |
160 | // StreamConsumer Implementation | |
161 | // ---------------------------------------------------------------------- | |
162 | ||
163 | /** {@inheritDoc} */ | |
164 | public void consumeLine( String line ) | |
165 | { | |
166 | 69 | switch ( status ) |
167 | { | |
168 | case GET_REVISION: | |
169 | 7 | processGetRevision( line ); |
170 | 7 | break; |
171 | case GET_COMMENT_BEGIN: | |
172 | 7 | status = GET_COMMENT; |
173 | 7 | break; |
174 | case GET_COMMENT: | |
175 | 18 | processGetComment( line ); |
176 | 18 | break; |
177 | case GET_AFFECTED_FILES: | |
178 | 13 | processGetAffectedFiles( line ); |
179 | 13 | break; |
180 | case GET_FILES_BEGIN: | |
181 | 7 | status = GET_FILE; |
182 | 7 | break; |
183 | case GET_FILE: | |
184 | 17 | processGetFile( line ); |
185 | 17 | break; |
186 | default: | |
187 | 0 | throw new IllegalStateException( "Unknown state: " + status ); |
188 | } | |
189 | 69 | } |
190 | ||
191 | // ---------------------------------------------------------------------- | |
192 | // | |
193 | // ---------------------------------------------------------------------- | |
194 | ||
195 | /** | |
196 | * Add a change log entry to the list (if it's not already there) | |
197 | * with the given file. | |
198 | * | |
199 | * @param entry a {@link ChangeSet} to be added to the list if another | |
200 | * with the same key (p4 change number) doesn't exist already. | |
201 | * @param file a {@link ChangeFile} to be added to the entry | |
202 | */ | |
203 | private void addEntry( ChangeSet entry, ChangeFile file ) | |
204 | { | |
205 | 9 | entry.addFile( file ); |
206 | 9 | } |
207 | ||
208 | /** | |
209 | * Each file matches the fileRegexp. | |
210 | * | |
211 | * @param line A line of text from the Perforce log output | |
212 | */ | |
213 | private void processGetFile( String line ) | |
214 | { | |
215 | 17 | if ( line.equals( CHANGELIST_DELIMITER ) ) { |
216 | 7 | entries.add( 0, currentChange ); |
217 | 7 | status = GET_REVISION; |
218 | 7 | return; |
219 | } | |
220 | 10 | if ( !fileRegexp.match( line ) ) |
221 | { | |
222 | 0 | return; |
223 | } | |
224 | ||
225 | 10 | currentFile = fileRegexp.getParen( 1 ); |
226 | ||
227 | // Although Perforce allows files to be submitted anywhere in the | |
228 | // repository in a single changelist, we're only concerned about the | |
229 | // local files. | |
230 | 10 | if( currentFile.startsWith( repoPath ) ) { |
231 | 9 | currentFile = currentFile.substring( repoPath.length() + 1 ); |
232 | 9 | addEntry( currentChange, new ChangeFile( currentFile, fileRegexp.getParen( 2 ) ) ); |
233 | } | |
234 | 10 | } |
235 | ||
236 | /** | |
237 | * Most of the relevant info is on the revision line matching the | |
238 | * 'pattern' string. | |
239 | * | |
240 | * @param line A line of text from the perforce log output | |
241 | */ | |
242 | private void processGetRevision( String line ) | |
243 | { | |
244 | 7 | if ( !revisionRegexp.match( line ) ) |
245 | { | |
246 | 0 | return; |
247 | } | |
248 | 7 | currentChange = new ChangeSet(); |
249 | 7 | currentRevision = revisionRegexp.getParen( 1 ); |
250 | 7 | currentChange.setAuthor( revisionRegexp.getParen( 2 ) ); |
251 | 7 | currentChange.setDate( revisionRegexp.getParen( 3 ), userDatePattern ); |
252 | ||
253 | 7 | status = GET_COMMENT_BEGIN; |
254 | 7 | } |
255 | ||
256 | /** | |
257 | * Process the current input line in the GET_COMMENT state. This | |
258 | * state gathers all of the comments that are part of a log entry. | |
259 | * | |
260 | * @param line a line of text from the perforce log output | |
261 | */ | |
262 | private void processGetComment( String line ) | |
263 | { | |
264 | 18 | if ( line.equals( COMMENT_DELIMITER ) ) |
265 | { | |
266 | 7 | status = GET_AFFECTED_FILES; |
267 | } | |
268 | else | |
269 | { | |
270 | // remove prepended tab | |
271 | 11 | currentChange.setComment( currentChange.getComment() + line.substring(1) + "\n" ); |
272 | } | |
273 | 18 | } |
274 | ||
275 | /** | |
276 | * Process the current input line in the GET_COMMENT state. This | |
277 | * state gathers all of the comments that are part of a log entry. | |
278 | * | |
279 | * @param line a line of text from the perforce log output | |
280 | */ | |
281 | private void processGetAffectedFiles( String line ) | |
282 | { | |
283 | 13 | if ( !line.equals( "Affected files ..." ) ) |
284 | { | |
285 | 6 | return; |
286 | } | |
287 | 7 | status = GET_FILES_BEGIN; |
288 | 7 | } |
289 | } |