001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.scm.provider.hg.command;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.maven.scm.ScmFileStatus;
028import org.apache.maven.scm.util.AbstractConsumer;
029
030/**
031 * Base consumer to do common parsing for all hg commands.
032 * <p>
033 * More specific: log line each line if debug is enabled, get file status
034 * and detect warnings from hg
035 *
036 * @author <a href="mailto:thurner.rupert@ymono.net">thurner rupert</a>
037 *
038 */
039public class HgConsumer extends AbstractConsumer {
040
041    /**
042     * A list of known keywords from hg
043     */
044    private static final Map<String, ScmFileStatus> IDENTIFIERS = new HashMap<>();
045
046    /**
047     * A list of known message prefixes from hg
048     */
049    private static final Map<String, String> MESSAGES = new HashMap<>();
050
051    /**
052     * Number of lines to keep from Std.Err
053     * This size is set to ensure that we capture enough info
054     * but still keeps a low memory footprint.
055     */
056    private static final int MAX_STDERR_SIZE = 10;
057
058    /**
059     * A list of the MAX_STDERR_SIZE last errors or warnings.
060     */
061    private final List<String> stderr = new ArrayList<>();
062
063    static {
064        /* Statuses from hg add
065         */
066        IDENTIFIERS.put("adding", ScmFileStatus.ADDED);
067        IDENTIFIERS.put("unknown", ScmFileStatus.UNKNOWN);
068        IDENTIFIERS.put("modified", ScmFileStatus.MODIFIED);
069        IDENTIFIERS.put("removed", ScmFileStatus.DELETED);
070        IDENTIFIERS.put("renamed", ScmFileStatus.MODIFIED);
071
072        /* Statuses from hg status;
073         */
074        IDENTIFIERS.put("A", ScmFileStatus.ADDED);
075        IDENTIFIERS.put("?", ScmFileStatus.UNKNOWN);
076        IDENTIFIERS.put("M", ScmFileStatus.MODIFIED);
077        IDENTIFIERS.put("R", ScmFileStatus.DELETED);
078        IDENTIFIERS.put("C", ScmFileStatus.CHECKED_IN);
079        IDENTIFIERS.put("!", ScmFileStatus.MISSING);
080        IDENTIFIERS.put("I", ScmFileStatus.UNKNOWN); // not precisely the same, but i think semantics work? - rwd
081
082        MESSAGES.put("hg: WARNING:", "WARNING");
083        MESSAGES.put("hg: ERROR:", "ERROR");
084        MESSAGES.put("'hg' ", "ERROR"); // hg isn't found in windows path
085    }
086
087    public void doConsume(ScmFileStatus status, String trimmedLine) {
088        // override this
089    }
090
091    /** {@inheritDoc} */
092    public void consumeLine(String line) {
093        if (logger.isDebugEnabled()) {
094            logger.debug(line);
095        }
096        String trimmedLine = line.trim();
097
098        String statusStr = processInputForKnownIdentifiers(trimmedLine);
099
100        // If its not a status report - then maybe its a message?
101        if (statusStr == null) {
102            boolean isMessage = processInputForKnownMessages(trimmedLine);
103            // If it is then its already processed and we can ignore futher processing
104            if (isMessage) {
105                return;
106            }
107        } else {
108            // Strip away identifier
109            trimmedLine = trimmedLine.substring(statusStr.length());
110            trimmedLine = trimmedLine.trim(); // one or more spaces
111        }
112
113        ScmFileStatus status = statusStr != null ? (IDENTIFIERS.get(statusStr.intern())) : null;
114        doConsume(status, trimmedLine);
115    }
116
117    /**
118     * Warnings and errors is usually printed out in Std.Err, thus for derived consumers
119     * operating on Std.Out this would typically return an empty string.
120     *
121     * @return Return the last lines interpreted as an warning or an error
122     */
123    public String getStdErr() {
124        StringBuilder str = new StringBuilder();
125        for (Iterator<String> it = stderr.iterator(); it.hasNext(); ) {
126            str.append(it.next());
127        }
128        return str.toString();
129    }
130
131    private static String processInputForKnownIdentifiers(String line) {
132        for (Iterator<String> it = IDENTIFIERS.keySet().iterator(); it.hasNext(); ) {
133            String id = it.next();
134            if (line.startsWith(id)) {
135                return id;
136            }
137        }
138        return null;
139    }
140
141    private boolean processInputForKnownMessages(String line) {
142        for (Iterator<String> it = MESSAGES.keySet().iterator(); it.hasNext(); ) {
143            String prefix = it.next();
144            if (line.startsWith(prefix)) {
145                stderr.add(line); // Add line
146                if (stderr.size() > MAX_STDERR_SIZE) {
147                    stderr.remove(0); // Rotate list
148                }
149                String message = line.substring(prefix.length());
150                if (MESSAGES.get(prefix).equals("WARNING")) {
151                    if (logger.isWarnEnabled()) {
152                        logger.warn(message);
153                    }
154                } else {
155                    if (logger.isErrorEnabled()) {
156                        logger.error(message);
157                    }
158                }
159                return true;
160            }
161        }
162        return false;
163    }
164}