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 java.util.ArrayList;
023    import java.util.List;
024    
025    import org.apache.maven.scm.ChangeFile;
026    import org.apache.maven.scm.ChangeSet;
027    import org.apache.maven.scm.ScmException;
028    import org.apache.maven.scm.log.ScmLogger;
029    import org.apache.maven.scm.util.AbstractConsumer;
030    import org.apache.regexp.RE;
031    import org.apache.regexp.RESyntaxException;
032    
033    /**
034     * Parse the tagged output from "p4 describe -s [change] [change] [...]".
035     *
036     * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
037     * @author Olivier Lamy
038     * @version $Id: PerforceDescribeConsumer.java 1054408 2011-01-02 14:05:25Z olamy $
039     */
040    public class PerforceDescribeConsumer
041        extends AbstractConsumer
042    {
043        
044        private List<ChangeSet> entries = new ArrayList<ChangeSet>();
045    
046        /**
047         * State machine constant: expecting revision
048         */
049        private static final int GET_REVISION = 1;
050    
051        /**
052         * State machine constant: eat the first blank line
053         */
054        private static final int GET_COMMENT_BEGIN = 2;
055    
056        /**
057         * State machine constant: expecting comments
058         */
059        private static final int GET_COMMENT = 3;
060    
061        /**
062         * State machine constant: expecting "Affected files"
063         */
064        private static final int GET_AFFECTED_FILES = 4;
065    
066        /**
067         * State machine constant: expecting blank line
068         */
069        private static final int GET_FILES_BEGIN = 5;
070    
071        /**
072         * State machine constant: expecting files
073         */
074        private static final int GET_FILE = 6;
075    
076        /**
077         * Current status of the parser
078         */
079        private int status = GET_REVISION;
080    
081        /**
082         * The current log entry being processed by the parser
083         */
084        @SuppressWarnings( "unused" )
085        private String currentRevision;
086    
087        /**
088         * The current log entry being processed by the parser
089         */
090        private ChangeSet currentChange;
091    
092        /**
093         * the current file being processed by the parser
094         */
095        private String currentFile;
096    
097        /**
098         * The location of files within the Perforce depot that we are processing
099         * e.g. //depot/projects/foo/bar
100         */
101        private String repoPath;
102    
103        private String userDatePattern;
104    
105        private static final String REVISION_PATTERN = "^Change (\\d+) " + // changelist number
106            "by (.*)@[^ ]+ " + // author
107            "on (.*)"; // date
108        /**
109         * The comment section ends with a blank line
110         */
111        private static final String COMMENT_DELIMITER = "";
112        /**
113         * The changelist ends with a blank line
114         */
115        private static final String CHANGELIST_DELIMITER = "";
116    
117        private static final String FILE_PATTERN = "^\\.\\.\\. (.*)#(\\d+) ";
118    
119        /**
120         * The regular expression used to match header lines
121         */
122        private RE revisionRegexp;
123    
124        /**
125         * The regular expression used to match file paths
126         */
127        private RE fileRegexp;
128    
129        public PerforceDescribeConsumer( String repoPath, String userDatePattern, ScmLogger logger )
130        {
131            super( logger );
132    
133            this.repoPath = repoPath;
134            this.userDatePattern = userDatePattern;
135    
136            try
137            {
138                revisionRegexp = new RE( REVISION_PATTERN );
139                fileRegexp = new RE( FILE_PATTERN );
140            }
141            catch ( RESyntaxException ignored )
142            {
143                if ( getLogger().isErrorEnabled() )
144                {
145                    getLogger().error( "Could not create regexps to parse Perforce descriptions", ignored );
146                }
147            }
148        }
149    
150        // ----------------------------------------------------------------------
151        //
152        // ----------------------------------------------------------------------
153    
154        public List<ChangeSet> getModifications() throws ScmException
155        {
156            return entries;
157        }
158    
159        // ----------------------------------------------------------------------
160        // StreamConsumer Implementation
161        // ----------------------------------------------------------------------
162    
163        /** {@inheritDoc} */
164        public void consumeLine( String line )
165        {
166            switch ( status )
167            {
168                case GET_REVISION:
169                    processGetRevision( line );
170                    break;
171                case GET_COMMENT_BEGIN:
172                    status = GET_COMMENT;
173                    break;
174                case GET_COMMENT:
175                    processGetComment( line );
176                    break;
177                case GET_AFFECTED_FILES:
178                    processGetAffectedFiles( line );
179                    break;
180                case GET_FILES_BEGIN:
181                    status = GET_FILE;
182                    break;
183                case GET_FILE:
184                    processGetFile( line );
185                    break;
186                default:
187                    throw new IllegalStateException( "Unknown state: " + status );
188            }
189        }
190    
191        // ----------------------------------------------------------------------
192        //
193        // ----------------------------------------------------------------------
194    
195        /**
196         * Add a change log entry to the list (if it's not already there)
197         * with the given file.
198         *
199         * @param entry a {@link ChangeSet} to be added to the list if another
200         *              with the same key (p4 change number) doesn't exist already.
201         * @param file  a {@link ChangeFile} to be added to the entry
202         */
203        private void addEntry( ChangeSet entry, ChangeFile file )
204        {
205            entry.addFile( file );
206        }
207    
208        /**
209         * Each file matches the fileRegexp.
210         *
211         * @param line A line of text from the Perforce log output
212         */
213        private void processGetFile( String line )
214        {
215            if ( line.equals( CHANGELIST_DELIMITER ) ) {
216                entries.add( 0, currentChange );
217                status = GET_REVISION;
218                return;
219            }
220            if ( !fileRegexp.match( line ) )
221            {
222                return;
223            }
224    
225            currentFile = fileRegexp.getParen( 1 );
226    
227            // Although Perforce allows files to be submitted anywhere in the
228            // repository in a single changelist, we're only concerned about the
229            // local files.
230            if( currentFile.startsWith( repoPath ) ) {
231                currentFile = currentFile.substring( repoPath.length() + 1 );
232                addEntry( currentChange, new ChangeFile( currentFile, fileRegexp.getParen( 2 ) ) );
233            }
234        }
235    
236        /**
237         * Most of the relevant info is on the revision line matching the
238         * 'pattern' string.
239         *
240         * @param line A line of text from the perforce log output
241         */
242        private void processGetRevision( String line )
243        {
244            if ( !revisionRegexp.match( line ) )
245            {
246                return;
247            }
248            currentChange = new ChangeSet();
249            currentRevision = revisionRegexp.getParen( 1 );
250            currentChange.setAuthor( revisionRegexp.getParen( 2 ) );
251            currentChange.setDate( revisionRegexp.getParen( 3 ), userDatePattern );
252    
253            status = GET_COMMENT_BEGIN;
254        }
255    
256        /**
257         * Process the current input line in the GET_COMMENT state.  This
258         * state gathers all of the comments that are part of a log entry.
259         *
260         * @param line a line of text from the perforce log output
261         */
262        private void processGetComment( String line )
263        {
264            if ( line.equals( COMMENT_DELIMITER ) )
265            {
266                status = GET_AFFECTED_FILES;
267            }
268            else
269            {
270                // remove prepended tab
271                currentChange.setComment( currentChange.getComment() + line.substring(1) + "\n" );
272            }
273        }
274    
275        /**
276         * Process the current input line in the GET_COMMENT state.  This
277         * state gathers all of the comments that are part of a log entry.
278         *
279         * @param line a line of text from the perforce log output
280         */
281        private void processGetAffectedFiles( String line )
282        {
283            if ( !line.equals( "Affected files ..." ) )
284            {
285                return;
286            }
287            status = GET_FILES_BEGIN;
288        }
289    }