001package 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 022import java.util.ArrayList; 023import java.util.List; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026 027import org.apache.maven.scm.ChangeFile; 028import org.apache.maven.scm.ChangeSet; 029import org.apache.maven.scm.ScmException; 030import org.apache.maven.scm.log.ScmLogger; 031import org.apache.maven.scm.util.AbstractConsumer; 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 * 039 */ 040public 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 /** 106 * The regular expression used to match header lines 107 */ 108 private static final Pattern REVISION_PATTERN = Pattern.compile( "^Change (\\d+) " + // changelist number 109 "by (.*)@[^ ]+ " + // author 110 "on (.*)" ); // date 111 /** 112 * The comment section ends with a blank line 113 */ 114 private static final String COMMENT_DELIMITER = ""; 115 /** 116 * The changelist ends with a blank line 117 */ 118 private static final String CHANGELIST_DELIMITER = ""; 119 120 /** 121 * The regular expression used to match file paths 122 */ 123 private static final Pattern FILE_PATTERN = Pattern.compile( "^\\.\\.\\. (.*)#(\\d+) " ); 124 125 public PerforceDescribeConsumer( String repoPath, String userDatePattern, ScmLogger logger ) 126 { 127 super( logger ); 128 129 this.repoPath = repoPath; 130 this.userDatePattern = userDatePattern; 131 } 132 133 // ---------------------------------------------------------------------- 134 // 135 // ---------------------------------------------------------------------- 136 137 public List<ChangeSet> getModifications() throws ScmException 138 { 139 return entries; 140 } 141 142 // ---------------------------------------------------------------------- 143 // StreamConsumer Implementation 144 // ---------------------------------------------------------------------- 145 146 /** {@inheritDoc} */ 147 public void consumeLine( String line ) 148 { 149 switch ( status ) 150 { 151 case GET_REVISION: 152 processGetRevision( line ); 153 break; 154 case GET_COMMENT_BEGIN: 155 status = GET_COMMENT; 156 break; 157 case GET_COMMENT: 158 processGetComment( line ); 159 break; 160 case GET_AFFECTED_FILES: 161 processGetAffectedFiles( line ); 162 break; 163 case GET_FILES_BEGIN: 164 status = GET_FILE; 165 break; 166 case GET_FILE: 167 processGetFile( line ); 168 break; 169 default: 170 throw new IllegalStateException( "Unknown state: " + status ); 171 } 172 } 173 174 // ---------------------------------------------------------------------- 175 // 176 // ---------------------------------------------------------------------- 177 178 /** 179 * Add a change log entry to the list (if it's not already there) 180 * with the given file. 181 * 182 * @param entry a {@link ChangeSet} to be added to the list if another 183 * with the same key (p4 change number) doesn't exist already. 184 * @param file a {@link ChangeFile} to be added to the entry 185 */ 186 private void addEntry( ChangeSet entry, ChangeFile file ) 187 { 188 entry.addFile( file ); 189 } 190 191 /** 192 * Each file matches the fileRegexp. 193 * 194 * @param line A line of text from the Perforce log output 195 */ 196 private void processGetFile( String line ) 197 { 198 if ( line.equals( CHANGELIST_DELIMITER ) ) 199 { 200 entries.add( 0, currentChange ); 201 status = GET_REVISION; 202 return; 203 } 204 205 Matcher matcher = FILE_PATTERN.matcher( line ); 206 if ( !matcher.find() ) 207 { 208 return; 209 } 210 211 currentFile = matcher.group( 1 ); 212 213 // Although Perforce allows files to be submitted anywhere in the 214 // repository in a single changelist, we're only concerned about the 215 // local files. 216 if ( currentFile.startsWith( repoPath ) ) 217 { 218 currentFile = currentFile.substring( repoPath.length() + 1 ); 219 addEntry( currentChange, new ChangeFile( currentFile, matcher.group( 2 ) ) ); 220 } 221 } 222 223 /** 224 * Most of the relevant info is on the revision line matching the 225 * 'pattern' string. 226 * 227 * @param line A line of text from the perforce log output 228 */ 229 private void processGetRevision( String line ) 230 { 231 Matcher matcher = REVISION_PATTERN.matcher( line ); 232 if ( !matcher.find() ) 233 { 234 return; 235 } 236 currentChange = new ChangeSet(); 237 currentRevision = matcher.group( 1 ); 238 currentChange.setAuthor( matcher.group( 2 ) ); 239 currentChange.setDate( matcher.group( 3 ), userDatePattern ); 240 241 status = GET_COMMENT_BEGIN; 242 } 243 244 /** 245 * Process the current input line in the GET_COMMENT state. This 246 * state gathers all of the comments that are part of a log entry. 247 * 248 * @param line a line of text from the perforce log output 249 */ 250 private void processGetComment( String line ) 251 { 252 if ( line.equals( COMMENT_DELIMITER ) ) 253 { 254 status = GET_AFFECTED_FILES; 255 } 256 else 257 { 258 // remove prepended tab 259 currentChange.setComment( currentChange.getComment() + line.substring( 1 ) + "\n" ); 260 } 261 } 262 263 /** 264 * Process the current input line in the GET_COMMENT state. This 265 * state gathers all of the comments that are part of a log entry. 266 * 267 * @param line a line of text from the perforce log output 268 */ 269 private void processGetAffectedFiles( String line ) 270 { 271 if ( !line.equals( "Affected files ..." ) ) 272 { 273 return; 274 } 275 status = GET_FILES_BEGIN; 276 } 277}