001package org.apache.maven.scm.provider.hg.command; 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.ScmFileStatus; 023import org.apache.maven.scm.log.ScmLogger; 024import org.apache.maven.scm.util.AbstractConsumer; 025 026import java.util.ArrayList; 027import java.util.HashMap; 028import java.util.Iterator; 029import java.util.List; 030import java.util.Map; 031 032/** 033 * Base consumer to do common parsing for all hg commands. 034 * <p/> 035 * More specific: log line each line if debug is enabled, get file status 036 * and detect warnings from hg 037 * 038 * @author <a href="mailto:thurner.rupert@ymono.net">thurner rupert</a> 039 * 040 */ 041public class HgConsumer 042 extends AbstractConsumer 043{ 044 045 /** 046 * A list of known keywords from hg 047 */ 048 private static final Map<String, ScmFileStatus> IDENTIFIERS = new HashMap<String, ScmFileStatus>(); 049 050 /** 051 * A list of known message prefixes from hg 052 */ 053 private static final Map<String, String> MESSAGES = new HashMap<String, String>(); 054 055 /** 056 * Number of lines to keep from Std.Err 057 * This size is set to ensure that we capture enough info 058 * but still keeps a low memory footprint. 059 */ 060 private static final int MAX_STDERR_SIZE = 10; 061 062 /** 063 * A list of the MAX_STDERR_SIZE last errors or warnings. 064 */ 065 private final List<String> stderr = new ArrayList<String>(); 066 067 static 068 { 069 /** Statuses from hg add 070 */ 071 IDENTIFIERS.put( "adding", ScmFileStatus.ADDED ); 072 IDENTIFIERS.put( "unknown", ScmFileStatus.UNKNOWN ); 073 IDENTIFIERS.put( "modified", ScmFileStatus.MODIFIED ); 074 IDENTIFIERS.put( "removed", ScmFileStatus.DELETED ); 075 IDENTIFIERS.put( "renamed", ScmFileStatus.MODIFIED ); 076 077 /** Statuses from hg status; 078 */ 079 IDENTIFIERS.put( "A", ScmFileStatus.ADDED ); 080 IDENTIFIERS.put( "?", ScmFileStatus.UNKNOWN ); 081 IDENTIFIERS.put( "M", ScmFileStatus.MODIFIED ); 082 IDENTIFIERS.put( "R", ScmFileStatus.DELETED ); 083 IDENTIFIERS.put( "C", ScmFileStatus.CHECKED_IN ); 084 IDENTIFIERS.put( "!", ScmFileStatus.MISSING ); 085 IDENTIFIERS.put( "I", ScmFileStatus.UNKNOWN ); // not precisely the same, but i think semantics work? - rwd 086 087 MESSAGES.put( "hg: WARNING:", "WARNING" ); 088 MESSAGES.put( "hg: ERROR:", "ERROR" ); 089 MESSAGES.put( "'hg' ", "ERROR" ); // hg isn't found in windows path 090 } 091 092 public HgConsumer( ScmLogger logger ) 093 { 094 super( logger ); 095 } 096 097 public void doConsume( ScmFileStatus status, String trimmedLine ) 098 { 099 //override this 100 } 101 102 /** {@inheritDoc} */ 103 public void consumeLine( String line ) 104 { 105 if ( getLogger().isDebugEnabled() ) 106 { 107 getLogger().debug( line ); 108 } 109 String trimmedLine = line.trim(); 110 111 String statusStr = processInputForKnownIdentifiers( trimmedLine ); 112 113 //If its not a status report - then maybe its a message? 114 if ( statusStr == null ) 115 { 116 boolean isMessage = processInputForKnownMessages( trimmedLine ); 117 //If it is then its already processed and we can ignore futher processing 118 if ( isMessage ) 119 { 120 return; 121 } 122 } 123 else 124 { 125 //Strip away identifier 126 trimmedLine = trimmedLine.substring( statusStr.length() ); 127 trimmedLine = trimmedLine.trim(); //one or more spaces 128 } 129 130 ScmFileStatus status = statusStr != null ? ( (ScmFileStatus) IDENTIFIERS.get( statusStr.intern() ) ) : null; 131 doConsume( status, trimmedLine ); 132 } 133 134 /** 135 * Warnings and errors is usually printed out in Std.Err, thus for derived consumers 136 * operating on Std.Out this would typically return an empty string. 137 * 138 * @return Return the last lines interpreted as an warning or an error 139 */ 140 public String getStdErr() 141 { 142 StringBuilder str = new StringBuilder(); 143 for ( Iterator<String> it = stderr.iterator(); it.hasNext(); ) 144 { 145 str.append( it.next() ); 146 } 147 return str.toString(); 148 } 149 150 private static String processInputForKnownIdentifiers( String line ) 151 { 152 for ( Iterator<String> it = IDENTIFIERS.keySet().iterator(); it.hasNext(); ) 153 { 154 String id = it.next(); 155 if ( line.startsWith( id ) ) 156 { 157 return id; 158 } 159 } 160 return null; 161 } 162 163 private boolean processInputForKnownMessages( String line ) 164 { 165 for ( Iterator<String> it = MESSAGES.keySet().iterator(); it.hasNext(); ) 166 { 167 String prefix = it.next(); 168 if ( line.startsWith( prefix ) ) 169 { 170 stderr.add( line ); //Add line 171 if ( stderr.size() > MAX_STDERR_SIZE ) 172 { 173 stderr.remove( 0 ); //Rotate list 174 } 175 String message = line.substring( prefix.length() ); 176 if ( MESSAGES.get( prefix ).equals( "WARNING" ) ) 177 { 178 if ( getLogger().isWarnEnabled() ) 179 { 180 getLogger().warn( message ); 181 } 182 } 183 else 184 { 185 if ( getLogger().isErrorEnabled() ) 186 { 187 getLogger().error( message ); 188 } 189 } 190 return true; 191 } 192 } 193 return false; 194 } 195}