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}