001    package 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    
022    import org.apache.maven.scm.ChangeFile;
023    import org.apache.maven.scm.ChangeSet;
024    import org.apache.maven.scm.log.ScmLogger;
025    import org.apache.maven.scm.util.AbstractConsumer;
026    
027    import java.util.ArrayList;
028    import java.util.Collections;
029    import java.util.Comparator;
030    import java.util.Iterator;
031    import java.util.List;
032    import 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     * @version $Id: CvsChangeLogConsumer.java 1054408 2011-01-02 14:05:25Z olamy $
039     */
040    public 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    }