package hudson.plugins.analysis.core; // NOPMD
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import com.google.common.collect.Sets;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import com.thoughtworks.xstream.XStream;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jenkins.model.Jenkins;
import hudson.XmlFile;
import hudson.model.AbstractBuild;
import hudson.model.Api;
import hudson.model.ModelObject;
import hudson.model.Result;
import hudson.model.Run;
import hudson.plugins.analysis.Messages;
import hudson.plugins.analysis.util.HtmlPrinter;
import hudson.plugins.analysis.util.PluginLogger;
import hudson.plugins.analysis.util.model.AnnotationContainer;
import hudson.plugins.analysis.util.model.AnnotationProvider;
import hudson.plugins.analysis.util.model.AnnotationStream;
import hudson.plugins.analysis.util.model.AnnotationsLabelProvider;
import hudson.plugins.analysis.util.model.FileAnnotation;
import hudson.plugins.analysis.util.model.JavaProject;
import hudson.plugins.analysis.util.model.MavenModule;
import hudson.plugins.analysis.util.model.Priority;
import hudson.plugins.analysis.views.DetailFactory;
/**
* A base class for build results that is capable of storing a reference to the
* current build. Provides support for persisting the results of the build and
* loading and saving of annotations (all, new, and fixed) and delta
* computation.
*
* @author Ulli Hafner
*/
//CHECKSTYLE:COUPLING-OFF
@ExportedBean
@SuppressWarnings({"PMD.TooManyFields", "PMD.ExcessiveClassLength"})
public abstract class BuildResult implements ModelObject, Serializable, AnnotationProvider {
private static final long serialVersionUID = 1110545450292087475L;
private static final Logger LOGGER = Logger.getLogger(BuildResult.class.getName());
private static final String UNSTABLE = "yellow.png";
private static final String FAILED = "red.png";
private static final String SUCCESS = "blue.png";
private transient Object projectLock = new Object();
/**
* Returns the number of days for the specified number of milliseconds.
*
* @param ms
* milliseconds
* @return the number of days
*/
public static long getDays(final long ms) {
return Math.max(1, ms / DateUtils.MILLIS_PER_DAY);
}
/** Current build as owner of this action. */
private Run<?, ?> owner;
/** All parsed modules. */
private Set<String> modules;
/** The total number of parsed modules (regardless if there are annotations). */
private int numberOfModules;
/** The default encoding to be used when reading and parsing files. */
private String defaultEncoding;
/** The project containing the annotations. */
@SuppressFBWarnings("Se")
private transient WeakReference<JavaProject> project;
/** All new warnings in the current build. */
@SuppressFBWarnings("Se")
private transient WeakReference<Collection<FileAnnotation>> newWarningsReference;
/** All fixed warnings in the current build. */
@SuppressFBWarnings("Se")
private transient WeakReference<Collection<FileAnnotation>> fixedWarningsReference;
/** The build history for the results of this plug-in. */
private transient BuildHistory history;
/** The number of warnings in this build. */
private int numberOfWarnings;
/** The number of new warnings in this build. */
private int numberOfNewWarnings;
/** The number of fixed warnings in this build. */
private int numberOfFixedWarnings;
/** Difference between this and the previous build. */
private int delta;
/** Difference between this and the previous build (Priority low). */
private int lowDelta;
/** Difference between this and the previous build (Priority normal). */
private int normalDelta;
/** Difference between this and the previous build (Priority high). */
private int highDelta;
/** The number of low priority warnings in this build. */
private int lowWarnings;
/** The number of normal priority warnings in this build. */
private int normalWarnings;
/** The number of high priority warnings in this build. */
private int highWarnings;
/** Determines since which build we have zero warnings. */
private int zeroWarningsSinceBuild;
/** Determines since which time we have zero warnings. */
private long zeroWarningsSinceDate;
/** Determines the zero warnings high score. */
private long zeroWarningsHighScore;
/** Determines if the old zero high score has been broken. */
private boolean isZeroWarningsHighscore;
/** Determines the number of msec still to go before a new high score is reached. */
private long highScoreGap;
/** Error messages. */
@SuppressFBWarnings("Se")
private List<String> errors;
/**
* The build result of the associated plug-in. This result is an additional
* state that denotes if this plug-in has changed the overall build result.
*
* @since 1.4
*/
private Result pluginResult = Result.SUCCESS;
/**
* Determines since which build the result is successful.
*
* @since 1.4
*/
private int successfulSinceBuild;
/**
* Determines since which time the result is successful.
*
* @since 1.4
*/
private long successfulSinceDate;
/**
* Determines the succesful build result high score.
*
* @since 1.4
*/
private long successfulHighscore;
/**
* Determines if the old successful build result high score has been broken.
*
* @since 1.4
*/
private boolean isSuccessfulHighscore;
/**
* Determines the number of msec still to go before a new high score is
* reached.
*
* @since 1.4
*/
private long successfulHighScoreGap;
/**
* Determines if this result has touched the successful state.
*
* @since 1.4
*/
private boolean isSuccessfulStateTouched;
private transient boolean useDeltaValues;
private transient Thresholds thresholds = new Thresholds();
/**
* Reference build number. If not defined then 0 or -1 could be used.
*
* @since 1.20
*/
private int referenceBuild;
/**
* Describes the reason for the build result evaluation.
*
* @since 1.38
*/
private String reason;
/**
* Creates a new instance of {@link BuildResult}. Note that the warnings are
* not serialized anymore automatically. You need to call
* {@link #serializeAnnotations(Collection)} manually in your constructor to
* persist them.
*
* @param build
* the current build as owner of this action
* @param history
* build history
* @param result
* the parsed result with all annotations
* @param defaultEncoding
* the default encoding to be used when reading and parsing files
* @since 1.73
*/
protected BuildResult(final Run<?, ?> build, final BuildHistory history,
final ParserResult result, final String defaultEncoding) {
initialize(history, build, defaultEncoding, result);
}
/**
* Creates a new history based on the specified build.
*
* @param build
* the build to start with
* @return the history
*/
protected BuildHistory createHistory(@Nonnull final Run<?, ?> build) {
return new BuildHistory(build, getResultActionType(), false, false);
}
/**
* Determines whether only stable builds should be used as reference builds
* or not.
*
* @return <code>true</code> if only stable builds should be used
*/
public boolean useOnlyStableBuildsAsReference() {
return history.useOnlyStableBuildsAsReference();
}
/**
* Determines whether to always use the previous build as the reference.
*
* @return <code>true</code> if the previous build should always be used.
*/
public boolean usePreviousBuildAsStable() {
return history.usePreviousBuildAsStable();
}
/**
* Initializes this new instance of {@link BuildResult}.
*
* @param build
* the current build as owner of this action
* @param defaultEncoding
* the default encoding to be used when reading and parsing files
* @param result
* the parsed result with all annotations
* @param history
* the history of build results of the associated plug-in
*/
@SuppressWarnings("hiding")
private void initialize(final BuildHistory history, final Run<?, ?> build, final String defaultEncoding, // NOCHECKSTYLE
final ParserResult result) {
this.history = history;
owner = build;
this.defaultEncoding = defaultEncoding;
modules = new HashSet<String>(result.getModules());
numberOfModules = modules.size();
errors = new ArrayList<String>(result.getErrorMessages());
numberOfWarnings = result.getNumberOfAnnotations();
AnnotationContainer referenceResult = history.getReferenceAnnotations();
delta = result.getNumberOfAnnotations() - referenceResult.getNumberOfAnnotations();
lowDelta = computeDelta(result, referenceResult, Priority.LOW);
normalDelta = computeDelta(result, referenceResult, Priority.NORMAL);
highDelta = computeDelta(result, referenceResult, Priority.HIGH);
Set<FileAnnotation> allWarnings = result.getAnnotations();
// FIXME: why is there a flag to enable computation of new warnings?
Set<FileAnnotation> newWarnings = AnnotationDifferencer.getNewAnnotations(allWarnings, referenceResult.getAnnotations());
numberOfNewWarnings = newWarnings.size();
newWarningsReference = new WeakReference<Collection<FileAnnotation>>(newWarnings);
Set<FileAnnotation> fixedWarnings = AnnotationDifferencer.getFixedAnnotations(allWarnings, referenceResult.getAnnotations());
numberOfFixedWarnings = fixedWarnings.size();
fixedWarningsReference = new WeakReference<Collection<FileAnnotation>>(fixedWarnings);
highWarnings = result.getNumberOfAnnotations(Priority.HIGH);
normalWarnings = result.getNumberOfAnnotations(Priority.NORMAL);
lowWarnings = result.getNumberOfAnnotations(Priority.LOW);
JavaProject container = new JavaProject();
container.addAnnotations(result.getAnnotations());
for (FileAnnotation newWarning : newWarnings) {
newWarning.setBuild(build.getNumber());
}
// FIXME: for the old warnings we need to find the introducing build by using the context hash code
project = new WeakReference<JavaProject>(container);
computeZeroWarningsHighScore(build, result);
defineReferenceBuild(history);
}
/**
* Returns the build history.
*
* @return the history
*/
public BuildHistory getHistory() {
return history;
}
@SuppressFBWarnings("NP")
private void defineReferenceBuild(final BuildHistory buildHistory) {
if (buildHistory.hasReferenceBuild()) {
referenceBuild = buildHistory.getReferenceBuild().getNumber();
}
else {
referenceBuild = -1;
}
}
/**
* Returns whether there is a reference build available.
*
* @return <code>true</code> if there is such a build, <code>false</code>
* otherwise
*/
private boolean hasReferenceBuild() {
return referenceBuild > 0 && getReferenceBuild() != null;
}
/**
* Returns the reference build.
* @return the reference build.
*/
@Exported
@WithBridgeMethods(value=AbstractBuild.class, adapterMethod="getReferenceAbstractBuild")
public Run<?, ?> getReferenceBuild() {
return owner.getParent().getBuildByNumber(referenceBuild);
}
/**
* Added for backward compatibility. It generates <pre>AbstractBuild getReferenceBuild()</pre> bytecode during the build
* process, so old implementations can use that signature.
*
* @see {@link WithBridgeMethods}
*/
@Deprecated
private final Object getReferenceAbstractBuild(Run owner, Class targetClass) {
return owner instanceof AbstractBuild ? ((AbstractBuild) owner).getProject().getBuildByNumber(referenceBuild) : null;
}
private int computeDelta(final ParserResult result, final AnnotationContainer referenceResult, final Priority priority) {
return result.getNumberOfAnnotations(priority) - referenceResult.getNumberOfAnnotations(priority);
}
/**
* Computes the zero warnings high score based on the current build and the
* previous build (with results of the associated plug-in).
*
* @param build
* the current build
* @param currentResult
* the current result
*/
private void computeZeroWarningsHighScore(final Run<?, ?> build, final ParserResult currentResult) {
if (history.hasPreviousResult()) {
BuildResult previous = history.getPreviousResult();
if (currentResult.hasNoAnnotations()) {
if (previous.hasNoAnnotations()) {
zeroWarningsSinceBuild = previous.getZeroWarningsSinceBuild();
zeroWarningsSinceDate = previous.getZeroWarningsSinceDate();
}
else {
zeroWarningsSinceBuild = build.getNumber();
zeroWarningsSinceDate = build.getTimestamp().getTimeInMillis();
}
zeroWarningsHighScore = Math.max(previous.getZeroWarningsHighScore(), build.getTimestamp().getTimeInMillis() - zeroWarningsSinceDate);
if (previous.getZeroWarningsHighScore() == 0) {
isZeroWarningsHighscore = true;
}
else {
isZeroWarningsHighscore = zeroWarningsHighScore != previous.getZeroWarningsHighScore();
}
if (!isZeroWarningsHighscore) {
highScoreGap = previous.getZeroWarningsHighScore() - (build.getTimestamp().getTimeInMillis() - zeroWarningsSinceDate);
}
}
else {
zeroWarningsHighScore = previous.getZeroWarningsHighScore();
}
}
else {
if (currentResult.hasNoAnnotations()) {
zeroWarningsSinceBuild = build.getNumber();
zeroWarningsSinceDate = build.getTimestamp().getTimeInMillis();
isZeroWarningsHighscore = true;
zeroWarningsHighScore = 0;
}
}
}
/**
* Returns whether a module with an error is part of this project.
*
* @return <code>true</code> if at least one module has an error.
*/
public boolean hasError() {
return !errors.isEmpty();
}
/**
* Returns the error messages associated with this build.
*
* @return the error messages
*/
public List<String> getErrors() {
return errors;
}
/**
* Initializes members that were not present in previous versions of the
* associated plug-in.
*
* @return the created object
*/
@SuppressWarnings({"PMD", "deprecation"})
protected Object readResolve() {
projectLock = new Object();
if (pluginResult == null) {
pluginResult = Result.SUCCESS;
resetSuccessfulState();
}
if (projectLock == null) {
projectLock = new Object();
}
if (history == null) {
history = createHistory(owner);
}
if (modules == null) {
modules = new HashSet<String>();
}
if (errors == null) {
errors = new ArrayList<String>();
}
try {
if (low != null) {
lowWarnings = Integer.valueOf(low);
}
if (normal != null) {
normalWarnings = Integer.valueOf(normal);
}
if (high != null) {
highWarnings = Integer.valueOf(high);
}
}
catch (NumberFormatException exception) {
// ignore, and start with zero
}
return this;
}
/**
* Returns the modules of this build result.
*
* @return the modules
*/
@Exported
public Collection<String> getModules() {
return modules;
}
/**
* Returns the number of modules in this project.
*
* @return the number of modules
*/
public int getNumberOfModules() {
return numberOfModules;
}
/**
* Returns the defined default encoding.
*
* @return the default encoding
*/
public String getDefaultEncoding() {
return defaultEncoding;
}
/**
* Returns the thresholds used to compute the build health.
*
* @return the thresholds
*/
public Thresholds getThresholds() {
return (Thresholds)ObjectUtils.defaultIfNull(thresholds, new Thresholds());
}
/**
* Returns the whether delta values should be used to compute the new
* warnings.
*
* @return <code>true</code> if the absolute annotations delta should be
* used, <code>false</code> if the actual annotations set difference
* should be used to evaluate the build stability.
*/
public boolean canUseDeltaValues() {
return useDeltaValues;
}
/**
* Returns the serialization file for all warnings.
*
* @return the serialization file.
*/
public final XmlFile getDataFile() {
return new XmlFile(getXStream(), new File(getOwner().getRootDir(), getSerializationFileName()));
}
/**
* Returns the serialization file for the fixed warnings.
*
* @return the serialization file.
*/
private XmlFile getFixedDataFile() {
return new XmlFile(getXStream(), new File(getOwner().getRootDir(),
getSerializationFileName().replace(".xml", "-fixed.xml")));
}
/**
* Returns the {@link XStream} to use.
*
* @return the annotation stream to use
*/
private XStream getXStream() {
AnnotationStream xstream = new AnnotationStream();
configure(xstream);
return xstream;
}
/**
* Configures the {@link XStream}. This default implementation is empty.
*
* @param xstream the stream to configure
*/
protected void configure(final XStream xstream) {
// empty default
}
/**
* Returns the name of the file to store the serialized annotations.
*
* @return the name of the file to store the serialized annotations
*/
protected abstract String getSerializationFileName();
/**
* Returns whether this result belongs to the last build.
*
* @return <code>true</code> if this result belongs to the last build
*/
public boolean isCurrent() {
return getOwner().getParent().getLastBuild().number == getOwner().number;
}
/**
* Returns the build as owner of this action.
*
* @return the owner
*/
@WithBridgeMethods(value=AbstractBuild.class, adapterMethod="getAbstractBuild")
public Run<?, ?> getOwner() {
return owner;
}
/**
* Added for backward compatibility. It generates <pre>AbstractBuild getOwner()</pre> bytecode during the build
* process, so old implementations can use that signature.
*
* @see {@link WithBridgeMethods}
*/
@Deprecated
private Object getAbstractBuild(final Run owner, final Class targetClass) {
return owner instanceof AbstractBuild ? (AbstractBuild) owner : null;
}
@Override
public boolean hasAnnotations(final Priority priority) {
return getContainer().hasAnnotations(priority);
}
@Override
public boolean hasAnnotations(final String priority) {
return getContainer().hasAnnotations(priority);
}
@Override
public boolean hasAnnotations() {
return numberOfWarnings != 0;
}
@Override
public boolean hasNoAnnotations() {
return numberOfWarnings == 0;
}
@Override
public boolean hasNoAnnotations(final Priority priority) {
return getContainer().hasAnnotations(priority);
}
@Override
public boolean hasNoAnnotations(final String priority) {
return getContainer().hasAnnotations(priority);
}
@Override
@Exported(name = "warnings")
public Set<FileAnnotation> getAnnotations() {
return getContainer().getAnnotations();
}
@Override
public FileAnnotation getAnnotation(final long key) {
return getContainer().getAnnotation(key);
}
@Override
public FileAnnotation getAnnotation(final String key) {
return getContainer().getAnnotation(key);
}
/**
* Sets the number of high warnings to the specified value.
*
* @param highWarnings the value to set
*/
protected void setHighWarnings(final int highWarnings) {
this.highWarnings = highWarnings;
}
/**
* Sets the number of normal warnings to the specified value.
*
* @param normalWarnings the value to set
*/
protected void setNormalWarnings(final int normalWarnings) {
this.normalWarnings = normalWarnings;
}
/**
* Sets the number of low warnings to the specified value.
*
* @param lowWarnings the value to set
*/
protected void setLowWarnings(final int lowWarnings) {
this.lowWarnings = lowWarnings;
}
/**
* Sets the number of warnings to the specified value.
*
* @param warnings the value to set
*/
protected void setWarnings(final int warnings) {
numberOfWarnings = warnings;
}
@Override
public Set<FileAnnotation> getAnnotations(final Priority priority) {
return getContainer().getAnnotations(priority);
}
/**
* Serializes the annotations of the specified project and writes them to
* the file specified by method {@link #getDataFile()}.
*
* @param annotations
* the annotations to store
*/
protected void serializeAnnotations(final Collection<FileAnnotation> annotations) {
try {
getDataFile().write(annotations.toArray(new FileAnnotation[annotations.size()]));
Set<FileAnnotation> allAnnotations = new HashSet<FileAnnotation>();
allAnnotations.addAll(annotations);
Collection<FileAnnotation> fixedWarnings = history.getFixedWarnings(allAnnotations);
getFixedDataFile().write(fixedWarnings.toArray(new FileAnnotation[fixedWarnings.size()]));
}
catch (IOException exception) {
LOGGER.log(Level.SEVERE, "Failed to serialize the annotations of the build.", exception);
}
}
/**
* Returns the build since we have zero warnings.
*
* @return the build since we have zero warnings
*/
@Exported
public int getZeroWarningsSinceBuild() {
return zeroWarningsSinceBuild;
}
/**
* Returns the time since we have zero warnings.
*
* @return the time since we have zero warnings
*/
@Exported
public long getZeroWarningsSinceDate() {
return zeroWarningsSinceDate;
}
/**
* Returns the maximum period with zero warnings in a build.
*
* @return the time since we have zero warnings
*/
@Exported
public long getZeroWarningsHighScore() {
return zeroWarningsHighScore;
}
/**
* Returns if the current result reached the old zero warnings highscore.
*
* @return <code>true</code>, if the current result reached the old zero warnings highscore.
*/
@Exported
public boolean isNewZeroWarningsHighScore() {
return isZeroWarningsHighscore;
}
/**
* Returns the number of msec still to go before a new highscore is reached.
*
* @return the number of msec still to go before a new highscore is reached.
*/
public long getHighScoreGap() {
return highScoreGap;
}
/**
* Returns the build since we are successful.
*
* @return the build since we are successful
*/
@Exported
public int getSuccessfulSinceBuild() {
return successfulSinceBuild;
}
/**
* Returns the time since we are successful.
*
* @return the time since we are successful
*/
@Exported
public long getSuccessfulSinceDate() {
return successfulSinceDate;
}
/**
* Returns the maximum period of successful builds.
*
* @return the maximum period of successful builds
*/
@Exported
public long getSuccessfulHighScore() {
return successfulHighscore;
}
/**
* Returns if the current result reached the old successful highscore.
*
* @return <code>true</code>, if the current result reached the old successful highscore.
*/
@Exported
public boolean isNewSuccessfulHighScore() {
return isSuccessfulHighscore;
}
/**
* Returns the number of msec still to go before a new highscore is reached.
*
* @return the number of msec still to go before a new highscore is reached.
*/
public long getSuccessfulHighScoreGap() {
return successfulHighScoreGap;
}
/**
* Gets the number of warnings.
*
* @return the number of warnings
*/
@Exported
public int getNumberOfWarnings() {
return numberOfWarnings;
}
/**
* Gets the number of warnings.
*
* @return the number of warnings
*/
@Override
public int getNumberOfAnnotations() {
return getNumberOfWarnings();
}
/**
* Returns the total number of warnings of the specified priority for
* this object.
*
* @param priority
* the priority
* @return total number of annotations of the specified priority for this
* object
*/
@Override
public int getNumberOfAnnotations(final Priority priority) {
if (priority == Priority.HIGH) {
return highWarnings;
}
else if (priority == Priority.NORMAL) {
return normalWarnings;
}
else {
return lowWarnings;
}
}
/**
* Gets the number of fixed warnings.
*
* @return the number of fixed warnings
*/
@Exported
public int getNumberOfFixedWarnings() {
return numberOfFixedWarnings;
}
/**
* Gets the number of new warnings.
*
* @return the number of new warnings
*/
@Exported
public int getNumberOfNewWarnings() {
return numberOfNewWarnings;
}
/**
* Returns the delta.
*
* @return the delta
*/
public int getDelta() {
return delta;
}
/**
* Returns the high delta.
*
* @return the delta
*/
public int getHighDelta() {
return highDelta;
}
/**
* Returns the normal delta.
*
* @return the delta
*/
public int getNormalDelta() {
return normalDelta;
}
/**
* Returns the low delta.
*
* @return the delta
*/
public int getLowDelta() {
return lowDelta;
}
/**
* Returns the delta between two builds.
*
* @return the delta
*/
@Exported
public int getWarningsDelta() {
return delta;
}
/**
* Returns the number of warnings with high priority.
*
* @return the number of warnings with high priority
*/
@Exported
public int getNumberOfHighPriorityWarnings() {
return highWarnings;
}
/**
* Returns the number of warnings with normal priority.
*
* @return the number of warnings with normal priority
*/
@Exported
public int getNumberOfNormalPriorityWarnings() {
return normalWarnings;
}
/**
* Returns the number of warnings with low priority.
*
* @return the number of warnings with low priority
*/
@Exported
public int getNumberOfLowPriorityWarnings() {
return lowWarnings;
}
/**
* Returns the associated project of this result.
*
* @return the associated project of this result.
*/
public JavaProject getProject() {
synchronized (projectLock) {
if (project == null) {
return loadResult();
}
JavaProject result = project.get();
if (result == null) {
return loadResult();
}
return result;
}
}
/**
* Loads the results and wraps them in a weak reference that might get
* removed by the garbage collector.
*
* @return the loaded result
*/
private JavaProject loadResult() {
JavaProject result;
try {
JavaProject newProject = new JavaProject();
FileAnnotation[] annotations = (FileAnnotation[])getDataFile().read();
newProject.addAnnotations(annotations);
attachLabelProvider(newProject);
LOGGER.log(Level.FINE, "Loaded data file " + getDataFile() + " for build " + getOwner().getNumber());
result = newProject;
}
catch (IOException exception) {
LOGGER.log(Level.WARNING, "Failed to load " + getDataFile(), exception);
result = new JavaProject();
}
project = new WeakReference<JavaProject>(result);
return result;
}
/**
* Returns the default label provider that is used to visualize the build result (i.e., the tab labels).
*
* @return the default label probider
* @since 1.69
*/
protected void attachLabelProvider(final AnnotationContainer container) {
container.setLabelProvider(new AnnotationsLabelProvider(container.getPackageCategoryTitle()));
}
/**
* Returns the new warnings of this build.
*
* @return the new warnings of this build
*/
@Exported
public Collection<FileAnnotation> getNewWarnings() {
if (newWarningsReference == null) {
return loadNewWarnings();
}
Collection<FileAnnotation> result = newWarningsReference.get();
if (result == null) {
return loadNewWarnings();
}
return result;
}
/**
* Filters all warnings by the current build number and wraps them in a weak reference that might get
* removed by the garbage collector.
*
* @return the new warnings
*/
private Collection<FileAnnotation> loadNewWarnings() {
Set<FileAnnotation> newWarnings = new HashSet<FileAnnotation>();
for (FileAnnotation warning : getProject().getAnnotations()) {
if (warning.getBuild() == getOwner().getNumber()) {
newWarnings.add(warning);
}
}
newWarningsReference = new WeakReference<Collection<FileAnnotation>>(newWarnings);
return newWarnings;
}
/**
* Returns the fixed warnings of this build.
*
* @return the fixed warnings of this build.
*/
public Collection<FileAnnotation> getFixedWarnings() {
if (getFixedDataFile().exists()) {
return getFixedWarningsAfterRelease72();
}
else {
return getFixedWarningsBeforeRelease72();
}
}
private Collection<FileAnnotation> getFixedWarningsAfterRelease72() {
synchronized (projectLock) {
if (fixedWarningsReference == null) {
return loadFixedWarningsAfterRelease72();
}
Collection<FileAnnotation> result = fixedWarningsReference.get();
if (result == null) {
return loadFixedWarningsAfterRelease72();
}
return result;
}
}
private Collection<FileAnnotation> loadFixedWarningsAfterRelease72() {
Set<FileAnnotation> fixedWarnings;
try {
FileAnnotation[] annotations = (FileAnnotation[])getFixedDataFile().read();
fixedWarnings = Sets.newHashSet(annotations);
LOGGER.log(Level.FINE, "Loaded data file " + getFixedDataFile() + " for build " + getOwner().getNumber());
}
catch (IOException exception) {
LOGGER.log(Level.WARNING, "Failed to load " + getFixedDataFile(), exception);
fixedWarnings = new HashSet<FileAnnotation>();
}
fixedWarningsReference = new WeakReference<Collection<FileAnnotation>>(fixedWarnings);
return fixedWarnings;
}
private Collection<FileAnnotation> getFixedWarningsBeforeRelease72() {
if (fixedWarningsReference == null) {
return loadFixedWarningsBeforeRelease72();
}
Collection<FileAnnotation> result = fixedWarningsReference.get();
if (result == null) {
return loadFixedWarningsBeforeRelease72();
}
return result;
}
/**
* Loads the results of the current and previous build, computes the fixed
* warnings and wraps them in a weak reference that might get removed by the
* garbage collector.
*
* @return the fixed warnings
*/
private Collection<FileAnnotation> loadFixedWarningsBeforeRelease72() {
Collection<FileAnnotation> difference = history.getFixedWarnings(getProject().getAnnotations());
fixedWarningsReference = new WeakReference<Collection<FileAnnotation>>(difference);
return difference;
}
/**
* Returns the actual type of the associated result action.
*
* @return the actual type of the associated result action
*/
protected abstract Class<? extends ResultAction<? extends BuildResult>> getResultActionType();
/**
* Returns the dynamic result of the selection element.
*
* @param link
* the link to identify the sub page to show
* @param request
* Stapler request
* @param response
* Stapler response
* @return the dynamic result of the analysis (detail page).
*/
public Object getDynamic(final String link, final StaplerRequest request, final StaplerResponse response) {
try {
return DetailFactory.create(getResultActionType()).createTrendDetails(link, getOwner(), getContainer(), getFixedWarnings(),
getNewWarnings(), getErrors(), getDefaultEncoding(), getDisplayName());
}
catch (NoSuchElementException exception) {
try {
response.sendRedirect2("../");
}
catch (IOException e) {
// ignore
}
return this; // fallback on broken URLs
}
}
/**
* Returns all possible priorities.
*
* @return all priorities
*/
public Priority[] getPriorities() {
return Priority.values();
}
@Override
public Set<FileAnnotation> getAnnotations(final String priority) {
return getContainer().getAnnotations(priority);
}
@Override
public int getNumberOfAnnotations(final String priority) {
return getNumberOfAnnotations(Priority.fromString(priority));
}
/**
* Gets the annotation container.
*
* @return the container
*/
public AnnotationContainer getContainer() {
return getProject();
}
/**
* Gets the remote API for this build result.
*
* @return the remote API
*/
public Api getApi() {
return new Api(this);
}
/**
* Returns whether this build is successful with respect to the
* {@link HealthDescriptor} of this result.
*
* @return <code>true</code> if the build is successful, <code>false</code>
* if the build has been set to {@link Result#UNSTABLE} or
* {@link Result#FAILURE} by this result.
*/
public boolean isSuccessful() {
return pluginResult == Result.SUCCESS;
}
/**
* Updates the build status, i.e. sets this plug-in result status field to
* the corresponding {@link Result}. Additionally, the {@link Result} of the
* build that owns this instance of {@link BuildResult} will be also
* changed.
*
* @param thresholds
* the failure thresholds
* @param useDeltaValues
* the use delta values when computing the differences
* @param logger
* the logger
* @param url
* the URL of the results
*/
// CHECKSTYLE:OFF
@SuppressWarnings("hiding")
public void evaluateStatus(final Thresholds thresholds, final boolean useDeltaValues, final PluginLogger logger, final String url) {
evaluateStatus(thresholds, useDeltaValues, true, logger, url);
}
// CHECKSTYLE:ON
/**
* Updates the build status, i.e. sets this plug-in result status field to
* the corresponding {@link Result}. Additionally, the {@link Result} of the
* build that owns this instance of {@link BuildResult} will be also
* changed.
*
* @param thresholds
* the failure thresholds
* @param useDeltaValues
* the use delta values when computing the differences
* @param canComputeNew
* determines whether new warnings should be computed (with
* respect to baseline)
* @param logger
* the logger
* @param url
* the URL of the results
*/
// CHECKSTYLE:OFF
@SuppressWarnings("hiding")
public void evaluateStatus(final Thresholds thresholds, final boolean useDeltaValues, final boolean canComputeNew,
final PluginLogger logger, final String url) {
// CHECKSTYLE:ON
this.thresholds = thresholds;
this.useDeltaValues = useDeltaValues;
BuildResultEvaluator resultEvaluator = new BuildResultEvaluator(url);
Result buildResult;
StringBuilder messages = new StringBuilder();
if (history.isEmpty() || !canComputeNew) {
logger.log("Ignore new warnings since this is the first valid build");
buildResult = resultEvaluator.evaluateBuildResult(messages, thresholds, getAnnotations());
}
else if (useDeltaValues) {
buildResult = resultEvaluator.evaluateBuildResult(messages, thresholds, getAnnotations(),
getDelta(), getHighDelta(), getNormalDelta(), getLowDelta());
}
else {
buildResult = resultEvaluator.evaluateBuildResult(messages, thresholds,
getAnnotations(), getNewWarnings());
}
reason = messages.toString();
saveResult(buildResult);
logger.log(String.format("%s %s - %s", Messages.ResultAction_Status(), buildResult.color.getDescription(), getReason()));
}
// CHECKSTYLE:OFF
/**
* @deprecated use {@link #evaluateStatus(Thresholds, boolean, boolean, PluginLogger, String)}
*/
@SuppressWarnings("javadoc")
@Deprecated
public void setResult(final Result result) {
saveResult(result);
}
// CHECKSTYLE:ON
private void saveResult(final Result result) {
isSuccessfulStateTouched = true;
pluginResult = result;
owner.setResult(result);
if (history.hasPreviousResult()) {
BuildResult previous = history.getPreviousResult();
if (isSuccessful()) {
if (previous.isSuccessful() && previous.isSuccessfulTouched()) {
successfulSinceBuild = previous.getSuccessfulSinceBuild();
successfulSinceDate = previous.getSuccessfulSinceDate();
}
else {
successfulSinceBuild = owner.getNumber();
successfulSinceDate = owner.getTimestamp().getTimeInMillis();
}
successfulHighscore = Math.max(previous.getSuccessfulHighScore(),
owner.getTimestamp().getTimeInMillis() - successfulSinceDate);
if (previous.getSuccessfulHighScore() == 0) {
isSuccessfulHighscore = true;
}
else {
isSuccessfulHighscore = successfulHighscore != previous.getSuccessfulHighScore();
}
if (!isSuccessfulHighscore) {
successfulHighScoreGap = previous.getSuccessfulHighScore()
- (owner.getTimestamp().getTimeInMillis() - successfulSinceDate);
}
}
else {
successfulHighscore = previous.getSuccessfulHighScore();
}
}
else {
if (isSuccessful()) {
resetSuccessfulState();
}
}
}
/**
* Returns the {@link Result} of the plug-in.
*
* @return the plugin result
*/
@Exported
public Result getPluginResult() {
return pluginResult;
}
/**
* Returns whether the successful state has been touched.
*
* @return <code>true</code> if the successful state has been touched,
* <code>false</code> otherwise
*/
public boolean isSuccessfulTouched() {
return isSuccessfulStateTouched;
}
/**
* Returns whether there is a previous result available.
*
* @return <code>true</code> if there is a previous result available
*/
public boolean hasPreviousResult() {
return history.hasPreviousResult();
}
/**
* Returns the previous build result.
*
* @return the previous build result
*/
public BuildResult getPreviousResult() {
return history.getPreviousResult();
}
/**
* Resets the successful high score counters.
*/
private void resetSuccessfulState() {
successfulSinceBuild = owner.getNumber();
successfulSinceDate = owner.getTimestamp().getTimeInMillis();
isSuccessfulHighscore = true;
successfulHighscore = 0;
}
/**
* Returns the reason for the computed value of the build result.
*
* @return the reason
*/
public String getReason() {
return reason;
}
/**
* Creates a default summary message for the build result. Typically, you
* can call this method in {@link #getSummary()} to create the actual
* visible user message.
*
* @param url
* the URL to the build results
* @param warnings
* number of warnings
* @param modules
* number of modules
* @return the summary message
*/
protected static String createDefaultSummary(final String url, final int warnings, final int modules) {
HtmlPrinter summary = new HtmlPrinter();
String message = createWarningsMessage(warnings);
if (warnings > 0) {
summary.append(summary.link(url, message));
}
else {
summary.append(message);
}
if (modules > 0) {
summary.append(" ");
summary.append(createAnalysesMessage(modules));
}
else {
summary.append(".");
}
return summary.toString();
}
private static String createAnalysesMessage(final int modules) {
if (modules == 1) {
return Messages.ResultAction_OneFile();
}
else {
return Messages.ResultAction_MultipleFiles(modules);
}
}
private static String createWarningsMessage(final int warnings) {
if (warnings == 1) {
return Messages.ResultAction_OneWarning();
}
else {
return Messages.ResultAction_MultipleWarnings(warnings);
}
}
/**
* Creates an HTML URL reference start tag.
*
* @param url the URL
* @return the HTML tag
*/
protected static String createUrl(final String url) {
return String.format("<a href=\"%s\">", url);
}
/**
* Creates a default delta message for the build result. Typically, you can
* call this method in {@link #createDeltaMessage()} to create the actual
* visible user message.
*
* @param url
* the URL to the build results
* @param newWarnings
* number of new warnings
* @param fixedWarnings
* number of fixed warnings
* @return the summary message
*/
protected static String createDefaultDeltaMessage(final String url, final int newWarnings, final int fixedWarnings) {
HtmlPrinter summary = new HtmlPrinter();
if (newWarnings > 0) {
summary.append(summary.item(
summary.link(url + "/new", createNewWarningsLinkName(newWarnings))));
}
if (fixedWarnings > 0) {
summary.append(summary.item(
summary.link(url + "/fixed", createFixedWarningsLinkName(fixedWarnings))));
}
return summary.toString();
}
private static String createNewWarningsLinkName(final int newWarnings) {
if (newWarnings == 1) {
return Messages.ResultAction_OneNewWarning();
}
else {
return Messages.ResultAction_MultipleNewWarnings(newWarnings);
}
}
private static String createFixedWarningsLinkName(final int fixedWarnings) {
if (fixedWarnings == 1) {
return Messages.ResultAction_OneFixedWarning();
}
else {
return Messages.ResultAction_MultipleFixedWarnings(fixedWarnings);
}
}
/**
* Returns a summary message for the summary.jelly file.
*
* @return the summary message
*/
public abstract String getSummary();
/**
* Returns the detail messages for the summary.jelly file.
*
* @return the summary message
*/
public String getDetails() {
HtmlPrinter printer = new HtmlPrinter();
printer.append(createDeltaMessage());
if (getNumberOfAnnotations() == 0 && getDelta() == 0) {
printer.append(printer.item(Messages.ResultAction_NoWarningsSince(getZeroWarningsSinceBuild())));
printer.append(printer.item(createHighScoreMessage()));
}
else if (isSuccessfulTouched()) {
printer.append(printer.item(createPluginResultMessage()));
if (isSuccessful()) {
printer.append(printer.item(createSuccessfulHighScoreMessage()));
}
}
return printer.toString();
}
private String createPluginResultMessage() {
return Messages.ResultAction_Status() + getResultIcon() + " - " + getReason() + getReferenceBuildUrl();
}
private String getReferenceBuildUrl() {
HtmlPrinter printer = new HtmlPrinter();
if (hasReferenceBuild()) {
Run<?, ?> build = getReferenceBuild();
printer.append(" ");
printer.append("(");
printer.append(Messages.ReferenceBuild());
printer.append(": ");
printer.append(printer.link(Jenkins.getInstance().getRootUrl() + "/" + build.getUrl(),
build.getDisplayName()));
printer.append(")");
}
return printer.toString();
}
/**
* Returns the icon for the build result.
*
* @return the icon for the build result
*/
public String getResultIcon() {
String message = "<img src=\"" + Stapler.getCurrentRequest().getContextPath() + Jenkins.RESOURCE_PATH
+ "/images/16x16/%s\" alt=\"%s\" title=\"%s\"/>";
if (pluginResult == Result.FAILURE) {
return String.format(message, FAILED,
hudson.model.Messages.BallColor_Failed(), hudson.model.Messages.BallColor_Failed());
}
else if (pluginResult == Result.UNSTABLE) {
return String.format(message, UNSTABLE,
hudson.model.Messages.BallColor_Unstable(), hudson.model.Messages.BallColor_Unstable());
}
else {
return String.format(message, SUCCESS,
hudson.model.Messages.BallColor_Success(), hudson.model.Messages.BallColor_Success());
}
}
/**
* Returns the header for the build result page.
*
* @return the header for the build result page
*/
public String getHeader() {
return StringUtils.EMPTY;
}
/**
* Returns the build summary HTML delta message.
*
* @return the build summary HTML message
*/
protected String createDeltaMessage() {
return StringUtils.EMPTY;
}
private String createHighScoreMessage() {
if (isNewZeroWarningsHighScore()) {
long days = getDays(getZeroWarningsHighScore());
if (days == 1) {
return Messages.ResultAction_OneHighScore();
}
else {
return Messages.ResultAction_MultipleHighScore(days);
}
}
else {
long days = getDays(getHighScoreGap());
if (days == 1) {
return Messages.ResultAction_OneNoHighScore();
}
else {
return Messages.ResultAction_MultipleNoHighScore(days);
}
}
}
private String createSuccessfulHighScoreMessage() {
if (isNewSuccessfulHighScore()) {
long days = getDays(getSuccessfulHighScore());
if (days == 1) {
return Messages.ResultAction_SuccessfulOneHighScore();
}
else {
return Messages.ResultAction_SuccessfulMultipleHighScore(days);
}
}
else {
long days = getDays(getSuccessfulHighScoreGap());
if (days == 1) {
return Messages.ResultAction_SuccessfulOneNoHighScore();
}
else {
return Messages.ResultAction_SuccessfulMultipleNoHighScore(days);
}
}
}
@Override
public String toString() {
return getDisplayName() + " : " + getNumberOfAnnotations() + " annotations";
}
/**
* Creates a new instance of {@link BuildResult}.
*
* @param build
* the current build as owner of this action
* @param defaultEncoding
* the default encoding to be used when reading and parsing files
* @param result
* the parsed result with all annotations
* @param history
* the history of build results of the associated plug-in
* @deprecated use {@link #BuildResult(AbstractBuild, BuildHistory, ParserResult, String)}
* The new constructor will not save the annotations anymore.
* you need to save them manually
*/
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
@Deprecated
public BuildResult(final AbstractBuild<?, ?> build, final String defaultEncoding, final ParserResult result, final BuildHistory history) {
initialize(history, build, defaultEncoding, result);
serializeAnnotations(result.getAnnotations());
}
/**
* Creates a new instance of {@link BuildResult}.
*
* @param build
* the current build as owner of this action
* @param defaultEncoding
* the default encoding to be used when reading and parsing files
* @param result
* the parsed result with all annotations
* @deprecated use {@link #BuildResult(AbstractBuild, BuildHistory, ParserResult, String)}
* The new constructor will not save the annotations anymore.
* you need to save them manually
*/
@Deprecated
@SuppressWarnings({"PMD.ConstructorCallsOverridableMethod", "deprecation"})
public BuildResult(final AbstractBuild<?, ?> build, final String defaultEncoding, final ParserResult result) {
initialize(createHistory(build), build, defaultEncoding, result);
serializeAnnotations(result.getAnnotations());
}
/**
* Creates a new instance of {@link BuildResult}. Note that the warnings are
* not serialized anymore automatically. You need to call
* {@link #serializeAnnotations(Collection)} manually in your constructor to
* persist them.
*
* @param build
* the current build as owner of this action
* @param history
* build history
* @param result
* the parsed result with all annotations
* @param defaultEncoding
* the default encoding to be used when reading and parsing files
* @since 1.39
*/
@Deprecated
protected BuildResult(final AbstractBuild<?, ?> build, final BuildHistory history,
final ParserResult result, final String defaultEncoding) {
initialize(history, build, defaultEncoding, result);
}
/**
* Creates a new history based on the specified build.
*
* @param build
* the build to start with
* @return the history
* @deprecated use {@link #createHistory(Run)} instead
*/
@Deprecated
protected BuildHistory createHistory(final AbstractBuild<?, ?> build) {
return createHistory((Run<?, ?>) build);
}
// Backward compatibility. Do not remove.
// CHECKSTYLE:OFF
@Deprecated
@java.lang.SuppressWarnings("unused")
private transient Map<String, MavenModule> emptyModules; // NOPMD
@Deprecated
@java.lang.SuppressWarnings("all")
protected transient String low;
@Deprecated
@java.lang.SuppressWarnings("all")
protected transient String normal;
@Deprecated
@java.lang.SuppressWarnings("all")
protected transient String high;
}