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&oslash;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}