package hudson.plugins.analysis.core; // NOPMD 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.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateUtils; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import com.thoughtworks.xstream.XStream; import hudson.XmlFile; import hudson.model.ModelObject; import hudson.model.Result; import hudson.model.AbstractBuild; import hudson.model.Api; import hudson.model.Hudson; import hudson.plugins.analysis.Messages; 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.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.gif"; private static final String FAILED = "red.gif"; private static final String SUCCESS = "blue.gif"; private 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 AbstractBuild<?, ?> 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. */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("Se") private transient WeakReference<JavaProject> project; /** All new warnings in the current build. */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("Se") private transient WeakReference<Collection<FileAnnotation>> newWarningsReference; /** All fixed warnings in the current build. */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("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; /** 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. */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("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; /** * 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 */ public BuildResult(final AbstractBuild<?, ?> build, final String defaultEncoding, final ParserResult result, final BuildHistory history) { initialize(history, build, defaultEncoding, result); } /** * 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 */ public BuildResult(final AbstractBuild<?, ?> build, final String defaultEncoding, final ParserResult result) { initialize(createHistory(build), build, defaultEncoding, result); } /** * Creates a new history based on the specified build. * * @param build * the build to start with * @return the history */ private BuildHistory createHistory(final AbstractBuild<?, ?> build) { return new BuildHistory(build, getResultActionType()); } /** * 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 */ private void initialize(final BuildHistory history, final AbstractBuild<?, ?> 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(); Set<FileAnnotation> allWarnings = result.getAnnotations(); 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); serializeAnnotations(result.getAnnotations()); JavaProject container = new JavaProject(); container.addAnnotations(result.getAnnotations()); project = new WeakReference<JavaProject>(container); computeZeroWarningsHighScore(build, result); } /** * 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 AbstractBuild<?, ?> 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") protected Object readResolve() { 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 */ 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 serialization file. * * @return the serialization file. */ public final XmlFile getDataFile() { return new XmlFile(getXStream(), new File(getOwner().getRootDir(), getSerializationFileName())); } /** * 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().getProject().getLastBuild().number == getOwner().number; } /** * Returns the build as owner of this action. * * @return the owner */ public AbstractBuild<?, ?> getOwner() { return owner; } /** {@inheritDoc} */ public boolean hasAnnotations(final Priority priority) { return getContainer().hasAnnotations(priority); } /** {@inheritDoc} */ public boolean hasAnnotations(final String priority) { return getContainer().hasAnnotations(priority); } /** {@inheritDoc} */ public boolean hasAnnotations() { return numberOfWarnings != 0; } /** {@inheritDoc} */ public boolean hasNoAnnotations() { return numberOfWarnings == 0; } /** {@inheritDoc} */ public boolean hasNoAnnotations(final Priority priority) { return getContainer().hasAnnotations(priority); } /** {@inheritDoc} */ public boolean hasNoAnnotations(final String priority) { return getContainer().hasAnnotations(priority); } /** {@inheritDoc} */ @Exported(name = "warnings") public Set<FileAnnotation> getAnnotations() { return getContainer().getAnnotations(); } /** {@inheritDoc} */ public FileAnnotation getAnnotation(final long key) { return getContainer().getAnnotation(key); } /** {@inheritDoc} */ 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; } /** {@inheritDoc} */ public Set<FileAnnotation> getAnnotations(final Priority priority) { return getContainer().getAnnotations(priority); } /** * Serializes the annotations of the specified project. * * @param annotations * the annotations to store */ private void serializeAnnotations(final Collection<FileAnnotation> annotations) { try { Collection<FileAnnotation> files = annotations; getDataFile().write(files.toArray(new FileAnnotation[files.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 */ 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 */ 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 delta between two builds. * * @return the delta */ @Exported public int getWarningsDelta() { return delta; } /** * 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); LOGGER.log(Level.INFO, "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 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; } /** * Loads the results of the current and reference build, computes the new * warnings and wraps them in a weak reference that might get removed by the * garbage collector. * * @return the new warnings */ private Collection<FileAnnotation> loadNewWarnings() { Collection<FileAnnotation> difference = history.getNewWarnings(getProject().getAnnotations()); newWarningsReference = new WeakReference<Collection<FileAnnotation>>(difference); return difference; } /** * Returns the fixed warnings of this build. * * @return the fixed warnings of this build. */ public Collection<FileAnnotation> getFixedWarnings() { if (fixedWarningsReference == null) { return loadFixedWarnings(); } Collection<FileAnnotation> result = fixedWarningsReference.get(); if (result == null) { return loadFixedWarnings(); } 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> loadFixedWarnings() { 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) { return DetailFactory.create(getResultActionType()).createTrendDetails(link, getOwner(), getContainer(), getFixedWarnings(), getNewWarnings(), getErrors(), getDefaultEncoding(), getDisplayName()); } /** * Returns all possible priorities. * * @return all priorities */ public Priority[] getPriorities() { return Priority.values(); } /** {@inheritDoc} */ public Set<FileAnnotation> getAnnotations(final String priority) { return getContainer().getAnnotations(priority); } /** {@inheritDoc} */ 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; } /** * Sets the Hudson build result for the associated plug-in build result. * * @param result the Hudson build result */ public void setResult(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 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 detail messages for the summary.jelly file. * * @return the summary message */ public String getDetails() { String message = createDeltaMessage(); if (getNumberOfAnnotations() == 0 && getDelta() == 0) { message += createNoWarningsMessage(); message += createHighScoreMessage(); } else if (isSuccessfulTouched()) { message += createListItem(Messages.ResultAction_Status() + getResultIcon()); if (isSuccessful()) { message += createSuccessfulHighScoreMessage(); } } return message; } /** * Returns the icon for the build result. * * @return the icon for the build result */ public String getResultIcon() { String rootUrl = Hudson.getInstance().getRootUrlFromRequest(); String message = "<img src=\"" + rootUrl + "/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()); } } /** * Creates a message that shows the number of warnings since a given build. * * @return a message */ protected String createNoWarningsMessage() { return createListItem(Messages.ResultAction_NoWarningsSince(getZeroWarningsSinceBuild())); } /** * Creates a HTML list item. * * @param text * the item text * @return the HTML item */ protected String createListItem(final String text) { return "<li>" + text + "</li>"; } /** * Returns the build summary HTML message. * * @return the build summary HTML message */ protected String createDeltaMessage() { return StringUtils.EMPTY; } /** * Creates a highscore message. * * @return a highscore message */ protected String createHighScoreMessage() { String message; if (isNewZeroWarningsHighScore()) { long days = getDays(getZeroWarningsHighScore()); if (days == 1) { message = Messages.ResultAction_OneHighScore(); } else { message = Messages.ResultAction_MultipleHighScore(days); } } else { long days = getDays(getHighScoreGap()); if (days == 1) { message = Messages.ResultAction_OneNoHighScore(); } else { message = Messages.ResultAction_MultipleNoHighScore(days); } } return createListItem(message); } /** * Creates a successful highscore message. * * @return a successful highscore message */ protected String createSuccessfulHighScoreMessage() { String message; if (isNewSuccessfulHighScore()) { long days = getDays(getSuccessfulHighScore()); if (days == 1) { message = Messages.ResultAction_SuccessfulOneHighScore(); } else { message = Messages.ResultAction_SuccessfulMultipleHighScore(days); } } else { long days = getDays(getSuccessfulHighScoreGap()); if (days == 1) { message = Messages.ResultAction_SuccessfulOneNoHighScore(); } else { message = Messages.ResultAction_SuccessfulMultipleNoHighScore(days); } } return createListItem(message); } /** {@inheritDoc} */ @Override public String toString() { return getDisplayName() + ": " + getNumberOfAnnotations() + " annotations"; } // Backward compatibility. Do not remove. // CHECKSTYLE:OFF @Deprecated @java.lang.SuppressWarnings("unused") private transient Map<String, MavenModule> emptyModules; // NOPMD @Deprecated @java.lang.SuppressWarnings("PMD") protected transient String low; @Deprecated @java.lang.SuppressWarnings("PMD") protected transient String normal; @Deprecated @java.lang.SuppressWarnings("PMD") protected transient String high; }