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 }