package hudson.plugins.analysis.util.model; // NOPMD
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import org.apache.commons.lang.StringUtils;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import hudson.plugins.analysis.Messages;
/**
* A container for annotations.
*
* @author Ulli Hafner
*/
public abstract class AnnotationContainer implements AnnotationProvider, Serializable, Comparable<AnnotationContainer> {
/** Unique identifier of this class. */
private static final long serialVersionUID = 855696821788264261L;
/** The hierarchy of a container. */
public enum Hierarchy {
/** Project level. */
PROJECT,
/** Module level. */
MODULE,
/** Package level. */
PACKAGE,
/** File level. */
FILE}
/** The annotations mapped by their key. */
@SuppressWarnings("Se")
private final Map<Long, FileAnnotation> annotations = new HashMap<Long, FileAnnotation>();
/** The annotations mapped by priority. */
private transient Map<Priority, Set<FileAnnotation>> annotationsByPriority;
/** The annotations mapped by category. */
private transient Map<String, Set<FileAnnotation>> annotationsByCategory;
/** The annotations mapped by type. */
private transient Map<String, Set<FileAnnotation>> annotationsByType;
/** The files that contain annotations mapped by file name. */
private transient Map<String, WorkspaceFile> filesByName;
/** The packages that contain annotations mapped by package name. */
private transient Map<String, JavaPackage> packagesByName;
/** The modules that contain annotations mapped by module name. */
private transient Map<String, MavenModule> modulesByName;
/** The files that contain annotations mapped by hash code of file name. */
private transient Map<Integer, WorkspaceFile> filesByHashCode;
/** The packages that contain annotations mapped by hash code of package name. */
private transient Map<Integer, JavaPackage> packagesByHashCode;
/** The modules that contain annotations mapped by hash code of module name. */
private transient Map<Integer, MavenModule> modulesByHashCode;
/** The modules that contain annotations mapped by hash code of category name. */
private transient Map<Integer, Set<FileAnnotation>> categoriesByHashCode;
/** The modules that contain annotations mapped by hash code of type name. */
private transient Map<Integer, Set<FileAnnotation>> typesByHashCode;
/** Determines whether to build up a set of {@link WorkspaceFile}s. */
@java.lang.SuppressWarnings("unused")
private boolean handleFiles; // backward compatibility NOPMD
/** Name of this container. */
private String name;
/** Hierarchy level of this container. */
private Hierarchy hierarchy;
/**
* Creates a new instance of <code>AnnotationContainer</code>.
*
* @param hierarchy the hierarchy of this container
*/
public AnnotationContainer(final Hierarchy hierarchy) {
this(StringUtils.EMPTY, hierarchy);
}
/**
* Returns this container.
*
* @return this container
*/
public AnnotationContainer getContainer() {
return this;
}
/**
* Gets the maximum number of annotations within the specified containers.
*
* @param containers
* the containers to scan for the upper bound
* @return the maximum number of annotations
*/
public int getUpperBound(final Collection<? extends AnnotationContainer> containers) {
int maximum = 0;
for (AnnotationContainer container : containers) {
maximum = Math.max(maximum, container.getNumberOfAnnotations());
}
return maximum;
}
/**
* Creates a new instance of <code>AnnotationContainer</code>.
*
* @param name the name of this container
* @param hierarchy the hierarchy of this container
*/
protected AnnotationContainer(final String name, final Hierarchy hierarchy) {
initialize();
this.name = name;
this.hierarchy = hierarchy;
}
/**
* Sets the hierarchy to the specified value.
*
* @param hierarchy the value to set
*/
protected void setHierarchy(final Hierarchy hierarchy) {
this.hierarchy = hierarchy;
}
/**
* Returns the name of this container.
*
* @return the name of this container
*/
public final String getName() {
return name;
}
/**
* Sets the name of this container.
*
* @param name the name of this container
*/
public final void setName(final String name) {
this.name = name;
}
/**
* Initializes the transient mappings.
*/
private void initialize() {
annotationsByPriority = new EnumMap<Priority, Set<FileAnnotation>>(Priority.class);
for (Priority priority : Priority.values()) {
annotationsByPriority.put(priority, new HashSet<FileAnnotation>());
}
annotationsByCategory = new HashMap<String, Set<FileAnnotation>>();
annotationsByType = new HashMap<String, Set<FileAnnotation>>();
filesByName = new HashMap<String, WorkspaceFile>();
packagesByName = new HashMap<String, JavaPackage>();
modulesByName = new HashMap<String, MavenModule>();
filesByHashCode = new HashMap<Integer, WorkspaceFile>();
packagesByHashCode = new HashMap<Integer, JavaPackage>();
modulesByHashCode = new HashMap<Integer, MavenModule>();
categoriesByHashCode = new HashMap<Integer, Set<FileAnnotation>>();
typesByHashCode = new HashMap<Integer, Set<FileAnnotation>>();
}
/**
* Rebuilds the priorities mapping.
*
* @return the created object
*/
@SuppressWarnings("Se")
private Object readResolve() {
rebuildMappings();
return this;
}
/**
* Rebuilds the priorities and files after deserialization.
*/
protected void rebuildMappings() {
initialize();
for (FileAnnotation annotation : getAnnotations()) {
updateMappings(annotation);
}
}
/**
* Updates the annotation drill-down mappings (priority, packages, files) with the specified annotation.
*
* @param annotation the new annotation
*/
private void updateMappings(final FileAnnotation annotation) {
annotationsByPriority.get(annotation.getPriority()).add(annotation);
if (StringUtils.isNotBlank(annotation.getCategory())) {
addCategory(annotation);
}
if (StringUtils.isNotBlank(annotation.getType())) {
addType(annotation);
}
if (hierarchy == Hierarchy.PROJECT) {
addModule(annotation);
}
if (hierarchy == Hierarchy.PROJECT || hierarchy == Hierarchy.MODULE) {
addPackage(annotation);
}
if (hierarchy == Hierarchy.PROJECT || hierarchy == Hierarchy.MODULE || hierarchy == Hierarchy.PACKAGE) {
addFile(annotation);
}
}
/**
* Adds a new category to this container that will contain the specified
* annotation. If the category already exists, then the annotation is only added
* to this category.
*
* @param annotation the new annotation
*/
private void addCategory(final FileAnnotation annotation) {
String category = annotation.getCategory();
if (!annotationsByCategory.containsKey(category)) {
HashSet<FileAnnotation> container = new HashSet<FileAnnotation>();
annotationsByCategory.put(category, container);
categoriesByHashCode.put(category.hashCode(), container);
}
annotationsByCategory.get(category).add(annotation);
}
/**
* Adds a new type to this container that will contain the specified
* annotation. If the type already exists, then the annotation is only added
* to this type.
*
* @param annotation the new annotation
*/
private void addType(final FileAnnotation annotation) {
String type = annotation.getType();
if (!annotationsByType.containsKey(type)) {
HashSet<FileAnnotation> container = new HashSet<FileAnnotation>();
annotationsByType.put(type, container);
typesByHashCode.put(type.hashCode(), container);
}
annotationsByType.get(type).add(annotation);
}
/**
* Adds a new module to this container that will contain the specified
* annotation. If the module already exists, then the annotation is only added
* to this module.
*
* @param annotation the new annotation
*/
private void addModule(final FileAnnotation annotation) {
String moduleName = annotation.getModuleName();
if (!modulesByName.containsKey(moduleName)) {
MavenModule module = new MavenModule(moduleName);
modulesByName.put(moduleName, module);
modulesByHashCode.put(moduleName.hashCode(), module);
}
modulesByName.get(moduleName).addAnnotation(annotation);
}
/**
* Adds a new package to this container that will contain the specified
* annotation. If the package already exists, then the annotation is only added
* to this package.
*
* @param annotation the new annotation
*/
private void addPackage(final FileAnnotation annotation) {
String packageName;
if (annotation.hasPackageName()) {
packageName = annotation.getPackageName();
}
else {
packageName = annotation.getPathName();
}
if (StringUtils.isBlank(packageName)) {
packageName = "-";
}
if (!packagesByName.containsKey(packageName)) {
JavaPackage javaPackage = new JavaPackage(packageName);
packagesByName.put(packageName, javaPackage);
packagesByHashCode.put(packageName.hashCode(), javaPackage);
}
packagesByName.get(packageName).addAnnotation(annotation);
}
/**
* Adds a new file to this container that will contain the specified
* annotation. If the file already exists, then the annotation is only added
* to this class.
*
* @param annotation the new annotation
*/
private void addFile(final FileAnnotation annotation) {
String fileName = annotation.getFileName();
if (!filesByName.containsKey(fileName)) {
WorkspaceFile file = new WorkspaceFile(fileName);
filesByName.put(fileName, file);
filesByHashCode.put(file.getName().hashCode(), file);
}
filesByName.get(fileName).addAnnotation(annotation);
}
/**
* Adds the specified annotation to this container.
*
* @param annotation the annotation to add
*/
public final void addAnnotation(final FileAnnotation annotation) {
annotations.put(annotation.getKey(), annotation);
updateMappings(annotation);
}
/**
* Adds the specified annotations to this container.
*
* @param newAnnotations the annotations to add
*/
public final void addAnnotations(final Collection<? extends FileAnnotation> newAnnotations) {
for (FileAnnotation annotation : newAnnotations) {
addAnnotation(annotation);
}
}
/**
* Adds the specified annotations to this container.
*
* @param newAnnotations the annotations to add
*/
public final void addAnnotations(final FileAnnotation[] newAnnotations) {
addAnnotations(Arrays.asList(newAnnotations));
}
/**
* Returns a sorted set of the annotations.
*
* @return a sorted set of the annotations
*/
public final SortedSet<FileAnnotation> getSortedAnnotations() {
return ImmutableSortedSet.copyOf(annotations.values());
}
/** {@inheritDoc} */
public final Set<FileAnnotation> getAnnotations() {
return ImmutableSet.copyOf(annotations.values());
}
/** {@inheritDoc} */
public final Set<FileAnnotation> getAnnotations(final Priority priority) {
return ImmutableSortedSet.copyOf(annotationsByPriority.get(priority));
}
/**
* Returns the annotations with {@link Priority#HIGH}.
*
* @return the annotations with {@link Priority#HIGH}
*/
public final Set<FileAnnotation> getHighAnnotations() {
return getAnnotations(Priority.HIGH);
}
/**
* Returns the annotations with {@link Priority#NORMAL}.
*
* @return the annotations with {@link Priority#NORMAL}
*/
public final Set<FileAnnotation> getNormalAnnotations() {
return getAnnotations(Priority.NORMAL);
}
/**
* Returns the annotations with {@link Priority#LOW}.
*
* @return the annotations with {@link Priority#LOW}
*/
public final Set<FileAnnotation> getLowAnnotations() {
return getAnnotations(Priority.LOW);
}
/** {@inheritDoc} */
public final Set<FileAnnotation> getAnnotations(final String priority) {
return getAnnotations(getPriority(priority));
}
/**
* Converts a String priority to an actual enumeration value.
*
* @param priority priority as a String
*
* @return enumeration value.
*/
private Priority getPriority(final String priority) {
return Priority.fromString(priority);
}
/** {@inheritDoc} */
public int getNumberOfAnnotations() {
return annotations.size();
}
/**
* Gets the number of annotations with priority low.
*
* @return the number of annotations with priority low
*/
public int getNumberOfLowAnnotations() {
return getLowAnnotations().size();
}
/**
* Gets the number of annotations with priority normal.
*
* @return the number of annotations with priority normal
*/
public int getNumberOfNormalAnnotations() {
return getNormalAnnotations().size();
}
/**
* Gets the number of annotations with priority high.
*
* @return the number of annotations with priority high
*/
public int getNumberOfHighAnnotations() {
return getHighAnnotations().size();
}
/** {@inheritDoc} */
public int getNumberOfAnnotations(final Priority priority) {
return annotationsByPriority.get(priority).size();
}
/** {@inheritDoc} */
public final int getNumberOfAnnotations(final String priority) {
return getNumberOfAnnotations(getPriority(priority));
}
/** {@inheritDoc} */
public final boolean hasAnnotations() {
return !hasNoAnnotations();
}
/** {@inheritDoc} */
public final boolean hasAnnotations(final Priority priority) {
return !hasNoAnnotations(priority);
}
/** {@inheritDoc} */
public final boolean hasAnnotations(final String priority) {
return !hasNoAnnotations(priority);
}
/** {@inheritDoc} */
public final boolean hasNoAnnotations() {
return annotations.isEmpty();
}
/** {@inheritDoc} */
public final boolean hasNoAnnotations(final Priority priority) {
return annotationsByPriority.get(priority).isEmpty();
}
/** {@inheritDoc} */
public final boolean hasNoAnnotations(final String priority) {
return hasNoAnnotations(getPriority(priority));
}
/** {@inheritDoc} */
public final FileAnnotation getAnnotation(final long key) {
FileAnnotation annotation = annotations.get(key);
if (annotation != null) {
return annotation;
}
throw new NoSuchElementException("Annotation not found: key=" + key);
}
/** {@inheritDoc} */
public final FileAnnotation getAnnotation(final String key) {
return getAnnotation(Long.parseLong(key));
}
/**
* Returns a tooltip showing the distribution of priorities for this container.
*
* @return a tooltip showing the distribution of priorities
*/
public String getToolTip() {
StringBuilder message = new StringBuilder();
String separator = " - ";
for (Priority priority : Priority.values()) {
if (hasAnnotations(priority)) {
message.append(priority.getLocalizedString());
message.append(":");
message.append(getNumberOfAnnotations(priority));
message.append(separator);
}
}
return StringUtils.removeEnd(message.toString(), separator);
}
/**
* Returns the package category name for the scanned files. Currently, only
* java and c# files are supported.
*
* @return the package category name for the scanned files
*/
public final String getPackageCategoryName() {
if (hasAnnotations()) {
FileAnnotation annotation = getAnnotations().iterator().next();
String fileName = annotation.getFileName();
if (fileName.endsWith(".cs")) {
return Messages.NamespaceDetail_header();
}
if (annotation.hasPackageName()) {
return Messages.PackageDetail_header();
}
else {
return Messages.PathDetail_header();
}
}
return Messages.PackageDetail_header();
}
/**
* Gets the modules of this container that have annotations.
*
* @return the modules with annotations
*/
public Collection<MavenModule> getModules() {
ArrayList<MavenModule> modules = new ArrayList<MavenModule>(modulesByName.values());
Collections.sort(modules);
return Collections.unmodifiableCollection(modules);
}
/**
* Returns whether the maven module with the given name exists.
*
* @param moduleName the module to check for
*
* @return <code>true</code> if the maven module with the given name
* exists, <code>false</code> otherwise
*/
public boolean containsModule(final String moduleName) {
return modulesByName.containsKey(moduleName);
}
/**
* Gets the module with the given name.
*
* @param moduleName the name of the module
*
* @return the module with the given name
*/
public MavenModule getModule(final String moduleName) {
if (modulesByName.containsKey(moduleName)) {
return modulesByName.get(moduleName);
}
throw new NoSuchElementException("Module not found: " + moduleName);
}
/**
* Gets the module with the given hash code.
*
* @param hashCode the hash code of the module
*
* @return the module with the given name
*/
public MavenModule getModule(final int hashCode) {
if (modulesByHashCode.containsKey(hashCode)) {
return modulesByHashCode.get(hashCode);
}
throw new NoSuchElementException("Module by hashcode not found: " + hashCode);
}
/**
* Gets the packages of this container that have annotations.
*
* @return the packages with annotations
*/
public Collection<JavaPackage> getPackages() {
ArrayList<JavaPackage> packages = new ArrayList<JavaPackage>(packagesByName.values());
Collections.sort(packages);
return Collections.unmodifiableCollection(packages);
}
/**
* Returns whether the package with the given name exists.
*
* @param packageName the package to check for
*
* @return <code>true</code> if the package with the given name
* exists, <code>false</code> otherwise
*/
public boolean containsPackage(final String packageName) {
return packagesByName.containsKey(packageName);
}
/**
* Gets the package with the given name.
*
* @param packageName the name of the package
*
* @return the file with the given name
*/
public JavaPackage getPackage(final String packageName) {
if (packagesByName.containsKey(packageName)) {
return packagesByName.get(packageName);
}
throw new NoSuchElementException("Package not found: " + packageName);
}
/**
* Gets the package with the given hash code.
*
* @param hashCode the hash code of the package
*
* @return the package with the given name
*/
public JavaPackage getPackage(final int hashCode) {
if (packagesByHashCode.containsKey(hashCode)) {
return packagesByHashCode.get(hashCode);
}
throw new NoSuchElementException("Package by hashcode not found: " + hashCode);
}
/**
* Gets the files of this container that have annotations.
*
* @return the files with annotations
*/
public Collection<WorkspaceFile> getFiles() {
ArrayList<WorkspaceFile> files = new ArrayList<WorkspaceFile>(filesByName.values());
Collections.sort(files);
return Collections.unmodifiableCollection(files);
}
/**
* Returns whether the file with the given name exists.
*
* @param fileName the file to check for
*
* @return <code>true</code> if the file with the given name
* exists, <code>false</code> otherwise
*/
public boolean containsFile(final String fileName) {
return filesByName.containsKey(fileName);
}
/**
* Gets the file with the given name.
*
* @param fileName the short name of the file
*
* @return the file with the given name
*/
public WorkspaceFile getFile(final String fileName) {
if (filesByName.containsKey(fileName)) {
return filesByName.get(fileName);
}
throw new NoSuchElementException("File not found: " + fileName);
}
/**
* Gets the file with the given hash code.
*
* @param hashCode the hash code of the file
*
* @return the file with the given name
*/
public WorkspaceFile getFile(final int hashCode) {
if (filesByHashCode.containsKey(hashCode)) {
return filesByHashCode.get(hashCode);
}
throw new NoSuchElementException("File by hashcode not found: " + hashCode);
}
/**
* Gets the categories of this container that have annotations.
*
* @return the categories with annotations
*/
public Collection<AnnotationContainer> getCategories() {
ArrayList<AnnotationContainer> categories = new ArrayList<AnnotationContainer>();
for (String category : annotationsByCategory.keySet()) {
categories.add(getCategory(category));
}
Collections.sort(categories);
return categories;
}
/**
* Returns whether the category with the given name exists.
*
* @param category the file to check for
*
* @return <code>true</code> if the category with the given name
* exists, <code>false</code> otherwise
*/
public boolean containsCategory(final String category) {
return annotationsByCategory.containsKey(category);
}
/**
* Gets the category with the given name.
*
* @param category the category name
* @return the category with the given name
*/
public DefaultAnnotationContainer getCategory(final String category) {
if (annotationsByCategory.containsKey(category)) {
return new DefaultAnnotationContainer(category, annotationsByCategory.get(category));
}
throw new NoSuchElementException("Category not found: " + category);
}
/**
* Gets the category with the given hash code.
*
* @param hashCode the category hash code
* @return the category with the given hash code
*/
public DefaultAnnotationContainer getCategory(final int hashCode) {
if (categoriesByHashCode.containsKey(hashCode)) {
Set<FileAnnotation> container = categoriesByHashCode.get(hashCode);
FileAnnotation fileAnnotation = container.iterator().next();
return new DefaultAnnotationContainer(fileAnnotation.getCategory(), container);
}
throw new NoSuchElementException("Category by hashCode not found: " + hashCode);
}
/**
* Gets the types of this container that have annotations.
*
* @return the types with annotations
*/
public Collection<AnnotationContainer> getTypes() {
ArrayList<AnnotationContainer> types = new ArrayList<AnnotationContainer>();
for (String type : annotationsByType.keySet()) {
types.add(getType(type));
}
Collections.sort(types);
return types;
}
/**
* Returns whether the type with the given name exists.
*
* @param type the type to check for
*
* @return <code>true</code> if the type with the given name
* exists, <code>false</code> otherwise
*/
public boolean containsType(final String type) {
return annotationsByType.containsKey(type);
}
/**
* Gets the type with the given name.
*
* @param type the type name
* @return the type with the given name
*/
public DefaultAnnotationContainer getType(final String type) {
if (annotationsByType.containsKey(type)) {
return new DefaultAnnotationContainer(type, annotationsByType.get(type));
}
throw new NoSuchElementException("Type not found: " + type);
}
/**
* Gets the type with the given hash code.
*
* @param hashCode the type hash code
* @return the type with the given hash code
*/
public DefaultAnnotationContainer getType(final int hashCode) {
if (typesByHashCode.containsKey(hashCode)) {
Set<FileAnnotation> container = typesByHashCode.get(hashCode);
FileAnnotation fileAnnotation = container.iterator().next();
return new DefaultAnnotationContainer(fileAnnotation.getType(), container);
}
throw new NoSuchElementException("Type by hashcode not found: " + hashCode);
}
/**
* Returns {@link Priority#HIGH}.
*
* @return {@link Priority#HIGH}
*/
public Priority getHighPriority() {
return Priority.HIGH;
}
/**
* Returns {@link Priority#NORMAL}.
*
* @return {@link Priority#NORMAL}
*/
public Priority getNormalPriority() {
return Priority.NORMAL;
}
/**
* Returns {@link Priority#LOW}.
*
* @return {@link Priority#LOW}
*/
public Priority getLowPriority() {
return Priority.LOW;
}
/** {@inheritDoc} */
public int compareTo(final AnnotationContainer other) {
return getName().compareTo(other.getName());
}
/** {@inheritDoc} */
@Override
public int hashCode() {
int prime = 31; // NOCHECKSTYLE
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AnnotationContainer other = (AnnotationContainer)obj;
if (name == null) {
if (other.name != null) {
return false;
}
}
else if (!name.equals(other.name)) {
return false;
}
return true;
}
/** {@inheritDoc} */
@Override
public String toString() {
return getName() + ": " + getNumberOfAnnotations() + " annotations";
}
}