001package 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
022import org.apache.maven.scm.ChangeFile;
023import org.apache.maven.scm.ChangeSet;
024import org.apache.maven.scm.log.ScmLogger;
025import org.apache.maven.scm.provider.starteam.command.StarteamCommandLineUtils;
026import org.apache.maven.scm.util.AbstractConsumer;
027
028import java.io.File;
029import java.text.SimpleDateFormat;
030import java.util.ArrayList;
031import java.util.Date;
032import java.util.List;
033import java.util.Locale;
034
035/**
036 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
037 * @author Olivier Lamy
038 *
039 */
040public 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}