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