View Javadoc
1   package org.apache.maven.scm.provider.cvslib.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.log.ScmLogger;
25  import org.apache.maven.scm.util.AbstractConsumer;
26  
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.Comparator;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.StringTokenizer;
33  
34  /**
35   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse </a>
36   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
37   * @author Olivier Lamy
38   *
39   */
40  public class CvsChangeLogConsumer
41      extends AbstractConsumer
42  {
43      private List<ChangeSet> entries = new ArrayList<ChangeSet>();
44  
45      // state machine constants for reading cvs output
46  
47      /**
48       * expecting file information
49       */
50      private static final int GET_FILE = 1;
51  
52      /**
53       * expecting date
54       */
55      private static final int GET_DATE = 2;
56  
57      /**
58       * expecting comments
59       */
60      private static final int GET_COMMENT = 3;
61  
62      /**
63       * expecting revision
64       */
65      private static final int GET_REVISION = 4;
66  
67      /**
68       * Marks start of file data
69       */
70      private static final String START_FILE = "Working file: ";
71  
72      /**
73       * Marks end of file
74       */
75      private static final String END_FILE =
76          "===================================" + "==========================================";
77  
78      /**
79       * Marks start of revision
80       */
81      private static final String START_REVISION = "----------------------------";
82  
83      /**
84       * Marks revision data
85       */
86      private static final String REVISION_TAG = "revision ";
87  
88      /**
89       * Marks date data
90       */
91      private static final String DATE_TAG = "date: ";
92  
93      /**
94       * current status of the parser
95       */
96      private int status = GET_FILE;
97  
98      /**
99       * 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 }