001package org.apache.maven.scm.provider.vss.commands.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.vss.repository.VssScmProviderRepository;
026import org.apache.maven.scm.util.AbstractConsumer;
027
028import java.text.SimpleDateFormat;
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.List;
032import java.util.Map;
033import java.util.TreeMap;
034import java.util.Vector;
035
036/**
037 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
038 * @author Olivier Lamy
039 *
040 */
041public class VssChangeLogConsumer
042    extends AbstractConsumer
043{
044    /**
045     * Custom date/time formatter. Rounds ChangeLogEntry times to the nearest
046     * minute.
047     */
048    private static final SimpleDateFormat ENTRY_KEY_TIMESTAMP_FORMAT = new SimpleDateFormat( "yyyyMMddHHmm" );
049
050    // state machine constants for reading Starteam output
051
052    /**
053     * expecting file information
054     */
055    private static final int GET_FILE = 1;
056
057    /**
058     * expecting file path information
059     */
060    private static final int GET_FILE_PATH = 2;
061
062    /**
063     * expecting date
064     */
065    private static final int GET_AUTHOR = 3;
066
067    /**
068     * expecting comments
069     */
070    private static final int GET_COMMENT = 4;
071
072    /**
073     * expecting revision
074     */
075    private static final int GET_REVISION = 5;
076
077    /**
078     * unknown vss history line status
079     */
080    private static final int GET_UNKNOWN = 6;
081
082    /**
083     * Marks start of file data
084     */
085    private static final String START_FILE = "*****  ";
086
087    /**
088     * Marks start of file data
089     */
090    private static final String START_FILE_PATH = "$/";
091
092    /**
093     * Marks start of revision
094     */
095    private static final String START_REVISION = "Version";
096
097    /**
098     * Marks author data
099     */
100    private static final String START_AUTHOR = "User: ";
101
102    /**
103     * Marks comment data
104     */
105    private static final String START_COMMENT = "Comment: ";
106
107    /**
108     * rcs entries, in reverse (date, time, author, comment) order
109     */
110    private Map<String, ChangeSet> entries = new TreeMap<String, ChangeSet>( Collections.reverseOrder() );
111
112    private ChangeFile currentFile;
113
114    private ChangeSet currentChangeSet;
115
116    /**
117     * last status of the parser
118     */
119    private int lastStatus = GET_FILE;
120
121    private VssScmProviderRepository repo;
122
123    private String userDatePattern;
124
125    public VssChangeLogConsumer( VssScmProviderRepository repo, String userDatePattern, ScmLogger logger )
126    {
127        super( logger );
128        this.userDatePattern = userDatePattern;
129        this.repo = repo;
130    }
131
132    public List<ChangeSet> getModifications()
133    {
134        return new ArrayList<ChangeSet>( entries.values() );
135    }
136
137    /** {@inheritDoc} */
138    public void consumeLine( String line )
139    {
140        switch ( getLineStatus( line ) )
141        {
142            case GET_FILE:
143                processGetFile( line );
144                break;
145            case GET_REVISION:
146                processGetRevision( line );
147                break;
148            case GET_AUTHOR:
149                processGetAuthor( line );
150                break;
151            case GET_FILE_PATH:
152                processGetFilePath( line );
153                break;
154            case GET_COMMENT:
155                processGetComment( line );
156                break;
157            default:
158                break;
159        }
160    }
161
162    /**
163     * Process the current input line in the Get Comment state.
164     *
165     * @param line a line of text from the VSS log output
166     */
167    private void processGetComment( String line )
168    {
169        String[] commentLine = line.split( ":" );
170        if ( commentLine.length == 2 )
171        {
172            currentChangeSet.setComment( commentLine[1] );
173        }
174        //Comment suite on a new line
175        else
176        {
177            String comment = currentChangeSet.getComment();
178            comment = comment + " " + line;
179            currentChangeSet.setComment( comment );
180        }
181    }
182
183    /**
184     * Process the current input line in the Get Author state.
185     *
186     * @param line a line of text from the VSS log output
187     */
188    private void processGetAuthor( String line )
189    {
190        String[] result = line.split( "\\s" );
191        Vector<String> vector = new Vector<String>();
192        for ( int i = 0; i < result.length; i++ )
193        {
194            if ( !result[i].equals( "" ) )
195            {
196                vector.add( result[i] );
197            }
198        }
199        currentChangeSet.setAuthor( vector.get( 1 ) );
200        currentChangeSet.setDate(
201            parseDate( vector.get( 3 ) + " " + vector.get( 5 ), userDatePattern, "dd.MM.yy HH:mm" ) );
202    }
203
204    /**
205     * Process the current input line in the Get File state.
206     *
207     * @param line a line of text from the VSS log output
208     */
209    private void processGetFile( String line )
210    {
211        currentChangeSet = ( new ChangeSet() );
212        String[] fileLine = line.split( " " );
213        currentFile = new ChangeFile( fileLine[2] );
214    }
215
216    /**
217     * Process the current input line in the Get File Path state.
218     *
219     * @param line a line of text from the VSS log output
220     */
221    private void processGetFilePath( String line )
222    {
223        if ( currentFile != null )
224        {
225            String fileName = currentFile.getName();
226
227            String path = line.substring( line.indexOf( '$' ), line.length() );
228            String longPath = path.substring( repo.getProject()
229                .length() + 1, path.length() ) + "/" + fileName;
230            currentFile.setName( longPath );
231            addEntry( currentChangeSet, currentFile );
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 VSS log output
239     */
240    private void processGetRevision( String line )
241    {
242        String[] revisionLine = line.split( " " );
243        currentFile.setRevision( revisionLine[1] );
244    }
245
246    /**
247     * Identify the status of a vss history line
248     *
249     * @param line The line to process
250     * @return status
251     */
252    private int getLineStatus( String line )
253    {
254        int argument = GET_UNKNOWN;
255        if ( line.startsWith( START_FILE ) )
256        {
257            argument = GET_FILE;
258        }
259        else if ( line.startsWith( START_REVISION ) )
260        {
261            argument = GET_REVISION;
262        }
263        else if ( line.startsWith( START_AUTHOR ) )
264        {
265            argument = GET_AUTHOR;
266        }
267        else if ( line.indexOf( START_FILE_PATH ) != -1 )
268        {
269            argument = GET_FILE_PATH;
270        }
271        else if ( line.startsWith( START_COMMENT ) )
272        {
273            argument = GET_COMMENT;
274        }
275        else if ( lastStatus == GET_COMMENT )
276        {
277            //Comment suite on a new line
278            argument = lastStatus;
279        }
280        lastStatus = argument;
281
282        return argument;
283    }
284
285    /**
286     * Add a change log entry to the list (if it's not already there) with the
287     * given file.
288     *
289     * @param entry a {@link ChangeSet}to be added to the list if another
290     *              with the same key doesn't exist already. If the entry's author
291     *              is null, the entry wont be added
292     * @param file  a {@link ChangeFile}to be added to the entry
293     */
294    private void addEntry( ChangeSet entry, ChangeFile file )
295    {
296        // do not add if entry is not populated
297        if ( entry.getAuthor() == null )
298        {
299            return;
300        }
301
302        String key = ENTRY_KEY_TIMESTAMP_FORMAT.format( entry.getDate() ) + entry.getAuthor() + entry.getComment();
303
304        if ( !entries.containsKey( key ) )
305        {
306            entry.addFile( file );
307            entries.put( key, entry );
308        }
309        else
310        {
311            ChangeSet existingEntry = (ChangeSet) entries.get( key );
312            existingEntry.addFile( file );
313        }
314    }
315}