001package org.apache.maven.scm.provider.starteam.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.provider.starteam.command.StarteamCommandLineUtils; 026import org.apache.maven.scm.util.AbstractConsumer; 027 028import java.io.File; 029import java.text.SimpleDateFormat; 030import java.util.ArrayList; 031import java.util.Date; 032import java.util.List; 033import java.util.Locale; 034 035/** 036 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a> 037 * @author Olivier Lamy 038 * 039 */ 040public class StarteamChangeLogConsumer 041 extends AbstractConsumer 042{ 043 private SimpleDateFormat localFormat = new SimpleDateFormat( "", Locale.getDefault() ); 044 045 private List<ChangeSet> entries = new ArrayList<ChangeSet>(); 046 047 private String workingDirectory; 048 049 private String currentDir = ""; 050 051 // state machine constants for reading Starteam output 052 053 /** 054 * expecting file information 055 */ 056 private static final int GET_FILE = 1; 057 058 /** 059 * expecting date 060 */ 061 private static final int GET_AUTHOR = 2; 062 063 /** 064 * expecting comments 065 */ 066 private static final int GET_COMMENT = 3; 067 068 /** 069 * expecting revision 070 */ 071 private static final int GET_REVISION = 4; 072 073 074 /** 075 * Marks current directory data 076 */ 077 private static final String DIR_MARKER = "(working dir: "; 078 079 /** 080 * Marks start of file data 081 */ 082 private static final String START_FILE = "History for: "; 083 084 085 /** 086 * Marks end of file 087 */ 088 private static final String END_FILE = 089 "===================================" + "=========================================="; 090 091 /** 092 * Marks start of revision 093 */ 094 private static final String START_REVISION = "----------------------------"; 095 096 /** 097 * Marks revision data 098 */ 099 private static final String REVISION_TAG = "Branch Revision: "; 100 101 /** 102 * Marks author data 103 */ 104 private static final String AUTHOR_TAG = "Author: "; 105 106 /** 107 * Marks date data 108 */ 109 private static final String DATE_TAG = " Date: "; 110 111 /** 112 * current status of the parser 113 */ 114 private int status = GET_FILE; 115 116 /** 117 * the current log entry being processed by the parser 118 */ 119 private ChangeSet currentChange = null; 120 121 /** 122 * the current file being processed by the parser 123 */ 124 private ChangeFile currentFile = null; 125 126 /** 127 * the before date 128 */ 129 private Date startDate; 130 131 /** 132 * the to date 133 */ 134 private Date endDate; 135 136 private String userDateFormat; 137 138 // ---------------------------------------------------------------------- 139 // 140 // ---------------------------------------------------------------------- 141 142 public StarteamChangeLogConsumer( File workingDirectory, ScmLogger logger, Date startDate, Date endDate, 143 String userDateFormat ) 144 { 145 super( logger ); 146 147 this.workingDirectory = workingDirectory.getPath().replace( '\\', '/' ); 148 149 this.startDate = startDate; 150 151 this.endDate = endDate; 152 153 this.userDateFormat = userDateFormat; 154 155 //work around for all en_US compatible locales, where Starteam 156 // stcmd hist output uses a different format, ugly eh? 157 // makesure to change the test file as well if this ever got fixed 158 159 if ( "M/d/yy h:mm a".equals( localFormat.toLocalizedPattern() ) ) 160 { 161 this.localFormat = new SimpleDateFormat( "M/d/yy h:mm:ss a z" ); 162 } 163 } 164 165 // ---------------------------------------------------------------------- 166 // 167 // ---------------------------------------------------------------------- 168 169 public List<ChangeSet> getModifications() 170 { 171 return entries; 172 } 173 174 // ---------------------------------------------------------------------- 175 // StreamConsumer Implementation 176 // ---------------------------------------------------------------------- 177 178 /** {@inheritDoc} */ 179 public void consumeLine( String line ) 180 { 181 if ( getLogger().isDebugEnabled() ) 182 { 183 getLogger().debug( line ); 184 } 185 186 int pos = 0; 187 188 if ( ( pos = line.indexOf( DIR_MARKER ) ) != -1 ) 189 { 190 processDirectory( line, pos ); 191 return; 192 } 193 194 // current state transitions in the state machine - starts with Get File 195 // Get File -> Get Revision 196 // Get Revision -> Get Date or Get File 197 // Get Date -> Get Comment 198 // Get Comment -> Get Comment or Get Revision 199 switch ( getStatus() ) 200 { 201 case GET_FILE: 202 processGetFile( line ); 203 break; 204 case GET_REVISION: 205 processGetRevision( line ); 206 break; 207 case GET_AUTHOR: 208 processGetAuthor( line ); 209 break; 210 case GET_COMMENT: 211 processGetComment( line ); 212 break; 213 default: 214 throw new IllegalStateException( "Unknown state: " + status ); 215 } 216 } 217 218 // ---------------------------------------------------------------------- 219 // 220 // ---------------------------------------------------------------------- 221 222 /** 223 * Add a change log entry to the list (if it's not already there) 224 * with the given file. 225 * 226 * @param entry a {@link ChangeSet} to be added to the list if another 227 * with the same key doesn't exist already. If the entry's author 228 * is null, the entry wont be added 229 * @param file a {@link ChangeFile} to be added to the entry 230 */ 231 private void addEntry( ChangeSet entry, ChangeFile file ) 232 { 233 // do not add if entry is not populated 234 if ( entry.getAuthor() == null ) 235 { 236 return; 237 } 238 239 // do not add if entry is out of date range 240 if ( startDate != null && entry.getDate().before( startDate ) ) 241 { 242 return; 243 } 244 245 if ( endDate != null && entry.getDate().after( endDate ) ) 246 { 247 return; 248 } 249 250 entry.addFile( file ); 251 252 entries.add( entry ); 253 } 254 255 private void processDirectory( String line, int pos ) 256 { 257 String dirPath = line.substring( pos + DIR_MARKER.length(), line.length() - 1 ).replace( '\\', '/' ); 258 try 259 { 260 this.currentDir = StarteamCommandLineUtils.getRelativeChildDirectory( this.workingDirectory, dirPath ); 261 } 262 catch ( IllegalStateException e ) 263 { 264 String error = "Working and checkout directories are not on the same tree"; 265 266 if ( getLogger().isErrorEnabled() ) 267 { 268 getLogger().error( error ); 269 270 getLogger().error( "Working directory: " + workingDirectory ); 271 272 getLogger().error( "Checked out directory: " + dirPath ); 273 } 274 275 throw new IllegalStateException( error ); 276 } 277 } 278 279 /** 280 * Process the current input line in the Get File state. 281 * 282 * @param line a line of text from the Starteam log output 283 */ 284 private void processGetFile( String line ) 285 { 286 if ( line.startsWith( START_FILE ) ) 287 { 288 setCurrentChange( new ChangeSet() ); 289 290 setCurrentFile( 291 new ChangeFile( this.currentDir + "/" + line.substring( START_FILE.length(), line.length() ) ) ); 292 293 setStatus( GET_REVISION ); 294 } 295 } 296 297 /** 298 * Process the current input line in the Get Revision state. 299 * 300 * @param line a line of text from the Starteam log output 301 */ 302 private void processGetRevision( String line ) 303 { 304 int pos; 305 306 if ( ( pos = line.indexOf( REVISION_TAG ) ) != -1 ) 307 { 308 getCurrentFile().setRevision( line.substring( pos + REVISION_TAG.length() ) ); 309 310 setStatus( GET_AUTHOR ); 311 } 312 else if ( line.startsWith( END_FILE ) ) 313 { 314 // If we encounter an end of file line, it means there 315 // are no more revisions for the current file. 316 // there could also be a file still being processed. 317 setStatus( GET_FILE ); 318 319 addEntry( getCurrentChange(), getCurrentFile() ); 320 } 321 } 322 323 /** 324 * Process the current input line in the Get Author/Date state. 325 * 326 * @param line a line of text from the Starteam log output 327 */ 328 private void processGetAuthor( String line ) 329 { 330 if ( line.startsWith( AUTHOR_TAG ) ) 331 { 332 int posDateTag = line.indexOf( DATE_TAG ); 333 334 String author = line.substring( AUTHOR_TAG.length(), posDateTag ); 335 336 getCurrentChange().setAuthor( author ); 337 338 String date = line.substring( posDateTag + DATE_TAG.length() ); 339 340 Date dateObj = parseDate( date, userDateFormat, localFormat.toPattern() ); 341 342 if ( dateObj != null ) 343 { 344 getCurrentChange().setDate( dateObj ); 345 } 346 else 347 { 348 getCurrentChange().setDate( date, userDateFormat ); 349 } 350 351 setStatus( GET_COMMENT ); 352 } 353 } 354 355 /** 356 * Process the current input line in the Get Comment state. 357 * 358 * @param line a line of text from the Starteam log output 359 */ 360 private void processGetComment( String line ) 361 { 362 if ( line.startsWith( START_REVISION ) ) 363 { 364 // add entry, and set state to get revision 365 addEntry( getCurrentChange(), getCurrentFile() ); 366 367 // new change log entry 368 setCurrentChange( new ChangeSet() ); 369 370 // same file name, but different rev 371 setCurrentFile( new ChangeFile( getCurrentFile().getName() ) ); 372 373 setStatus( GET_REVISION ); 374 } 375 else if ( line.startsWith( END_FILE ) ) 376 { 377 addEntry( getCurrentChange(), getCurrentFile() ); 378 379 setStatus( GET_FILE ); 380 } 381 else 382 { 383 // keep gathering comments 384 getCurrentChange().setComment( getCurrentChange().getComment() + line + "\n" ); 385 } 386 } 387 388 /** 389 * Getter for property currentFile. 390 * 391 * @return Value of property currentFile. 392 */ 393 private ChangeFile getCurrentFile() 394 { 395 return currentFile; 396 } 397 398 /** 399 * Setter for property currentFile. 400 * 401 * @param currentFile New value of property currentFile. 402 */ 403 private void setCurrentFile( ChangeFile currentFile ) 404 { 405 this.currentFile = currentFile; 406 } 407 408 /** 409 * Getter for property currentChange. 410 * 411 * @return Value of property currentChange. 412 */ 413 private ChangeSet getCurrentChange() 414 { 415 return currentChange; 416 } 417 418 /** 419 * Setter for property currentChange. 420 * 421 * @param currentChange New value of property currentChange. 422 */ 423 private void setCurrentChange( ChangeSet currentChange ) 424 { 425 this.currentChange = currentChange; 426 } 427 428 /** 429 * Getter for property status. 430 * 431 * @return Value of property status. 432 */ 433 private int getStatus() 434 { 435 return status; 436 } 437 438 /** 439 * Setter for property status. 440 * 441 * @param status New value of property status. 442 */ 443 private void setStatus( int status ) 444 { 445 this.status = status; 446 } 447}