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}