001 package 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
022 import org.apache.maven.scm.ScmFileStatus;
023 import org.apache.maven.scm.log.ScmLogger;
024 import org.apache.maven.scm.util.AbstractConsumer;
025
026 import java.util.ArrayList;
027 import java.util.HashMap;
028 import java.util.Iterator;
029 import java.util.List;
030 import 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 */
041 public 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 }