package hudson.plugins.analysis.core; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import edu.umd.cs.findbugs.annotations.SuppressWarnings; import hudson.FilePath; import hudson.plugins.analysis.Messages; import hudson.plugins.analysis.util.FileFinder; import hudson.plugins.analysis.util.model.FileAnnotation; import hudson.plugins.analysis.util.model.Priority; /** * Stores the collection of parsed annotations and associated error messages. * * @author Ulli Hafner */ public class ParserResult implements Serializable { private static final long serialVersionUID = -8414545334379193330L; private static final Logger LOGGER = Logger.getLogger(ParserResult.class.getName()); private static final int DUPLICATES_REPORTING_LIMIT = 20; private static final String SLASH = "/"; /** The parsed annotations. */ @SuppressWarnings("Se") private final Set<FileAnnotation> annotations = new HashSet<FileAnnotation>(); /** The collection of error messages. */ @SuppressWarnings("Se") private final List<String> errorMessages = new ArrayList<String>(); /** Number of annotations by priority. */ @SuppressWarnings("Se") private final Map<Priority, Integer> annotationCountByPriority = new HashMap<Priority, Integer>(); /** The set of modules. */ @SuppressWarnings("Se") private final Set<String> modules = new HashSet<String>(); /** The workspace (might be null). */ private final FilePath workspace; /** A mapping of relative file names to absolute file names. */ @SuppressWarnings("Se") private final Map<String, String> fileNameCache = new HashMap<String, String>(); /** * Creates a new instance of {@link ParserResult}. */ public ParserResult() { this((FilePath)null); } /** * Creates a new instance of {@link ParserResult}. * * @param workspace * the workspace to find the files in */ public ParserResult(final FilePath workspace) { this.workspace = workspace; Priority[] priorities = Priority.values(); for (int priority = 0; priority < priorities.length; priority++) { annotationCountByPriority.put(priorities[priority], 0); } } /** * Creates a new instance of {@link ParserResult}. * * @param annotations * the annotations to add */ public ParserResult(final Collection<FileAnnotation> annotations) { this((FilePath)null); addAnnotations(annotations); } /** * Finds a file with relative filename and replaces the name with the absolute path. * * @param annotation the annotation */ private void expandRelativePaths(final FileAnnotation annotation) { try { if (workspace != null && hasRelativeFileName(annotation)) { FilePath remote = workspace.child(annotation.getFileName()); if (remote.exists()) { annotation.setFileName(remote.getRemote()); } else { findFileByScanningAllWorkspaceFiles(annotation); } } } catch (IOException exception) { // ignore } catch (InterruptedException exception) { // ignore } } /** * Returns the file name from the cache of all workspace files. The cache will * be built only once. * * @param annotation * the annotation to get the filename for * @throws IOException * signals that an I/O exception has occurred. * @throws InterruptedException * If the user cancels this action */ private void findFileByScanningAllWorkspaceFiles(final FileAnnotation annotation) throws IOException, InterruptedException { if (fileNameCache.isEmpty()) { populateFileNameCache(); } if (fileNameCache.containsKey(annotation.getFileName())) { annotation.setFileName(workspace.getRemote() + SLASH + fileNameCache.get(annotation.getFileName())); } } /** * Builds a cache of file names in the remote file system. * * @throws IOException * if the file could not be read * @throws InterruptedException * if the user cancels the search */ private void populateFileNameCache() throws IOException, InterruptedException { LOGGER.log(Level.INFO, "Building cache of all workspace files to obtain absolute filenames for all warnings."); String[] allFiles = workspace.act(new FileFinder("**/*")); Set<String> duplicates = new TreeSet<String>(); for (String file : allFiles) { String fileName = new File(file).getName(); if (fileNameCache.containsKey(fileName)) { if (duplicates.size() >= DUPLICATES_REPORTING_LIMIT) { duplicates.add("\u2026"); // HORIZONTAL ELLIPSIS sorts after ASCII whereas ... FULL STOP is before letters } else { duplicates.add(fileName); } fileNameCache.remove(fileName); } else { fileNameCache.put(fileName, file); } } if (!duplicates.isEmpty()) { LOGGER.log(Level.INFO, "Relative filenames {0} found more than once; absolute filename resolution disabled for these files.", duplicates); } } /** * Returns whether the annotation references a relative filename. * * @param annotation the annotation * @return <code>true</code> if the filename is relative */ private boolean hasRelativeFileName(final FileAnnotation annotation) { String fileName = annotation.getFileName(); return !fileName.startsWith(SLASH) && !fileName.contains(":"); } /** * Adds the specified annotation to this container. * * @param annotation the annotation to add */ public final void addAnnotation(final FileAnnotation annotation) { if (!annotations.contains(annotation)) { expandRelativePaths(annotation); annotations.add(annotation); Integer count = annotationCountByPriority.get(annotation.getPriority()); annotationCountByPriority.put(annotation.getPriority(), count + 1); } } /** * 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)); } /** * Addds an error message for the specified module name. * * @param module * the current module * @param message * the error message */ public void addErrorMessage(final String module, final String message) { errorMessages.add(Messages.Result_Error_ModuleErrorMessage(module, message)); } /** * Adds an error message. * * @param message * the error message */ public void addErrorMessage(final String message) { errorMessages.add(message); } /** * Adds the error messages to this result. * * @param errors the error messages to add */ public void addErrors(final List<String> errors) { errorMessages.addAll(errors); } /** * Returns the errorMessages. * * @return the errorMessages */ public List<String> getErrorMessages() { return ImmutableList.copyOf(errorMessages); } /** * Returns the annotations of this result. * * @return the annotations of this result */ public Set<FileAnnotation> getAnnotations() { return ImmutableSet.copyOf(annotations); } /** * Returns the total number of annotations for this object. * * @return total number of annotations for this object */ public int getNumberOfAnnotations() { return annotations.size(); } /** * Returns the total number of annotations 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) { return annotationCountByPriority.get(priority); } /** * Returns whether this objects has annotations. * * @return <code>true</code> if this objects has annotations. */ public boolean hasAnnotations() { return !annotations.isEmpty(); } /** * Returns whether this objects has annotations with the specified priority. * * @param priority * the priority * @return <code>true</code> if this objects has annotations. */ public boolean hasAnnotations(final Priority priority) { return annotationCountByPriority.get(priority) > 0; } /** * Returns whether this objects has no annotations. * * @return <code>true</code> if this objects has no annotations. */ public boolean hasNoAnnotations() { return !hasAnnotations(); } /** * Returns whether this objects has no annotations with the specified priority. * * @param priority * the priority * @return <code>true</code> if this objects has no annotations. */ public boolean hasNoAnnotations(final Priority priority) { return !hasAnnotations(priority); } /** * Returns the number of modules. * * @return the number of modules */ public int getNumberOfModules() { return modules.size(); } /** * Returns the parsed modules. * * @return the parsed modules */ public Set<String> getModules() { return Collections.unmodifiableSet(modules); } /** * Adds a new parsed module. * * @param moduleName * the name of the parsed module */ public void addModule(final String moduleName) { modules.add(moduleName); } /** * Adds the specified parsed modules. * * @param additionalModules * the name of the parsed modules */ public void addModules(final Collection<String> additionalModules) { modules.addAll(additionalModules); } /** {@inheritDoc} */ @Override public String toString() { return getNumberOfAnnotations() + " annotations"; } }