/* * The MIT License * * Copyright 2012 Sony Mobile Communications AB. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.sonyericsson.jenkins.plugins.bfa.model; import com.sonyericsson.jenkins.plugins.bfa.model.indication.FoundIndication; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sonyericsson.jenkins.plugins.bfa.model.indication.Indication; /** * Found Failure Cause of a build. * * @author Tomas Westling <tomas.westling@sonymobile.com> */ @ExportedBean public class FoundFailureCause { private static final Logger logger = Logger.getLogger(FoundFailureCause.class.getName()); private final String id; private final String name; private final String description; private final List<String> categories; private List<FoundIndication> indications; /** * Constructor used when converting old failureCauses to foundFailureCauses. * * @param originalCause the original FailureCause. */ public FoundFailureCause(final FailureCause originalCause) { this(originalCause, new LinkedList<FoundIndication>()); } /** * Standard constructor. * * @param originalCause the original FailureCause. * @param indications the indications found that imply this cause. */ public FoundFailureCause(final FailureCause originalCause, final List<FoundIndication> indications) { this.id = originalCause.getId(); this.name = originalCause.getName(); this.categories = originalCause.getCategories(); this.indications = new LinkedList<FoundIndication>(indications); this.description = buildFormattedDescription(originalCause, this.indications, originalCause.getDescription()); } /** * Getter for the id. * * @return the id. */ @Exported public String getId() { return id; } /** * Getter for the name. * * @return the name. */ @Exported public String getName() { return name; } /** * Getter for the description. * * @return the description. */ @Exported public String getDescription() { return description; } /** * Getter for the categories. * * @return the categories. */ @Exported public List<String> getCategories() { return categories; } /** * Getter for the list of found indications. * * @return the list. */ public List<FoundIndication> getIndications() { if (indications == null) { indications = new LinkedList<FoundIndication>(); } return indications; } /** * Adds a found indication to the list. * * @param indication the indication to add. * * @deprecated Prefer adding indications via the constructor. Indication added with this method do not participate * in the building of the formatted description. */ @Deprecated public void addIndication(FoundIndication indication) { indications.add(indication); } /** * Adds a list of FoundIndications to this cause. * * @param foundIndications the list of FoundIndications to add. * * @deprecated Prefer adding indications via the constructor. Indication added with this method do not participate * in the building of the formatted description. */ @Deprecated public void addIndications(List<FoundIndication> foundIndications) { indications.addAll(foundIndications); } /** * Builds the formatted description from build log indication regular expressions. * @param originalCause the original cause of the FoundFailureCause * @param foundIndications the indications found that the FoundFailureCause * @param description the description to be formatted * @return the formatted description */ private static String buildFormattedDescription(final FailureCause originalCause, final List<FoundIndication> foundIndications, final String description) { String formattedDescription = description; if (!foundIndications.isEmpty()) { final FoundIndication firstFoundIndication = foundIndications.get(0); try { // Find the first found indication in the list of original potential cause indications. // The expression index of the first found indication will be used later to determine which // placholders will be used and which placeholders will be removed. if (originalCause != null) { final List<Indication> originalCauseIndications = originalCause.getIndications(); int expressionIndex = 0; boolean foundExpressionIndex = false; for (final int size = originalCauseIndications.size(); expressionIndex < size; ++expressionIndex) { if (originalCauseIndications.get(expressionIndex).getPattern().pattern().equals( firstFoundIndication.getPattern())) { foundExpressionIndex = true; break; } } if (foundExpressionIndex) { final int expressionNumber = expressionIndex + 1; // Convert the "${1,2}" tokens in the description to "$2" formattedDescription = convertFormat(formattedDescription, expressionNumber); // Replace the "$2" tokens with the values from the matched indication. final Pattern contentPattern = Pattern.compile(firstFoundIndication.getPattern()); final Matcher contentMatcher = contentPattern.matcher(firstFoundIndication.getMatchingString()); formattedDescription = contentMatcher.replaceAll(formattedDescription); } } } catch (final Exception exception) { logger.log(Level.SEVERE, null, exception); } } return formattedDescription; } /** * Convert "${i,G}" to "$G" and "${E,G}" to "" while ignoring the escaped form "\${E,G}". * @param input the input string that may contain replacement tokens of the form ${E,G}, where E is the * expression number and G is the captured group within the expression numbered E. * @param expressionNumber the 1-based expression number in a list of expressions that may contain captured groups * @return the input string with replacement tokens replaced by {@code Matcher} group number tokens */ /* package private */ static String convertFormat(final String input, final int expressionNumber) { // Replace the input's "${i,G}" with "$M". e.g., if i == 2, // "Foo ${2,1}${3,1}" becomes "Foo $1${3,1}" // Do not replace \${E,G}. final Pattern expressionPattern = Pattern.compile( "(?<!\\\\)\\$\\{\\s*" + Integer.toString(expressionNumber) + "\\s*,\\s*(\\d+?)\\s*\\}"); final Matcher expressionMatcher = expressionPattern.matcher(input); final String expressionTokensReplaced = expressionMatcher.replaceAll("\\$$1"); // Replace the rest of input's "${E,G}" with "". e.g., // "Foo $1${3,1}" becomes "Foo $1" // Do not replace \${E,G}. final Pattern nonExpressionPattern = Pattern.compile("(?<!\\\\)\\$\\{\\s*\\d+?\\s*,\\s*\\d+?\\s*\\}"); final Matcher nonExpressionMatcher = nonExpressionPattern.matcher(expressionTokensReplaced); final String nonExpressionTokensRemoved = nonExpressionMatcher.replaceAll(""); // Because we ignored \${E,G}, now replace \${E,G} with ${E,G}. final Pattern escapedTokenPattern = Pattern.compile("\\\\(\\$\\{\\s*\\d+?\\s*,\\s*\\d+?\\s*\\})"); final Matcher escapedTokenMatcher = escapedTokenPattern.matcher(nonExpressionTokensRemoved); return escapedTokenMatcher.replaceAll("$1"); } }