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     * @version $Id: HgConsumer.java 1057017 2011-01-09 20:11:55Z olamy $
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    }