001package 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 022import org.apache.maven.scm.ChangeFile; 023import org.apache.maven.scm.ChangeSet; 024import org.apache.maven.scm.log.ScmLogger; 025import org.apache.maven.scm.util.AbstractConsumer; 026 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Comparator; 030import java.util.Iterator; 031import java.util.List; 032import 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 */ 040public 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}