001    package org.apache.maven.scm.provider.starteam.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.provider.starteam.command.StarteamCommandLineUtils;
026    import org.apache.maven.scm.util.AbstractConsumer;
027    
028    import java.io.File;
029    import java.text.SimpleDateFormat;
030    import java.util.ArrayList;
031    import java.util.Date;
032    import java.util.List;
033    import java.util.Locale;
034    
035    /**
036     * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
037     * @author Olivier Lamy
038     * @version $Id: StarteamChangeLogConsumer.java 1054408 2011-01-02 14:05:25Z olamy $
039     */
040    public class StarteamChangeLogConsumer
041        extends AbstractConsumer
042    {
043        private SimpleDateFormat localFormat = new SimpleDateFormat( "", Locale.getDefault() );
044    
045        private List<ChangeSet> entries = new ArrayList<ChangeSet>();
046    
047        private String workingDirectory;
048    
049        private String currentDir = "";
050    
051        // state machine constants for reading Starteam output
052    
053        /**
054         * expecting file information
055         */
056        private static final int GET_FILE = 1;
057    
058        /**
059         * expecting date
060         */
061        private static final int GET_AUTHOR = 2;
062    
063        /**
064         * expecting comments
065         */
066        private static final int GET_COMMENT = 3;
067    
068        /**
069         * expecting revision
070         */
071        private static final int GET_REVISION = 4;
072    
073    
074        /**
075         * Marks current directory data
076         */
077        private static final String DIR_MARKER = "(working dir: ";
078    
079        /**
080         * Marks start of file data
081         */
082        private static final String START_FILE = "History for: ";
083    
084    
085        /**
086         * Marks end of file
087         */
088        private static final String END_FILE =
089            "===================================" + "==========================================";
090    
091        /**
092         * Marks start of revision
093         */
094        private static final String START_REVISION = "----------------------------";
095    
096        /**
097         * Marks revision data
098         */
099        private static final String REVISION_TAG = "Branch Revision: ";
100    
101        /**
102         * Marks author data
103         */
104        private static final String AUTHOR_TAG = "Author: ";
105    
106        /**
107         * Marks date data
108         */
109        private static final String DATE_TAG = " Date: ";
110    
111        /**
112         * current status of the parser
113         */
114        private int status = GET_FILE;
115    
116        /**
117         * the current log entry being processed by the parser
118         */
119        private ChangeSet currentChange = null;
120    
121        /**
122         * the current file being processed by the parser
123         */
124        private ChangeFile currentFile = null;
125    
126        /**
127         * the before date
128         */
129        private Date startDate;
130    
131        /**
132         * the to date
133         */
134        private Date endDate;
135    
136        private String userDateFormat;
137    
138        // ----------------------------------------------------------------------
139        //
140        // ----------------------------------------------------------------------
141    
142        public StarteamChangeLogConsumer( File workingDirectory, ScmLogger logger, Date startDate, Date endDate,
143                                          String userDateFormat )
144        {
145            super( logger );
146    
147            this.workingDirectory = workingDirectory.getPath().replace( '\\', '/' );
148    
149            this.startDate = startDate;
150    
151            this.endDate = endDate;
152    
153            this.userDateFormat = userDateFormat;
154    
155            //work around for all en_US compatible locales, where Starteam
156            // stcmd hist output uses a different format, ugly eh?
157            // makesure to change the test file as well if this ever got fixed
158    
159            if ( "M/d/yy h:mm a".equals( localFormat.toLocalizedPattern() ) )
160            {
161                this.localFormat = new SimpleDateFormat( "M/d/yy h:mm:ss a z" );
162            }
163        }
164    
165        // ----------------------------------------------------------------------
166        //
167        // ----------------------------------------------------------------------
168    
169        public List<ChangeSet> getModifications()
170        {
171            return entries;
172        }
173    
174        // ----------------------------------------------------------------------
175        // StreamConsumer Implementation
176        // ----------------------------------------------------------------------
177    
178        /** {@inheritDoc} */
179        public void consumeLine( String line )
180        {
181            if ( getLogger().isDebugEnabled() )
182            {
183                getLogger().debug( line );
184            }
185    
186            int pos = 0;
187    
188            if ( ( pos = line.indexOf( DIR_MARKER ) ) != -1 )
189            {
190                processDirectory( line, pos );
191                return;
192            }
193    
194            // current state transitions in the state machine - starts with Get File
195            //      Get File                -> Get Revision
196            //      Get Revision            -> Get Date or Get File
197            //      Get Date                -> Get Comment
198            //      Get Comment             -> Get Comment or Get Revision
199            switch ( getStatus() )
200            {
201                case GET_FILE:
202                    processGetFile( line );
203                    break;
204                case GET_REVISION:
205                    processGetRevision( line );
206                    break;
207                case GET_AUTHOR:
208                    processGetAuthor( line );
209                    break;
210                case GET_COMMENT:
211                    processGetComment( line );
212                    break;
213                default:
214                    throw new IllegalStateException( "Unknown state: " + status );
215            }
216        }
217    
218        // ----------------------------------------------------------------------
219        //
220        // ----------------------------------------------------------------------
221    
222        /**
223         * Add a change log entry to the list (if it's not already there)
224         * with the given file.
225         *
226         * @param entry a {@link ChangeSet} to be added to the list if another
227         *              with the same key doesn't exist already. If the entry's author
228         *              is null, the entry wont be added
229         * @param file  a {@link ChangeFile} to be added to the entry
230         */
231        private void addEntry( ChangeSet entry, ChangeFile file )
232        {
233            // do not add if entry is not populated
234            if ( entry.getAuthor() == null )
235            {
236                return;
237            }
238    
239            // do not add if entry is out of date range
240            if ( startDate != null && entry.getDate().before( startDate ) )
241            {
242                return;
243            }
244    
245            if ( endDate != null && entry.getDate().after( endDate ) )
246            {
247                return;
248            }
249    
250            entry.addFile( file );
251    
252            entries.add( entry );
253        }
254    
255        private void processDirectory( String line, int pos )
256        {
257            String dirPath = line.substring( pos + DIR_MARKER.length(), line.length() - 1 ).replace( '\\', '/' );
258            try
259            {
260                this.currentDir = StarteamCommandLineUtils.getRelativeChildDirectory( this.workingDirectory, dirPath );
261            }
262            catch ( IllegalStateException e )
263            {
264                String error = "Working and checkout directories are not on the same tree";
265    
266                if ( getLogger().isErrorEnabled() )
267                {
268                    getLogger().error( error );
269    
270                    getLogger().error( "Working directory: " + workingDirectory );
271    
272                    getLogger().error( "Checked out directory: " + dirPath );
273                }
274    
275                throw new IllegalStateException( error );
276            }
277        }
278    
279        /**
280         * Process the current input line in the Get File state.
281         *
282         * @param line a line of text from the Starteam log output
283         */
284        private void processGetFile( String line )
285        {
286            if ( line.startsWith( START_FILE ) )
287            {
288                setCurrentChange( new ChangeSet() );
289    
290                setCurrentFile(
291                    new ChangeFile( this.currentDir + "/" + line.substring( START_FILE.length(), line.length() ) ) );
292    
293                setStatus( GET_REVISION );
294            }
295        }
296    
297        /**
298         * Process the current input line in the Get Revision state.
299         *
300         * @param line a line of text from the Starteam log output
301         */
302        private void processGetRevision( String line )
303        {
304            int pos;
305    
306            if ( ( pos = line.indexOf( REVISION_TAG ) ) != -1 )
307            {
308                getCurrentFile().setRevision( line.substring( pos + REVISION_TAG.length() ) );
309    
310                setStatus( GET_AUTHOR );
311            }
312            else if ( line.startsWith( END_FILE ) )
313            {
314                // If we encounter an end of file line, it means there
315                // are no more revisions for the current file.
316                // there could also be a file still being processed.
317                setStatus( GET_FILE );
318    
319                addEntry( getCurrentChange(), getCurrentFile() );
320            }
321        }
322    
323        /**
324         * Process the current input line in the Get Author/Date state.
325         *
326         * @param line a line of text from the Starteam log output
327         */
328        private void processGetAuthor( String line )
329        {
330            if ( line.startsWith( AUTHOR_TAG ) )
331            {
332                int posDateTag = line.indexOf( DATE_TAG );
333    
334                String author = line.substring( AUTHOR_TAG.length(), posDateTag );
335    
336                getCurrentChange().setAuthor( author );
337    
338                String date = line.substring( posDateTag + DATE_TAG.length() );
339    
340                Date dateObj = parseDate( date, userDateFormat, localFormat.toPattern() );
341    
342                if ( dateObj != null )
343                {
344                    getCurrentChange().setDate( dateObj );
345                }
346                else
347                {
348                    getCurrentChange().setDate( date, userDateFormat );
349                }
350    
351                setStatus( GET_COMMENT );
352            }
353        }
354    
355        /**
356         * Process the current input line in the Get Comment state.
357         *
358         * @param line a line of text from the Starteam log output
359         */
360        private void processGetComment( String line )
361        {
362            if ( line.startsWith( START_REVISION ) )
363            {
364                // add entry, and set state to get revision
365                addEntry( getCurrentChange(), getCurrentFile() );
366    
367                // new change log entry
368                setCurrentChange( new ChangeSet() );
369    
370                // same file name, but different rev
371                setCurrentFile( new ChangeFile( getCurrentFile().getName() ) );
372    
373                setStatus( GET_REVISION );
374            }
375            else if ( line.startsWith( END_FILE ) )
376            {
377                addEntry( getCurrentChange(), getCurrentFile() );
378    
379                setStatus( GET_FILE );
380            }
381            else
382            {
383                // keep gathering comments
384                getCurrentChange().setComment( getCurrentChange().getComment() + line + "\n" );
385            }
386        }
387    
388        /**
389         * Getter for property currentFile.
390         *
391         * @return Value of property currentFile.
392         */
393        private ChangeFile getCurrentFile()
394        {
395            return currentFile;
396        }
397    
398        /**
399         * Setter for property currentFile.
400         *
401         * @param currentFile New value of property currentFile.
402         */
403        private void setCurrentFile( ChangeFile currentFile )
404        {
405            this.currentFile = currentFile;
406        }
407    
408        /**
409         * Getter for property currentChange.
410         *
411         * @return Value of property currentChange.
412         */
413        private ChangeSet getCurrentChange()
414        {
415            return currentChange;
416        }
417    
418        /**
419         * Setter for property currentChange.
420         *
421         * @param currentChange New value of property currentChange.
422         */
423        private void setCurrentChange( ChangeSet currentChange )
424        {
425            this.currentChange = currentChange;
426        }
427    
428        /**
429         * Getter for property status.
430         *
431         * @return Value of property status.
432         */
433        private int getStatus()
434        {
435            return status;
436        }
437    
438        /**
439         * Setter for property status.
440         *
441         * @param status New value of property status.
442         */
443        private void setStatus( int status )
444        {
445            this.status = status;
446        }
447    }