package hudson.plugins.dry.parser;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import org.apache.commons.lang.StringUtils;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import de.java2html.converter.JavaSource2HTMLConverter;
import de.java2html.javasource.JavaSource;
import de.java2html.javasource.JavaSourceParser;
import de.java2html.options.JavaSourceConversionOptions;
import de.java2html.util.IllegalConfigurationException;
import hudson.plugins.analysis.util.model.AbstractAnnotation;
import hudson.plugins.analysis.util.model.FileAnnotation;
import hudson.plugins.analysis.util.model.Priority;
import hudson.plugins.dry.Messages;
/**
* A serializable Java Bean class representing a duplicate code warning.
* <p>
* Note: this class has a natural ordering that is inconsistent with equals.
* </p>
*
* @author Ulli Hafner
*/
public class DuplicateCode extends AbstractAnnotation {
/** Unique identifier of this class. */
private static final long serialVersionUID = -6231614169627992548L;
/** Origin of the annotation. */
public static final String ORIGIN = "dry";
/**
* Removes duplicates from the specified set of duplicate code warnings. All warnings that belong to the same
* duplication are duplicate.
*
* @param allAnnotations the annotations to filter
* @return only one warning per duplication
*/
public static SortedSet<FileAnnotation> filter(final Set<FileAnnotation> allAnnotations) {
Set<Integer> numbers = Sets.newHashSet();
Set<FileAnnotation> filtered = Sets.newHashSet();
for (FileAnnotation fileAnnotation : allAnnotations) {
DuplicateCode duplication = (DuplicateCode)fileAnnotation;
int id = duplication.getNumber();
if (!numbers.contains(id)) {
filtered.add(fileAnnotation);
numbers.add(id);
}
}
return ImmutableSortedSet.copyOf(filtered);
}
/** The links to the other code duplications. */
@SuppressWarnings("Se")
private final Set<DuplicateCode> links = new HashSet<DuplicateCode>();
/** The duplicate source code fragment. */
private String sourceCode;
private int number;
private static int oldFormat;
/**
* Creates a new instance of {@link DuplicateCode}.
*
* @param priority
* the priority of the warning
* @param firstLine
* the starting line of the duplication
* @param numberOfLines
* total number of duplicate lines
* @param fileName
* name of the file that contains the duplication
*/
public DuplicateCode(final Priority priority, final int firstLine, final int numberOfLines, final String fileName) {
super(priority, Messages.DRY_Warning_Message(numberOfLines), firstLine, firstLine + numberOfLines - 1,
StringUtils.EMPTY, Messages.DRY_Warning_Type());
setOrigin(ORIGIN);
setFileName(fileName);
}
private Object readResolve() {
superReadResolve();
if (number == 0) {
number = oldFormat++;
}
return this;
}
@Override
public String getType() {
return Messages.DRY_Warning_Type();
}
@Override
public String getMessage() {
return Messages.DRY_Warning_Message(size());
}
/**
* Returns the total number of duplicate lines.
*
* @return the number of duplicate lines
*/
public int getNumberOfLines() {
return getLineRanges().iterator().next().getEnd() - getPrimaryLineNumber() + 1;
}
/**
* Returns the total number of duplicate lines.
*
* @return the number of duplicate lines
*/
public int size() {
return getNumberOfLines();
}
/**
* Returns the total number of duplicate lines.
*
* @return the number of duplicate lines
*/
public int length() {
return getNumberOfLines();
}
@Override
public String getToolTip() {
StringBuilder message = new StringBuilder(512);
message.append("<p>");
message.append(Messages.DRY_Duplications_Header());
message.append("<ul>");
for (DuplicateCode duplication : links) {
message.append("<li>");
message.append(String.format("<a href=\"link.%s.%s/#%s\">%s (%s)</a>", getKey(), duplication.getKey(),
duplication.getPrimaryLineNumber(), duplication.getLinkName(), duplication.getPrimaryLineNumber()));
message.append("</li>");
}
message.append("</ul>");
message.append("</p>");
return message.toString();
}
/**
* Creates links to the specified collection of other code blocks.
*
* @param codeBlocks
* the code blocks to links to
*/
public void linkTo(final List<DuplicateCode> codeBlocks) {
links.addAll(codeBlocks);
links.remove(this);
}
/**
* Returns the links to the duplicated code in other files.
*
* @return the links
*/
public Collection<DuplicateCode> getLinks() {
return Collections.unmodifiableCollection(links);
}
/**
* Returns the duplicate source code fragment.
*
* @return the duplicate source code fragment
*/
public String getSourceCode() {
return sourceCode;
}
/**
* Returns the duplicate source code fragment as formatted HTML string.
*
* @return the duplicate source code fragment
*/
public String getFormattedSourceCode() {
try {
JavaSource source = new JavaSourceParser().parse(new StringReader(sourceCode));
JavaSource2HTMLConverter converter = new JavaSource2HTMLConverter();
StringWriter writer = new StringWriter();
JavaSourceConversionOptions options = JavaSourceConversionOptions.getDefault();
options.setShowLineNumbers(false);
options.setAddLineAnchors(false);
converter.convert(source, options, writer);
return writer.toString();
}
catch (IllegalConfigurationException exception) {
return sourceCode;
}
catch (IOException exception) {
return sourceCode;
}
}
/**
* Sets the duplicate source code fragment to the specified value.
*
* @param sourceCode
* the duplicate code fragment
*/
public void setSourceCode(final String sourceCode) {
this.sourceCode = sourceCode;
}
@Override
public int hashCode() {
int prime = 31; // NOCHECKSTYLE
int result = super.hashCode();
result = prime * result + ((sourceCode == null) ? 0 : sourceCode.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DuplicateCode other = (DuplicateCode)obj;
if (sourceCode == null) {
if (other.sourceCode != null) {
return false;
}
}
else if (!sourceCode.equals(other.sourceCode)) {
return false;
}
return true;
}
/**
* Returns the link with the specified hash code.
*
* @param linkHashCode
* the hash code of the linked annotation
* @return the link with the specified hash code
*/
public FileAnnotation getLink(final long linkHashCode) {
for (FileAnnotation link : links) {
if (link.getKey() == linkHashCode) {
return link;
}
}
throw new NoSuchElementException("Linked annotation not found: key=" + linkHashCode);
}
/**
* Sets the duplication number this warning belongs to.
*
* @param number
* the duplication number
*/
public void setNumber(final int number) {
this.number = 1 + number;
}
/**
* Returns the duplication number this warning belongs to.
*
* @return the duplication number
*/
public int getNumber() {
return number;
}
/** Backward compatibility. @deprecated do not remove */
@SuppressWarnings({"unused", "PMD.UnusedPrivateField"})
@Deprecated
private boolean isDerived;
}