package hudson.plugins.analysis.util.model; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import hudson.model.Item; import hudson.model.AbstractBuild; /** * A base class for annotations. * * @author Ulli Hafner */ @ExportedBean @SuppressWarnings("PMD.CyclomaticComplexity") public abstract class AbstractAnnotation implements FileAnnotation, Serializable { private static final String DEFAULT_PACKAGE = "Default Package"; /** UNIX path separator. */ private static final String SLASH = "/"; /** Temporary directory holding the workspace files. */ public static final String WORKSPACE_FILES = "workspace-files"; /** Unique identifier of this class. */ private static final long serialVersionUID = -1092014926477547148L; /** Current key of this annotation. */ private static long currentKey; /** The message of this annotation. */ private final String message; /** The priority of this annotation. */ private Priority priority; /** Unique key of this annotation. */ private final long key; /** The ordered list of line ranges that show the origin of the annotation in the associated file. */ private final List<LineRange> lineRanges; /** Primary line number of this warning, i.e., the start line of the first line range. */ private final int primaryLineNumber; /** The filename of the class that contains this annotation. */ private String fileName; /** The name of the maven or ant module that contains this annotation. */ private String moduleName; /** The name of the package (or name space) that contains this annotation. */ private String packageName; /** Bug category. */ private final String category; /** Bug type. */ private final String type; /** * Context hash code of this annotation. This hash code is used to decide if * two annotations are equal even if the equals method returns <code>false</code>. */ private long contextHashCode; /** The origin of this warning. */ private String origin; /** Relative path of this duplication. @since 1.10 */ private String pathName; /** * Creates a new instance of <code>AbstractAnnotation</code>. * * @param message * the message of the warning * @param start * the first line of the line range * @param end * the last line of the line range * @param category * the category of the annotation * @param type * the type of the annotation */ public AbstractAnnotation(final String message, final int start, final int end, final String category, final String type) { this.message = StringUtils.strip(message); this.category = StringUtils.defaultString(category); this.type = StringUtils.defaultString(type); key = currentKey++; lineRanges = new ArrayList<LineRange>(); lineRanges.add(new LineRange(start, end)); primaryLineNumber = start; contextHashCode = currentKey; } /** * Creates a new instance of <code>AbstractAnnotation</code>. * * @param priority * the priority * @param message * the message of the warning * @param start * the first line of the line range * @param end * the last line of the line range * @param category * the category of the annotation * @param type * the type of the annotation */ public AbstractAnnotation(final Priority priority, final String message, final int start, final int end, final String category, final String type) { this(message, start, end, category, type); this.priority = priority; } /** * Copy constructor: Creates a new instance of {@link AbstractAnnotation}. * * @param copy * the annotation to copy the values from */ public AbstractAnnotation(final AbstractAnnotation copy) { key = currentKey++; message = copy.getMessage(); priority = copy.getPriority(); primaryLineNumber = copy.getPrimaryLineNumber(); lineRanges = new ArrayList<LineRange>(copy.getLineRanges()); contextHashCode = copy.getContextHashCode(); fileName = copy.getFileName(); category = copy.getCategory(); type = copy.getType(); moduleName = copy.getModuleName(); packageName = copy.getPackageName(); } /** {@inheritDoc} */ public String getLinkName() { if (hasPackageName()) { return getPackageName() + "." + FilenameUtils.getBaseName(getFileName()); } else { if (StringUtils.isBlank(pathName)) { return getFileName(); } else { return pathName + SLASH + getShortFileName(); } } } /** {@inheritDoc} */ public boolean hasPackageName() { String actualPackageName = StringUtils.trim(packageName); return StringUtils.isNotBlank(actualPackageName) && !StringUtils.equals(actualPackageName, "-"); } /** * Sets the pathname for this warning. * * @param workspacePath * the workspace path */ public void setPathName(final String workspacePath) { String normalized = workspacePath.replace('\\', '/'); pathName = StringUtils.removeStart(getFileName(), normalized); pathName = StringUtils.remove(pathName, FilenameUtils.getName(getFileName())); pathName = StringUtils.removeStart(pathName, SLASH); pathName = StringUtils.removeEnd(pathName, SLASH); } /** {@inheritDoc} */ public String getPathName() { return pathName; } /** {@inheritDoc} */ public String getOrigin() { return StringUtils.defaultString(origin); } /** * Sets the origin of this annotation to the specified value. * * @param origin the value to set */ public void setOrigin(final String origin) { this.origin = origin; } /** * Sets the priority to the specified value. * * @param priority the value to set */ public void setPriority(final Priority priority) { this.priority = priority; } /** {@inheritDoc} */ @Exported public String getMessage() { return message; } /** {@inheritDoc} */ @Exported public Priority getPriority() { return priority; } /** {@inheritDoc} */ @Exported public final long getKey() { return key; } /** {@inheritDoc} */ @Exported public final String getFileName() { return fileName; } /** {@inheritDoc} */ public String getTempName(final AbstractBuild<?, ?> owner) { if (fileName != null) { return owner.getRootDir().getAbsolutePath() + SLASH + WORKSPACE_FILES + SLASH + Integer.toHexString(fileName.hashCode()) + ".tmp"; } return StringUtils.EMPTY; } /** {@inheritDoc} */ public String getCategory() { return category; } /** {@inheritDoc} */ public String getType() { return type; } /** * Sets the file name to the specified value. * * @param fileName the value to set */ public final void setFileName(final String fileName) { this.fileName = StringUtils.strip(fileName).replace('\\', '/'); } /** {@inheritDoc} */ public final String getModuleName() { return StringUtils.defaultIfEmpty(moduleName, "Default Module"); } /** * Sets the module name to the specified value. * * @param moduleName the value to set */ public final void setModuleName(final String moduleName) { this.moduleName = moduleName; } /** {@inheritDoc} */ public final String getPackageName() { return StringUtils.defaultIfEmpty(packageName, DEFAULT_PACKAGE); } /** * Sets the package name to the specified value. * * @param packageName the value to set */ public final void setPackageName(final String packageName) { this.packageName = packageName; } /** {@inheritDoc} */ public final Collection<LineRange> getLineRanges() { return Collections.unmodifiableCollection(lineRanges); } /** {@inheritDoc} */ @Exported public final int getPrimaryLineNumber() { return primaryLineNumber; } /** * Adds another line range to this annotation. * * @param lineRange * the line range to add */ public void addLineRange(final LineRange lineRange) { if (!lineRanges.contains(lineRange)) { lineRanges.add(lineRange); } } /** {@inheritDoc} */ public long getContextHashCode() { return contextHashCode; } /** {@inheritDoc} */ public void setContextHashCode(final long contextHashCode) { this.contextHashCode = contextHashCode; } // CHECKSTYLE:OFF @Override @SuppressWarnings("PMD") public int hashCode() { int prime = 31; int result = 1; result = prime * result + ((category == null) ? 0 : category.hashCode()); result = prime * result + ((fileName == null) ? 0 : fileName.hashCode()); result = prime * result + ((lineRanges == null) ? 0 : lineRanges.hashCode()); result = prime * result + ((message == null) ? 0 : message.hashCode()); result = prime * result + ((moduleName == null) ? 0 : moduleName.hashCode()); result = prime * result + ((packageName == null) ? 0 : packageName.hashCode()); result = prime * result + primaryLineNumber; result = prime * result + ((priority == null) ? 0 : priority.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override @SuppressWarnings("PMD") public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } AbstractAnnotation other = (AbstractAnnotation)obj; if (category == null) { if (other.category != null) { return false; } } else if (!category.equals(other.category)) { return false; } if (fileName == null) { if (other.fileName != null) { return false; } } else if (!fileName.equals(other.fileName)) { return false; } if (lineRanges == null) { if (other.lineRanges != null) { return false; } } else if (!lineRanges.equals(other.lineRanges)) { return false; } if (message == null) { if (other.message != null) { return false; } } else if (!message.equals(other.message)) { return false; } if (moduleName == null) { if (other.moduleName != null) { return false; } } else if (!moduleName.equals(other.moduleName)) { return false; } if (packageName == null) { if (other.packageName != null) { return false; } } else if (!packageName.equals(other.packageName)) { return false; } if (primaryLineNumber != other.primaryLineNumber) { return false; } if (priority == null) { if (other.priority != null) { return false; } } else if (!priority.equals(other.priority)) { return false; } if (type == null) { if (other.type != null) { return false; } } else if (!type.equals(other.type)) { return false; } return true; } /** * Gets the associated file name of this bug (without path). * * @return the short file name */ public String getShortFileName() { return FilenameUtils.getName(fileName); } /** * Checks if the file exists and the user is authorized to see the contents of the file. * * @return <code>true</code>, if successful */ public final boolean canDisplayFile(final AbstractBuild<?, ?> owner) { if (owner.hasPermission(Item.WORKSPACE)) { return new File(getFileName()).exists() || new File(getTempName(owner)).exists(); } return false; } /** {@inheritDoc} */ public int compareTo(final FileAnnotation other) { int result; result = getFileName().compareTo(other.getFileName()); if (result != 0) { return result; } result = getPrimaryLineNumber() - other.getPrimaryLineNumber(); if (result != 0) { return result; } return hashCode() - other.hashCode(); // fallback } /** {@inheritDoc} */ @Override public String toString() { return String.format("%s(%s):%s,%s,%s:%s", getFileName(), primaryLineNumber, priority, getCategory(), getType(), getMessage()); } }