001    package org.apache.maven.scm.provider.perforce.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.ScmException;
025    import org.apache.maven.scm.log.ScmLogger;
026    import org.apache.maven.scm.util.AbstractConsumer;
027    import org.apache.regexp.RE;
028    import org.apache.regexp.RESyntaxException;
029    
030    import java.util.ArrayList;
031    import java.util.Date;
032    import java.util.LinkedHashMap;
033    import java.util.List;
034    import java.util.Map;
035    
036    /**
037     * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
038     * @version $Id: PerforceChangeLogConsumer.java 1057019 2011-01-09 20:13:07Z olamy $
039     */
040    public class PerforceChangeLogConsumer
041        extends AbstractConsumer
042    {
043        /**
044         * Date formatter for perforce timestamp
045         */
046        private static final String PERFORCE_TIMESTAMP_PATTERN = "yyyy/MM/dd HH:mm:ss";
047    
048        private List<ChangeSet> entries = new ArrayList<ChangeSet>();
049    
050        /**
051         * State machine constant: expecting revision and/or file information
052         */
053        private static final int GET_REVISION = 1;
054    
055        /**
056         * State machine constant: eat the first blank line
057         */
058        private static final int GET_COMMENT_BEGIN = 2;
059    
060        /**
061         * State machine constant: expecting comments
062         */
063        private static final int GET_COMMENT = 3;
064    
065        /**
066         * The comment section ends with a blank line
067         */
068        private static final String COMMENT_DELIMITER = "";
069    
070        /**
071         * A file line begins with two slashes
072         */
073        private static final String FILE_BEGIN_TOKEN = "//";
074    
075        /**
076         * Current status of the parser
077         */
078        private int status = GET_REVISION;
079    
080        /**
081         * The current log entry being processed by the parser
082         */
083        private ChangeSet currentChange;
084    
085        /**
086         * the current file being processed by the parser
087         */
088        private String currentFile;
089    
090        /**
091         * The location of files within the Perforce depot that we are processing
092         * e.g. //depot/projects/foo/bar
093         */
094        private String repoPath;
095    
096        /**
097         * The regular expression used to match header lines
098         */
099        private RE revisionRegexp;
100    
101        private Date startDate;
102    
103        private Date endDate;
104    
105        private String userDatePattern;
106    
107        private static final String PATTERN = "^\\.\\.\\. #(\\d+) " + // revision number
108            "change (\\d+) .* " + // changelist number
109            "on (.*) " + // date
110            "by (.*)@"; // author
111    
112        public PerforceChangeLogConsumer( String path, Date startDate, Date endDate, String userDatePattern,
113                                          ScmLogger logger )
114        {
115            super( logger );
116    
117            this.startDate = startDate;
118            this.endDate = endDate;
119            this.userDatePattern = userDatePattern;
120            this.repoPath = path;
121    
122            try
123            {
124                revisionRegexp = new RE( PATTERN );
125            }
126            catch ( RESyntaxException ignored )
127            {
128                if ( getLogger().isErrorEnabled() )
129                {
130                    getLogger().error( "Could not create regexp to parse perforce log file", ignored );
131                }
132            }
133        }
134    
135        // ----------------------------------------------------------------------
136        //
137        // ----------------------------------------------------------------------
138    
139        public List<ChangeSet> getModifications() throws ScmException
140        {
141            
142            // Here there are one entry for each couple (changelist,file). We merge
143            // entries to have only one entry per changelist
144            
145            // Date > ChangeSet
146            Map<Date,ChangeSet> groupedEntries = new LinkedHashMap<Date,ChangeSet>();
147            for ( int i = 0; i < entries.size(); i++ )
148            {
149                ChangeSet cs = (ChangeSet) entries.get( i );
150                ChangeSet hit = (ChangeSet) groupedEntries.get( cs.getDate() );
151                if ( hit != null )
152                {
153                    if ( cs.getFiles().size() != 1 )
154                    {
155                        throw new ScmException( "Merge of entries failed. Bad entry size: " + cs.getFiles().size() );
156                    }
157                    hit.addFile( (ChangeFile) cs.getFiles().get( 0 ) );
158                }
159                else
160                {
161                    groupedEntries.put( cs.getDate(), cs );
162                }
163            }
164    
165            List<ChangeSet> result = new ArrayList<ChangeSet>();
166            result.addAll( groupedEntries.values() );
167    
168            return result;
169            
170        }
171    
172        // ----------------------------------------------------------------------
173        // StreamConsumer Implementation
174        // ----------------------------------------------------------------------
175    
176        /** {@inheritDoc} */
177        public void consumeLine( String line )
178        {
179            switch ( status )
180            {
181                case GET_REVISION:
182                    processGetRevision( line );
183                    break;
184                case GET_COMMENT_BEGIN:
185                    status = GET_COMMENT;
186                    break;
187                case GET_COMMENT:
188                    processGetComment( line );
189                    break;
190                default:
191                    throw new IllegalStateException( "Unknown state: " + status );
192            }
193        }
194    
195        // ----------------------------------------------------------------------
196        //
197        // ----------------------------------------------------------------------
198    
199        /**
200         * Add a change log entry to the list (if it's not already there)
201         * with the given file.
202         *
203         * @param entry a {@link ChangeSet} to be added to the list if another
204         *              with the same key (p4 change number) doesn't exist already.
205         * @param file  a {@link ChangeFile} to be added to the entry
206         */
207        private void addEntry( ChangeSet entry, ChangeFile file )
208        {
209            // ----------------------------------------------------------------------
210            // Check that we are inside the requested date range
211            // ----------------------------------------------------------------------
212    
213            if ( startDate != null && entry.getDate().before( startDate ) )
214            {
215                return;
216            }
217    
218            if ( endDate != null && entry.getDate().after( endDate ) )
219            {
220                return;
221            }
222    
223            // ----------------------------------------------------------------------
224            //
225            // ----------------------------------------------------------------------
226    
227            entry.addFile( file );
228    
229            entries.add( entry );
230        }
231    
232        /**
233         * Most of the relevant info is on the revision line matching the
234         * 'pattern' string.
235         *
236         * @param line A line of text from the perforce log output
237         */
238        private void processGetRevision( String line )
239        {
240            if ( line.startsWith( FILE_BEGIN_TOKEN ) )
241            {
242                currentFile = line.substring( repoPath.length() + 1 );
243                return;
244            }
245    
246            if ( !revisionRegexp.match( line ) )
247            {
248                return;
249            }
250    
251            currentChange = new ChangeSet();
252            currentChange.setDate( parseDate( revisionRegexp.getParen( 3 ), userDatePattern, PERFORCE_TIMESTAMP_PATTERN ) );
253            currentChange.setAuthor( revisionRegexp.getParen( 4 ) );
254    
255            status = GET_COMMENT_BEGIN;
256        }
257    
258        /**
259         * Process the current input line in the GET_COMMENT state.  This
260         * state gathers all of the comments that are part of a log entry.
261         *
262         * @param line a line of text from the perforce log output
263         */
264        private void processGetComment( String line )
265        {
266            if ( line.equals( COMMENT_DELIMITER ) )
267            {
268                addEntry( currentChange, new ChangeFile( currentFile, revisionRegexp.getParen( 1 ) ) );
269    
270                status = GET_REVISION;
271            }
272            else
273            {
274                currentChange.setComment( currentChange.getComment() + line + "\n" );
275            }
276        }
277    }