Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CPDRunner #43

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package de.unibremen.informatik.st.libvcs4j.pmd;

import de.unibremen.informatik.st.libvcs4j.VCSFile;
import lombok.NonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* The detection result of {@link CPDRunner}.
*/
public class CPDDetectionResult {

/**
* The detected violations.
*/
private final List<CPDDuplication> violations;

/**
* Creates a new instance with given violations.
*
* @param violations
* The violations to store (flat copied, {@code null} values are
* filtered out).
* @throws NullPointerException
* If {@code violations} is {@code null}.
*/
public CPDDetectionResult(@NonNull List<CPDDuplication> violations) {
this.violations = violations.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

/**
* Returns the violations.
*
* @return
* A copy of the internal list.
*/
public List<CPDDuplication> getViolations() {
return new ArrayList<>(violations);
}


/**
* Returns all violations detected in {@code file}. Returns an empty list
* if {@code file} is {@code null} or was not analyzed.
*
* @param file
* The requested file.
* @return
* All violations detected in {@code file}.
*/
public List<CPDDuplication> violationsOf(final VCSFile file) {
if(file == null){return new ArrayList<>();}
List<CPDDuplication> violationsOfFile = new ArrayList<CPDDuplication>();
for(CPDDuplication v : violations){
List<VCSFile.Range> ranges = v.getRanges();
for(VCSFile.Range range : ranges){
if(range.getFile().getRelativePath() == file.getRelativePath()){
violationsOfFile.add(v);
break;
}
}
}
return violationsOfFile;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package de.unibremen.informatik.st.libvcs4j.pmd;

import de.unibremen.informatik.st.libvcs4j.VCSFile;
import de.unibremen.informatik.st.libvcs4j.mapping.Mappable;
import lombok.NonNull;
import lombok.Value;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* A readonly representation of a violation detected by CPD.
*/
@Value
public final class CPDDuplication implements Mappable<String> {

/**
* The ranges of this violation.
*/
@NonNull
private final List<VCSFile.Range> ranges;

/**
* Amount of lines that are duplicated
*/
@NonNull
private final int lines;

/**
* Amount of tokens that the duplication shares
*/
@NonNull
private final int tokens;

@Override
public List<VCSFile.Range> getRanges() {
return new ArrayList<>(ranges);
}

@Override
public Optional<String> getMetadata() {
return Optional.of("cpd-clone");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package de.unibremen.informatik.st.libvcs4j.pmd;

import de.unibremen.informatik.st.libvcs4j.Revision;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pmd.cpd.CPD;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
* Allows to configure and run CPD on {@link Revision} instances.
*/
@Slf4j
public class CPDRunner {

/**
* Minimum Tokens that have to match for duplicate to be reported
*/
@Getter
private int minimumTokens;

/**
* Creates a new CPD runner.
*/
public CPDRunner(final int pMinimumTokens){
minimumTokens = pMinimumTokens;
}

/**
* Analyzes the given revision.
*
* @param revision
* The revision to analyze.
* @return
* The detection result.
* @throws NullPointerException
* If {@code revision} is {@code null}.
* @throws IOException
* If an error occurred while analyzing {@code revision}.
*/
public CPDDetectionResult analyze(@NonNull final Revision revision)
throws IOException {

final List<String> args = new ArrayList<>();
// language
args.add("--language");
args.add("java");
// tokens
args.add("--minimum-tokens");
args.add(String.valueOf(minimumTokens));
// input
args.add("--files");
args.add(revision.getOutput().toString());
// format
args.add("--format");
args.add("xml");
// encoding
args.add("--encoding");
args.add("utf-8");
// Skip files that can't be tokenized instead of throwing Exceptions
args.add("--skip-lexical-errors");
// Ignore Identifiers, Literals and Annotations to detect Type 2 Clones
args.add("--ignore-identifiers");
args.add("--ignore-literals");
args.add("--ignore-annotations");


// Temporarily redirect stdout to a string.
final PrintStream stdout = System.out;
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(bos);
System.setOut(ps);
try {
System.setProperty("net.sourceforge.pmd.cli.noExit","true");
CPD.main(args.toArray(String[]::new));
// According to PMD the resulting xml is UTF-8 encoded.
final String output = bos.toString(StandardCharsets.UTF_8.name());

// Parse output
final SAXParserFactory factory = SAXParserFactory.newInstance();
final SAXParser saxParser = factory.newSAXParser();
final InputStream bis = new ByteArrayInputStream(
output.getBytes(StandardCharsets.UTF_8.name()));
CPDSaxHandler handler = new CPDSaxHandler(revision.getFiles());
saxParser.parse(bis, handler);

// Result
return new CPDDetectionResult(handler.getViolations());
} catch (final UnsupportedOperationException | SAXException
| ParserConfigurationException e) {
throw new IOException(e);
} finally {
// Make sure to reset stdout.
System.setOut(stdout);
}
}
}
Loading