View Javadoc

1   /*
2   The MIT License
3   
4   Copyright (c) 2004, The Codehaus
5   
6   Permission is hereby granted, free of charge, to any person obtaining a copy of
7   this software and associated documentation files (the "Software"), to deal in
8   the Software without restriction, including without limitation the rights to
9   use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10  of the Software, and to permit persons to whom the Software is furnished to do
11  so, subject to the following conditions:
12  
13  The above copyright notice and this permission notice shall be included in all
14  copies or substantial portions of the Software.
15  
16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  SOFTWARE.
23   */
24  package org.codehaus.mojo.scmchangelog.scm.hg.command.changelog;
25  
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.Date;
29  import java.util.List;
30  import java.util.Locale;
31  
32  import org.apache.maven.scm.ChangeFile;
33  import org.apache.maven.scm.ScmFileStatus;
34  import org.apache.maven.scm.log.ScmLogger;
35  import org.apache.maven.scm.provider.hg.command.HgConsumer;
36  
37  /**
38   * Command consumer that parses the output from a <code>hg log --verbose -rx:y</code> 
39   * command, using a grammar to build  the changelog.
40   * @author ehsavoie
41   * @version $Id: HgChangeLogConsumer.java 7663 2008-09-12 07:55:51Z ehsavoie $
42   *
43   * @see org.apache.maven.scm.provider.hg.command.HgConsumer
44   * @see org.codehaus.mojo.scmchangelog.scm.hg.changelog.HgChangeLogCommand
45   */
46  public class HgChangeLogConsumer
47      extends HgConsumer
48  {
49  
50    /**
51     * The time stamp format.
52     */
53    private static final String TIME_PATTERN = "EEE MMM dd HH:mm:ss yyyy Z";
54    /**
55     * The changeset element in the changelog.
56     */
57    private static final String REVNO_TAG = "changeset: ";
58    /**
59     * The tag element in the changelog.
60     */
61    private static final String TAG_TAG = "tag:         ";
62    /**
63     * The author element in the changelog.
64     */
65    private static final String AUTHOR_TAG = "user: ";
66    /**
67     * The timestamp element in the changelog.
68     */
69    private static final String TIME_STAMP_TOKEN = "date: ";
70    /**
71     * The message element in the changelog.
72     */
73    private static final String MESSAGE_TOKEN = "description:";
74    /**
75     * The merged element in the changelog.
76     */
77    private static final String MERGED_TOKEN = "merged: ";
78    /**
79     * The files element in the changelog.
80     */
81    private static final String FILES_TOKEN = "files: ";
82    /**
83     * The previous line to the current parsed line.
84     */
85    private String prevLine = "";
86    /**
87     * The previous previous line to the current parsed line.
88     */
89    private String prevPrevLine = "";
90    /**
91     * The entries resulting from parsing this changelog output.
92     */
93    private ArrayList logEntries = new ArrayList();
94    /**
95     * The current entry.
96     */
97    private BetterChangeSet currentChange;
98    /**
99     * Last entry in the current changes.
100    */
101   private BetterChangeSet lastChange;
102   /**
103    * Indicates if it is a merge entry.
104    */
105   private boolean isMergeEntry;
106   /**
107    * The current revision of the entry.
108    */
109   private String currentRevision;
110   /**
111    * Not used.
112    */
113   private String currentTag; // don't know what to do with this
114   /**
115    * To specified another date pattern.
116    */
117   private String userDatePattern;
118   /**
119    * Used for multiline comments.
120    */
121   private boolean spoolingComments;
122   /**
123    * List of comments for the current entry.
124    */
125   private List currentComment = null;
126 
127   /**
128    * Construtor for HgChangeLogConsumer.
129    * @param logger the logger used for trace.
130    * @param userDatePattern the pattern for parsing dates.
131    */
132   public HgChangeLogConsumer( ScmLogger logger, String userDatePattern )
133   {
134     super( logger );
135 
136     this.userDatePattern = userDatePattern;
137   }
138 
139   /**
140    * Return a list of BetterChangeSet.
141    * @return a List&lt;BetterChangeSet&gt;
142    * @see org.codehaus.mojo.scmchangelog.scm.hg.changelog.BetterChangeSet
143    */
144   public List getModifications()
145   {
146     Collections.reverse( logEntries );
147     return logEntries;
148   }
149 
150   /**
151    * Consume a line of the output of the command.
152    * @param line the line to be interpreted.
153    */
154   public void consumeLine( String line )
155   {
156     // override default behaviour which tries to pick through things for some standard messages.  that
157     // does not apply here
158     doConsume( null, line );
159   }
160 
161   /**
162    * Consume a line
163    * @param status null.
164    * @param line a line of the command output.
165    */
166   public void doConsume( ScmFileStatus status, String line )
167   {
168     String tmpLine = line;
169 
170     // If current status == null then this is a new entry
171     // If the line == "" and previous line was "", then this is also a new entry
172     if ( ( line.equals( "" ) && ( prevLine.equals( "" ) && prevPrevLine.equals( "" ) ) ) || ( currentComment == null ) )
173     {
174       if ( currentComment != null )
175       {
176         StringBuffer comment = new StringBuffer();
177 
178         for ( int i = 0; i < ( currentComment.size() - 1 ); i++ )
179         {
180           comment.append( currentComment.get( i ) );
181 
182           if ( ( i + 1 ) < ( currentComment.size() - 1 ) )
183           {
184             comment.append( '\n' );
185           }
186         }
187         currentChange.setComment( comment.toString() );
188       }
189       spoolingComments = false;
190 
191       //If last entry was part a merged entry
192       if ( isMergeEntry 
193           && ( lastChange != null ) )
194       {
195         String comment = lastChange.getComment();
196         comment += ( "\n[MAVEN]: Merged from " 
197             + currentChange.getAuthor() );
198         comment += ( "\n[MAVEN]:    " 
199             + currentChange.getDateFormatted() );
200         comment += ( "\n[MAVEN]:    " 
201             + currentChange.getComment() );
202         lastChange.setComment( comment );
203       }
204 
205       //Init a new changeset
206       currentChange = new BetterChangeSet();
207       currentChange.setFiles( new ArrayList() );
208       logEntries.add( currentChange );
209 
210       //Reset memeber vars
211       currentComment = new ArrayList();
212       currentRevision = "";
213       isMergeEntry = false;
214     }
215 
216     if ( spoolingComments )
217     {
218       currentComment.add( line );
219     } 
220     else if ( line.startsWith( MESSAGE_TOKEN ) )
221     {
222       spoolingComments = true;
223     } 
224     else if ( line.startsWith( MERGED_TOKEN ) )
225     {
226       //This is part of lastChange and is not a separate log entry
227       isMergeEntry = true;
228       logEntries.remove( currentChange );
229 
230       if ( logEntries.size() > 0 )
231       {
232         lastChange = ( BetterChangeSet ) logEntries.get( logEntries.size() - 1 );
233       } 
234       else
235       {
236         getLogger().warn( "First entry was unexpectedly a merged entry" );
237         lastChange = null;
238       }
239     } 
240     else if ( line.startsWith( REVNO_TAG ) )
241     {
242       tmpLine = line.substring( REVNO_TAG.length() );
243       tmpLine = tmpLine.trim();
244       currentRevision = tmpLine;
245       currentChange.setRevision( currentRevision.substring( 0,
246           currentRevision.indexOf( ':' ) ) );
247     } 
248     else if ( line.startsWith( TAG_TAG ) )
249     {
250       tmpLine = line.substring( TAG_TAG.length() ).trim();
251       currentTag = tmpLine;
252     }
253     else if ( line.startsWith( AUTHOR_TAG ) )
254     {
255       tmpLine = line.substring( AUTHOR_TAG.length() );
256       tmpLine = tmpLine.trim();
257       currentChange.setAuthor( tmpLine );
258     } 
259     else if ( line.startsWith( TIME_STAMP_TOKEN ) )
260     {
261       // TODO: FIX Date Parsing to match Mercurial or fix with template
262       tmpLine = line.substring( TIME_STAMP_TOKEN.length() ).trim();
263 
264       Date date = parseDate( tmpLine, userDatePattern, TIME_PATTERN,
265           Locale.ENGLISH );
266       currentChange.setDate( date );
267     } 
268     else if ( line.startsWith( FILES_TOKEN ) )
269     {
270       tmpLine = line.substring( FILES_TOKEN.length() ).trim();
271 
272       String[] files = tmpLine.split( " " );
273 
274       for ( int i = 0; i < files.length; i++ )
275       {
276         String file = files[i];
277         ChangeFile changeFile = new ChangeFile( file, currentRevision );
278         currentChange.addFile( changeFile );
279       }
280     } 
281     else
282     {
283       getLogger().warn( "Could not figure out: " + line );
284     }
285 
286     // record previous line
287     prevLine = line;
288     prevPrevLine = prevLine;
289   }
290 }