001 package org.apache.maven.scm.provider.cvslib.command.changelog;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import org.apache.maven.scm.ChangeFile;
023 import org.apache.maven.scm.ChangeSet;
024 import org.apache.maven.scm.log.ScmLogger;
025 import org.apache.maven.scm.util.AbstractConsumer;
026
027 import java.util.ArrayList;
028 import java.util.Collections;
029 import java.util.Comparator;
030 import java.util.Iterator;
031 import java.util.List;
032 import java.util.StringTokenizer;
033
034 /**
035 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse </a>
036 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a>
037 * @author Olivier Lamy
038 *
039 */
040 public class CvsChangeLogConsumer
041 extends AbstractConsumer
042 {
043 private List<ChangeSet> entries = new ArrayList<ChangeSet>();
044
045 // state machine constants for reading cvs output
046
047 /**
048 * expecting file information
049 */
050 private static final int GET_FILE = 1;
051
052 /**
053 * expecting date
054 */
055 private static final int GET_DATE = 2;
056
057 /**
058 * expecting comments
059 */
060 private static final int GET_COMMENT = 3;
061
062 /**
063 * expecting revision
064 */
065 private static final int GET_REVISION = 4;
066
067 /**
068 * Marks start of file data
069 */
070 private static final String START_FILE = "Working file: ";
071
072 /**
073 * Marks end of file
074 */
075 private static final String END_FILE =
076 "===================================" + "==========================================";
077
078 /**
079 * Marks start of revision
080 */
081 private static final String START_REVISION = "----------------------------";
082
083 /**
084 * Marks revision data
085 */
086 private static final String REVISION_TAG = "revision ";
087
088 /**
089 * Marks date data
090 */
091 private static final String DATE_TAG = "date: ";
092
093 /**
094 * current status of the parser
095 */
096 private int status = GET_FILE;
097
098 /**
099 * the current log entry being processed by the parser
100 */
101 private ChangeSet currentChange = null;
102
103 /**
104 * the current file being processed by the parser
105 */
106 private ChangeFile currentFile = null;
107
108 private String userDatePattern;
109
110 public CvsChangeLogConsumer( ScmLogger logger, String userDatePattern )
111 {
112 super( logger );
113
114 this.userDatePattern = userDatePattern;
115 }
116
117 public List<ChangeSet> getModifications()
118 {
119 Collections.sort( entries, new Comparator<ChangeSet>()
120 {
121 public int compare( ChangeSet set1, ChangeSet set2 )
122 {
123 return set1.getDate().compareTo( set2.getDate() );
124 }
125 } );
126 List<ChangeSet> fixedModifications = new ArrayList<ChangeSet>();
127 ChangeSet currentEntry = null;
128 for ( Iterator<ChangeSet> entryIterator = entries.iterator(); entryIterator.hasNext(); )
129 {
130 ChangeSet entry = (ChangeSet) entryIterator.next();
131 if ( currentEntry == null )
132 {
133 currentEntry = entry;
134 }
135 else if ( areEqual( currentEntry, entry ) )
136 {
137 currentEntry.addFile( (ChangeFile) entry.getFiles().get( 0 ) );
138 }
139 else
140 {
141 fixedModifications.add( currentEntry );
142 currentEntry = entry;
143 }
144 }
145 if ( currentEntry != null )
146 {
147 fixedModifications.add( currentEntry );
148 }
149 return fixedModifications;
150 }
151
152 private boolean areEqual( ChangeSet set1, ChangeSet set2 )
153 {
154 if ( set1.getAuthor().equals( set2.getAuthor() ) && set1.getComment().equals( set2.getComment() )
155 && set1.getDate().equals( set2.getDate() ) )
156 {
157 return true;
158 }
159 return false;
160 }
161
162 /** {@inheritDoc} */
163 public void consumeLine( String line )
164 {
165 if ( getLogger().isDebugEnabled() )
166 {
167 getLogger().debug( line );
168 }
169 try
170 {
171 switch ( getStatus() )
172 {
173 case GET_FILE:
174 processGetFile( line );
175 break;
176 case GET_REVISION:
177 processGetRevision( line );
178 break;
179 case GET_DATE:
180 processGetDate( line );
181 break;
182 case GET_COMMENT:
183 processGetComment( line );
184 break;
185 default:
186 throw new IllegalStateException( "Unknown state: " + status );
187 }
188 }
189 catch ( Throwable ex )
190 {
191 if ( getLogger().isWarnEnabled() )
192 {
193 getLogger().warn( "Exception in the cvs changelog consumer.", ex );
194 }
195 }
196 }
197
198 /**
199 * Add a change log entry to the list (if it's not already there) with the
200 * given file.
201 *
202 * @param entry a {@link ChangeSet}to be added to the list if another
203 * with the same key doesn't exist already. If the entry's author
204 * is null, the entry wont be added
205 * @param file a {@link ChangeFile}to be added to the entry
206 */
207 private void addEntry( ChangeSet entry, ChangeFile file )
208 {
209 // do not add if entry is not populated
210 if ( entry.getAuthor() == null )
211 {
212 return;
213 }
214
215 entry.addFile( file );
216
217 entries.add( entry );
218 }
219
220 /**
221 * Process the current input line in the Get File state.
222 *
223 * @param line a line of text from the cvs log output
224 */
225 private void processGetFile( String line )
226 {
227 if ( line.startsWith( START_FILE ) )
228 {
229 setCurrentChange( new ChangeSet() );
230 setCurrentFile( new ChangeFile( line.substring( START_FILE.length(), line.length() ) ) );
231 setStatus( GET_REVISION );
232 }
233 }
234
235 /**
236 * Process the current input line in the Get Revision state.
237 *
238 * @param line a line of text from the cvs log output
239 */
240 private void processGetRevision( String line )
241 {
242 if ( line.startsWith( REVISION_TAG ) )
243 {
244 getCurrentFile().setRevision( line.substring( REVISION_TAG.length() ) );
245 setStatus( GET_DATE );
246 }
247 else if ( line.startsWith( END_FILE ) )
248 {
249 // If we encounter an end of file line, it means there
250 // are no more revisions for the current file.
251 // there could also be a file still being processed.
252 setStatus( GET_FILE );
253 addEntry( getCurrentChange(), getCurrentFile() );
254 }
255 }
256
257 /**
258 * Process the current input line in the Get Date state.
259 *
260 * @param line a line of text from the cvs log output
261 */
262 private void processGetDate( String line )
263 {
264 if ( line.startsWith( DATE_TAG ) )
265 {
266 StringTokenizer tokenizer = new StringTokenizer( line, ";" );
267 // date: YYYY/mm/dd HH:mm:ss [Z]; author: name;...
268
269 String datePart = tokenizer.nextToken().trim();
270 String dateTime = datePart.substring( "date: ".length() );
271 StringTokenizer dateTokenizer = new StringTokenizer( dateTime, " " );
272 if ( dateTokenizer.countTokens() == 2 )
273 {
274 dateTime += " UTC";
275 }
276 getCurrentChange().setDate( dateTime, userDatePattern );
277
278 String authorPart = tokenizer.nextToken().trim();
279 String author = authorPart.substring( "author: ".length() );
280 getCurrentChange().setAuthor( author );
281 setStatus( GET_COMMENT );
282 }
283 }
284
285 /**
286 * Process the current input line in the Get Comment state.
287 *
288 * @param line a line of text from the cvs log output
289 */
290 private void processGetComment( String line )
291 {
292 if ( line.startsWith( START_REVISION ) )
293 {
294 // add entry, and set state to get revision
295 addEntry( getCurrentChange(), getCurrentFile() );
296 // new change log entry
297 setCurrentChange( new ChangeSet() );
298 // same file name, but different rev
299 setCurrentFile( new ChangeFile( getCurrentFile().getName() ) );
300 setStatus( GET_REVISION );
301 }
302 else if ( line.startsWith( END_FILE ) )
303 {
304 addEntry( getCurrentChange(), getCurrentFile() );
305 setStatus( GET_FILE );
306 }
307 else
308 {
309 // keep gathering comments
310 getCurrentChange().setComment( getCurrentChange().getComment() + line + "\n" );
311 }
312 }
313
314 /**
315 * Getter for property currentFile.
316 *
317 * @return Value of property currentFile.
318 */
319 private ChangeFile getCurrentFile()
320 {
321 return currentFile;
322 }
323
324 /**
325 * Setter for property currentFile.
326 *
327 * @param currentFile New value of property currentFile.
328 */
329 private void setCurrentFile( ChangeFile currentFile )
330 {
331 this.currentFile = currentFile;
332 }
333
334 /**
335 * Getter for property currentChange.
336 *
337 * @return Value of property currentChange.
338 */
339 private ChangeSet getCurrentChange()
340 {
341 return currentChange;
342 }
343
344 /**
345 * Setter for property currentChange.
346 *
347 * @param currentChange New value of property currentChange.
348 */
349 private void setCurrentChange( ChangeSet currentChange )
350 {
351 this.currentChange = currentChange;
352 }
353
354 /**
355 * Getter for property status.
356 *
357 * @return Value of property status.
358 */
359 private int getStatus()
360 {
361 return status;
362 }
363
364 /**
365 * Setter for property status.
366 *
367 * @param status New value of property status.
368 */
369 private void setStatus( int status )
370 {
371 this.status = status;
372 }
373 }