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}