View Javadoc

1   package org.apache.maven.scm.provider.jazz.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 org.apache.maven.scm.ChangeFile;
23  import org.apache.maven.scm.ChangeSet;
24  import org.apache.maven.scm.ScmFileStatus;
25  import org.apache.maven.scm.log.ScmLogger;
26  import org.apache.maven.scm.provider.ScmProviderRepository;
27  import org.apache.maven.scm.provider.jazz.command.consumer.AbstractRepositoryConsumer;
28  import org.apache.regexp.RE;
29  import org.apache.regexp.RESyntaxException;
30  
31  import java.util.ArrayList;
32  import java.util.Calendar;
33  import java.util.Date;
34  import java.util.List;
35  import java.util.Locale;
36  
37  /**
38   * Consume the output of the scm command for the "list changesets" operation.
39   * <p/>
40   * This parses the contents of the output and uses it to fill in the remaining
41   * information in the <code>entries</code> list.
42   *
43   * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
44   */
45  public class JazzListChangesetConsumer
46      extends AbstractRepositoryConsumer
47  {
48  //Change sets:
49  //  (1589)  ---$ Deb "[maven-release-plugin] prepare for next development iteration"
50  //    Component: (1158) "GPDB"
51  //    Modified: Feb 25, 2012 10:15 PM (Yesterday)
52  //    Changes:
53  //      ---c- (1170) \GPDB\GPDBEAR\pom.xml
54  //      ---c- (1171) \GPDB\GPDBResources\pom.xml
55  //      ---c- (1167) \GPDB\GPDBWeb\pom.xml
56  //      ---c- (1165) \GPDB\pom.xml
57  //  (1585)  ---$ Deb "[maven-release-plugin] prepare release GPDB-1.0.21"
58  //    Component: (1158) "GPDB"
59  //    Modified: Feb 25, 2012 10:13 PM (Yesterday)
60  //    Changes:
61  //      ---c- (1170) \GPDB\GPDBEAR\pom.xml
62  //      ---c- (1171) \GPDB\GPDBResources\pom.xml
63  //      ---c- (1167) \GPDB\GPDBWeb\pom.xml
64  //      ---c- (1165) \GPDB\pom.xml
65  //  (1584)  ---$ Deb "This is my first changeset (2)"
66  //    Component: (1158) "GPDB"
67  //    Modified: Feb 25, 2012 10:13 PM (Yesterday)
68  //  (1583)  ---$ Deb "This is my first changeset (1)"
69  //    Component: (1158) "GPDB"
70  //    Modified: Feb 25, 2012 10:13 PM (Yesterday)
71  //  (1323)  ---$ Deb <No comment>
72  //    Component: (1158) "GPDB"
73  //    Modified: Feb 24, 2012 11:04 PM (Last Week)
74  //    Changes:
75  //      ---c- (1170) \GPDB\GPDBEAR\pom.xml
76  //      ---c- (1171) \GPDB\GPDBResources\pom.xml
77  //      ---c- (1167) \GPDB\GPDBWeb\pom.xml
78  //      ---c- (1165) \GPDB\pom.xml
79  //  (1319)  ---$ Deb <No comment>
80  //    Component: (1158) "GPDB"
81  //    Modified: Feb 24, 2012 11:03 PM (Last Week)
82  //    Changes:
83  //      ---c- (1170) \GPDB\GPDBEAR\pom.xml
84  //      ---c- (1171) \GPDB\GPDBResources\pom.xml
85  //      ---c- (1167) \GPDB\GPDBWeb\pom.xml
86  //      ---c- (1165) \GPDB\pom.xml
87  //
88  // NOTE: If the change sets originate on the current date, the date is not
89  //       displayed, only the time is.
90  // EG:
91  //Change sets:
92  //  (1809)  ---$ Deb "[maven-release-plugin] prepare for next development iteration"
93  //    Component: (1158) "GPDB"
94  //    Modified: 6:20 PM (5 minutes ago)
95  //    Changes:
96  //      ---c- (1170) \GPDB\GPDBEAR\pom.xml
97  //      ---c- (1171) \GPDB\GPDBResources\pom.xml
98  //      ---c- (1167) \GPDB\GPDBWeb\pom.xml
99  //      ---c- (1165) \GPDB\pom.xml
100 //  (1801)  ---$ Deb "[maven-release-plugin] prepare release GPDB-1.0.26"
101 //    Component: (1158) "GPDB"
102 //    Modified: 6:18 PM (10 minutes ago)
103 //    Changes:
104 //      ---c- (1170) \GPDB\GPDBEAR\pom.xml
105 //      ---c- (1171) \GPDB\GPDBResources\pom.xml
106 //      ---c- (1167) \GPDB\GPDBWeb\pom.xml
107 //  (1799)  ---$ Deb <No comment>
108 //    Component: (1158) "GPDB"
109 //    Modified: 6:18 PM (10 minutes ago)
110 //    Changes:
111 //      ---c- (1165) \GPDB\pom.xml
112 //  (1764)  ---$ Deb <No comment>
113 //    Component: (1158) "GPDB"
114 //    Modified: Mar 1, 2012 2:34 PM
115 //    Changes:
116 //      ---c- (1165) \GPDB\pom.xml
117 
118 
119     // State Machine Definitions
120     private static final int STATE_CHANGE_SETS = 0;
121 
122     private static final int STATE_CHANGE_SET = 1;
123 
124     private static final int STATE_COMPONENT = 2;
125 
126     private static final int STATE_MODIFIED = 3;
127 
128     private static final int STATE_CHANGES = 4;
129 
130     // Header definitions. 
131     private static final String HEADER_CHANGE_SETS = "Change sets:";
132 
133     private static final String HEADER_CHANGE_SET = "(";
134 
135     private static final String HEADER_COMPONENT = "Component:";
136 
137     private static final String HEADER_MODIFIED = "Modified:";
138 
139     private static final String HEADER_CHANGES = "Changes:";
140 
141     private static final String JAZZ_TIMESTAMP_PATTERN = "MMM d, yyyy h:mm a";
142     // Actually: DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.SHORT );
143 
144     private static final String JAZZ_TIMESTAMP_PATTERN_TIME = "h:mm a";
145     // Only seen when the data = today. Only the time is displayed.
146 
147     //  (1589)  ---$ Deb "[maven-release-plugin] prepare for next development iteration"
148     //  (1585)  ---$ Deb "[maven-release-plugin] prepare release GPDB-1.0.21"
149     private static final String CHANGESET_PATTERN = "\\((\\d+)\\)  (....) (\\w+) (.*)";
150 
151     /**
152      * @see #CHANGESET_PATTERN
153      */
154     private RE changeSetRegExp;
155 
156     //      ---c- (1170) \GPDB\GPDBEAR\pom.xml
157     //      ---c- (1171) \GPDB\GPDBResources\pom.xml
158     //      ---c- (1167) \GPDB\GPDBWeb\pom.xml
159     //      ---c- (1165) \GPDB\pom.xml
160     private static final String CHANGES_PATTERN = "(.....) \\((\\d+)\\) (.*)";
161 
162     /**
163      * @see #CHANGES_PATTERN
164      */
165     private RE changesRegExp;
166 
167 
168     private List<ChangeSet> entries;
169 
170     private final String userDateFormat;
171 
172     // This is incremented at the beginning of every change set line. So we start at -1 (to get zero on first processing)
173     private int currentChangeSetIndex = -1;
174 
175     private int currentState = STATE_CHANGE_SETS;
176 
177     /**
178      * Constructor for our "scm list changeset" consumer.
179      *
180      * @param repo    The JazzScmProviderRepository being used.
181      * @param logger  The ScmLogger to use.
182      * @param entries The List of ChangeSet entries that we will populate.
183      */
184     public JazzListChangesetConsumer( ScmProviderRepository repo, ScmLogger logger, List<ChangeSet> entries,
185                                       String userDateFormat )
186     {
187         super( repo, logger );
188         this.entries = entries;
189         this.userDateFormat = userDateFormat;
190 
191         try
192         {
193             changeSetRegExp = new RE( CHANGESET_PATTERN );
194             changesRegExp = new RE( CHANGES_PATTERN );
195         }
196         catch ( RESyntaxException ex )
197         {
198             throw new RuntimeException(
199                 "INTERNAL ERROR: Could not create regexp to parse jazz scm history output. This shouldn't happen. Something is probably wrong with the oro installation.",
200                 ex );
201         }
202     }
203 
204     /**
205      * Process one line of output from the execution of the "scm list changeset" command.
206      *
207      * @param line The line of output from the external command that has been pumped to us.
208      * @see org.codehaus.plexus.util.cli.StreamConsumer#consumeLine(java.lang.String)
209      */
210     public void consumeLine( String line )
211     {
212         super.consumeLine( line );
213 
214         // Process the "Change sets:" line - do nothing
215         if ( line.trim().startsWith( HEADER_CHANGE_SETS ) )
216         {
217             currentState = STATE_CHANGE_SETS;
218         }
219         else
220         {
221             if ( line.trim().startsWith( HEADER_CHANGE_SET ) )
222             {
223                 currentState = STATE_CHANGE_SET;
224             }
225             else
226             {
227                 if ( line.trim().startsWith( HEADER_COMPONENT ) )
228                 {
229                     currentState = STATE_COMPONENT;
230                 }
231                 else
232                 {
233                     if ( line.trim().startsWith( HEADER_MODIFIED ) )
234                     {
235                         currentState = STATE_MODIFIED;
236                     }
237                     else
238                     {
239                         if ( line.trim().startsWith( HEADER_CHANGES ) )
240                         {
241                             // Note: processChangesLine() will also be passed the "Changes:" line
242                             // So, it needs to be able to deal with that.
243                             // Changes:
244                             //   ---c- (1170) \GPDB\GPDBEAR\pom.xml
245                             //   ---c- (1171) \GPDB\GPDBResources\pom.xml
246                             //   ---c- (1167) \GPDB\GPDBWeb\pom.xml
247                             //   ---c- (1165) \GPDB\pom.xml
248                             currentState = STATE_CHANGES;
249                         }
250                     }
251                 }
252             }
253         }
254 
255         switch ( currentState )
256         {
257             case STATE_CHANGE_SETS:
258                 // Nothing to do.
259                 break;
260 
261             case STATE_CHANGE_SET:
262                 processChangeSetLine( line );
263                 break;
264 
265             case STATE_COMPONENT:
266                 // Nothing to do. Not used (Yet?)
267                 break;
268 
269             case STATE_MODIFIED:
270                 processModifiedLine( line );
271                 break;
272 
273             case STATE_CHANGES:
274                 processChangesLine( line );
275                 break;
276         }
277 
278     }
279 
280     private void processChangeSetLine( String line )
281     {
282         // Process the headerless change set line - starts with a '(', eg:
283         // (1589)  ---$ Deb "[maven-release-plugin] prepare for next development iteration"
284         // (1585)  ---$ Deb "[maven-release-plugin] prepare release GPDB-1.0.21"
285         if ( changeSetRegExp.match( line ) )
286         {
287             // This is the only place this gets incremented.
288             // It starts at -1, and on first execution is incremented to 0 - which is correct.
289             currentChangeSetIndex++;
290             ChangeSet currentChangeSet = entries.get( currentChangeSetIndex );
291 
292             // Init the file of files, so it is not null, but it can be empty!
293             List<ChangeFile> files = new ArrayList<ChangeFile>();
294             currentChangeSet.setFiles( files );
295 
296             String changesetAlias = changeSetRegExp.getParen( 1 );
297             String changeFlags = changeSetRegExp.getParen( 2 );     // Not used.
298             String author = changeSetRegExp.getParen( 3 );
299             String comment = changeSetRegExp.getParen( 4 );
300 
301             if ( getLogger().isDebugEnabled() )
302             {
303                 getLogger().debug( "  Parsing ChangeSet Line : " + line );
304                 getLogger().debug( "    changesetAlias : " + changesetAlias );
305                 getLogger().debug( "    changeFlags    : " + changeFlags );
306                 getLogger().debug( "    author         : " + author );
307                 getLogger().debug( "    comment        : " + comment );
308             }
309 
310             // Sanity check.
311             if ( currentChangeSet.getRevision() != null && !currentChangeSet.getRevision().equals( changesetAlias ) )
312             {
313                 getLogger().warn( "Warning! The indexes appear to be out of sequence! " +
314                                       "For currentChangeSetIndex = " + currentChangeSetIndex + ", we got '" +
315                                       changesetAlias + "' and not '" + currentChangeSet.getRevision()
316                                       + "' as expected." );
317             }
318 
319             comment = stripDelimiters( comment );
320             currentChangeSet.setAuthor( author );
321             currentChangeSet.setComment( comment );
322         }
323     }
324 
325     private void processModifiedLine( String line )
326     {
327         // Process the "Modified: ..." line, eg:
328         // Modified: Feb 25, 2012 10:15 PM (Yesterday)
329         // Modified: Feb 25, 2012 10:13 PM (Yesterday)
330         // Modified: Feb 24, 2012 11:03 PM (Last Week)
331         // Modified: Mar 1, 2012 2:34 PM
332         // Modified: 6:20 PM (5 minutes ago)
333 
334         if ( getLogger().isDebugEnabled() )
335         {
336             getLogger().debug( "  Parsing Modified Line : " + line );
337         }
338 
339         int colonPos = line.indexOf( ":" );
340         int parenPos = line.indexOf( "(" );
341 
342         String date = null;
343 
344         if ( colonPos != -1 && parenPos != -1 )
345         {
346             date = line.substring( colonPos + 2, parenPos - 1 );
347         }
348         else
349         {
350             if ( colonPos != -1 && parenPos == -1 )
351             {
352                 // No trailing bracket
353                 date = line.substring( colonPos + 2 );
354             }
355         }
356 
357         if ( date != null )
358         {
359             Date changesetDate = parseDate( date.toString(), userDateFormat, JAZZ_TIMESTAMP_PATTERN );
360             // try again forcing en locale
361             if ( changesetDate == null )
362             {
363                 changesetDate = parseDate( date.toString(), userDateFormat, JAZZ_TIMESTAMP_PATTERN, Locale.ENGLISH );
364             }
365             if ( changesetDate == null )
366             {
367                 // changesetDate will be null when the date is not given, it only has just the time. The date is today.
368                 changesetDate = parseDate( date.toString(), userDateFormat, JAZZ_TIMESTAMP_PATTERN_TIME );
369                 // Get today's time/date. Used to get the date.
370                 Calendar today = Calendar.getInstance();
371                 // Get a working one.
372                 Calendar changesetCal = Calendar.getInstance();
373                 // Set the date/time. Used to set the time.
374                 changesetCal.setTimeInMillis( changesetDate.getTime() );
375                 // Now set the date (today).
376                 changesetCal.set( today.get( Calendar.YEAR ), today.get( Calendar.MONTH ),
377                                   today.get( Calendar.DAY_OF_MONTH ) );
378                 // Now get the date of the combined results.
379                 changesetDate = changesetCal.getTime();
380             }
381 
382             if ( getLogger().isDebugEnabled() )
383             {
384                 getLogger().debug( "    date           : " + date );
385                 getLogger().debug( "    changesetDate  : " + changesetDate );
386             }
387 
388             ChangeSet currentChangeSet = entries.get( currentChangeSetIndex );
389             currentChangeSet.setDate( changesetDate );
390         }
391     }
392 
393     private void processChangesLine( String line )
394     {
395         // Process the changes line, eg:
396         //      ---c- (1170) \GPDB\GPDBEAR\pom.xml
397         //      ---c- (1171) \GPDB\GPDBResources\pom.xml
398         //      ---c- (1167) \GPDB\GPDBWeb\pom.xml
399         //      ---c- (1165) \GPDB\pom.xml
400         if ( changesRegExp.match( line ) )
401         {
402             ChangeSet currentChangeSet = entries.get( currentChangeSetIndex );
403 
404             String changeFlags = changesRegExp.getParen( 1 );     // Not used.
405             String fileAlias = changesRegExp.getParen( 2 );
406             String file = changesRegExp.getParen( 3 );
407 
408             if ( getLogger().isDebugEnabled() )
409             {
410                 getLogger().debug( "  Parsing Changes Line : " + line );
411                 getLogger().debug(
412                     "    changeFlags    : " + changeFlags + " Translated to : " + parseFileChangeState( changeFlags ) );
413                 getLogger().debug( "    filetAlias     : " + fileAlias );
414                 getLogger().debug( "    file           : " + file );
415             }
416 
417             ChangeFile changeFile = new ChangeFile( file );
418             ScmFileStatus status = parseFileChangeState( changeFlags );
419             changeFile.setAction( status );
420             currentChangeSet.getFiles().add( changeFile );
421         }
422     }
423 
424     /**
425      * String the leading/trailing ", < and > from the text.
426      *
427      * @param text The text to process.
428      * @return The striped text.
429      */
430     protected String stripDelimiters( String text )
431     {
432         if ( text == null )
433         {
434             return null;
435         }
436         String workingText = text;
437         if ( workingText.startsWith( "\"" ) || workingText.startsWith( "<" ) )
438         {
439             workingText = workingText.substring( 1 );
440         }
441         if ( workingText.endsWith( "\"" ) || workingText.endsWith( ">" ) )
442         {
443             workingText = workingText.substring( 0, workingText.length() - 1 );
444         }
445 
446         return workingText;
447     }
448 
449     /**
450      * Parse the change state file flags from Jazz and map them to the maven SCM ones.
451      * <p/>
452      * "----" Character positions 0-3.
453      * <p/>
454      * [0] is '*' or '-'    Indicates that this is the current change set ('*') or not ('-').   STATE_CHANGESET_CURRENT
455      * [1] is '!' or '-'    Indicates a Potential Conflict ('!') or not ('-').                  STATE_POTENTIAL_CONFLICT
456      * [2] is '#' or '-'    Indicates a Conflict ('#') or not ('-').                            STATE_CONFLICT
457      * [3] is '@' or '$'    Indicates whether the changeset is active ('@') or not ('$').       STATE_CHANGESET_ACTIVE
458      *
459      * @param state The 5 character long state string
460      * @return The ScmFileStatus value.
461      */
462     private ScmFileStatus parseChangeSetChangeState( String state )
463     {
464         if ( state.length() != 4 )
465         {
466             throw new IllegalArgumentException( "Change State string must be 4 chars long!" );
467         }
468 
469         // This is not used, but is here for potential future usage and for documentation purposes.
470         return ScmFileStatus.UNKNOWN;
471     }
472 
473     /**
474      * Parse the change state file flags from Jazz and map them to the maven SCM ones.
475      * <p/>
476      * "-----" Character positions 0-4. The default is '-'.
477      * <p/>
478      * [0] is '-' or '!'    Indicates a Potential Conflict. STATE_POTENTIAL_CONFLICT
479      * [1] is '-' or '#'    Indicates a Conflict.           STATE_CONFLICT
480      * [2] is '-' or 'a'    Indicates an addition.          STATE_ADD
481      * or 'd'    Indicates a deletion.           STATE_DELETE
482      * or 'm'    Indicates a move.               STATE_MOVE
483      * [3] is '-' or 'c'    Indicates a content change.     STATE_CONTENT_CHANGE
484      * [4] is '-' or 'p'    Indicates a property change.    STATE_PROPERTY_CHANGE
485      * <p/>
486      * NOTE: [3] and [4] can only be set it [2] is NOT 'a' or 'd'.
487      *
488      * @param state The 5 character long state string
489      * @return The SCMxxx value.
490      */
491     private ScmFileStatus parseFileChangeState( String state )
492     {
493         if ( state.length() != 5 )
494         {
495             throw new IllegalArgumentException( "Change State string must be 5 chars long!" );
496         }
497 
498         // NOTE: We have an impedance mismatch here. The Jazz file change flags represent
499         // many different states. However, we can only return *ONE* ScmFileStatus value,
500         // so we need to be careful as to the precedence that we give to them.
501 
502         ScmFileStatus status = ScmFileStatus.UNKNOWN;   // Probably not a valid initial default value.
503 
504         // [0] is '-' or '!'    Indicates a Potential Conflict. STATE_POTENTIAL_CONFLICT
505         if ( state.charAt( 0 ) == '!' )
506         {
507             status = ScmFileStatus.CONFLICT;
508         }
509         // [1] is '-' or '#'    Indicates a Conflict.           STATE_CONFLICT
510         if ( state.charAt( 1 ) == '#' )
511         {
512             status = ScmFileStatus.CONFLICT;
513         }
514 
515         // [2] is '-' or 'a'    Indicates an addition.          STATE_ADD
516         //            or 'd'    Indicates a deletion.           STATE_DELETE
517         //            or 'm'    Indicates a move.               STATE_MOVE
518         if ( state.charAt( 2 ) == 'a' )
519         {
520             status = ScmFileStatus.ADDED;
521         }
522         else
523         {
524             if ( state.charAt( 2 ) == 'd' )
525             {
526                 status = ScmFileStatus.DELETED;
527             }
528             else
529             {
530                 if ( state.charAt( 2 ) == 'm' )
531                 {
532                     status = ScmFileStatus.RENAMED;     // Has been renamed or moved.
533                 }
534 
535                 // [3] is '-' or 'c'    Indicates a content change.     STATE_CONTENT_CHANGE
536                 if ( state.charAt( 3 ) == 'c' )
537                 {
538                     status = ScmFileStatus.MODIFIED;    // The file has been modified in the working tree.
539                 }
540 
541                 // [4] is '-' or 'p'    Indicates a property change.    STATE_PROPERTY_CHANGE
542                 if ( state.charAt( 4 ) == 'p' )
543                 {
544                     status =
545                         ScmFileStatus.MODIFIED;    // ScmFileStatus has no concept of property or meta data changes.
546                 }
547             }
548         }
549 
550         return status;
551     }
552 }