package hudson.plugins.crap4j;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.plugins.crap4j.calculation.HealthBuilder;
import hudson.plugins.crap4j.model.CrapReportMerger;
import hudson.plugins.crap4j.model.IMethodCrap;
import hudson.plugins.crap4j.model.MethodCrapBean;
import hudson.plugins.crap4j.model.ProjectCrapBean;
import hudson.plugins.crap4j.util.FoundFile;
import hudson.plugins.crap4j.util.ReportFilesFinder;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.schneide.crap4j.reader.ReportReader;
import com.schneide.crap4j.reader.model.ICrapReport;
import com.schneide.crap4j.reader.model.IMethodCrapData;
import hudson.Extension;
import hudson.matrix.MatrixProject;
import hudson.model.FreeStyleProject;
import hudson.tasks.Recorder;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
public class Crap4JPublisher extends Recorder {
/** Logger. */
private static final Logger LOGGER = Logger.getLogger(Crap4JPublisher.class.getName());
@Extension
public static final Crap4JPluginDescriptor DESCRIPTOR = new Crap4JPluginDescriptor();
private String reportPattern;
private String healthThreshold;
/**
* @param reportPattern
*/
@DataBoundConstructor
public Crap4JPublisher(String reportPattern,
String healthThreshold) {
super();
this.reportPattern = reportPattern;
this.healthThreshold = healthThreshold;
}
private HealthBuilder getHealthBuilderFor(String healthThreshold) {
if ((null == healthThreshold) || (healthThreshold.isEmpty())) {
return DESCRIPTOR.getHealthBuilder();
}
try {
return new HealthBuilder(Double.parseDouble(healthThreshold));
} catch (NumberFormatException e) {
LOGGER.log(Level.WARNING, "Could not parse health threshold representation to a number: " + healthThreshold, e);
return DESCRIPTOR.getHealthBuilder();
} catch (IllegalArgumentException e) {
LOGGER.log(Level.WARNING, "Not a valid health threshold: " + healthThreshold, e);
return DESCRIPTOR.getHealthBuilder();
}
}
@Override
public BuildStepDescriptor<Publisher> getDescriptor() {
return DESCRIPTOR;
}
@Override
public Action getProjectAction(AbstractProject<?, ?> project) {
return new Crap4JProjectAction(project);
}
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.BUILD;
}
protected void log(final PrintStream logger, final String message) {
logger.println("[CRAP4J] " + message);
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) throws InterruptedException, IOException {
PrintStream logger = listener.getLogger();
log(logger, "Collecting Crap4J analysis files...");
log(logger, "Searching for report files within " + this.reportPattern);
log(logger, "Using the new FileSetBuilder");
ReportFilesFinder finder = new ReportFilesFinder(this.reportPattern);
FoundFile[] reports = build.getWorkspace().act(finder);
if (0 == reports.length) {
log(logger, "No crap4j report files were found. Configuration error?");
return false;
}
ProjectCrapBean previousCrap = getPreviousProjectCrapBean(build);
ProjectCrapBean reportBean = createCurrentProjectCrapBean(logger,
reports, previousCrap);
build.getActions().add(new Crap4JBuildAction(
build,
new CrapBuildResult(build, reportBean),
getHealthBuilderFor(this.healthThreshold)));
return true;
}
private ProjectCrapBean getPreviousProjectCrapBean(AbstractBuild<?, ?> build) {
CrapBuildResult previousResult = CrapBuildResult.getPrevious(build);
if (null != previousResult) {
return previousResult.getResultData();
}
return null;
}
private ProjectCrapBean createCurrentProjectCrapBean(PrintStream logger,
FoundFile[] reports, ProjectCrapBean previousCrap)
throws UnsupportedEncodingException, IOException {
ProjectCrapBean[] currentBeans = loadProjectCrapBeans(logger, reports, previousCrap);
if (1 == currentBeans.length) {
return currentBeans[0];
}
CrapReportMerger merger = new CrapReportMerger();
return merger.mergeReports(previousCrap, currentBeans);
}
private ProjectCrapBean[] loadProjectCrapBeans(PrintStream logger,
FoundFile[] reports, ProjectCrapBean previousCrap)
throws UnsupportedEncodingException, IOException {
List<ProjectCrapBean> result = new ArrayList<ProjectCrapBean>();
for (FoundFile currentReportFile : reports) {
Reader reportReader = new BufferedReader(
new InputStreamReader(
currentReportFile.getFile().read(),
currentReportFile.getEncoding()));
ReportReader parser = new ReportReader(reportReader);
ICrapReport report = parser.parseData();
ProjectCrapBean reportBean = new ProjectCrapBean(
previousCrap,
report.getStatistics(),
convertToMethodCrap(report.getDetails().getMethodCrapManager().getCrapData()));
log(logger, "Got a report bean with " + reportBean.getCrapMethodCount() + " crap methods out of " + reportBean.getMethodCount() + " methods.");
result.add(reportBean);
}
return result.toArray(new ProjectCrapBean[result.size()]);
}
private IMethodCrap[] convertToMethodCrap(Collection<IMethodCrapData> crapData) {
IMethodCrap[] result = new IMethodCrap[crapData.size()];
Iterator<IMethodCrapData> dataIterator = crapData.iterator();
int index = 0;
while (dataIterator.hasNext()) {
result[index] = new MethodCrapBean(dataIterator.next());
index++;
}
return result;
}
public String getReportPattern() {
return this.reportPattern;
}
public String getHealthThreshold() {
return this.healthThreshold;
}
public void setHealthThreshold(String healthThreshold) {
this.healthThreshold = healthThreshold;
}
public void setReportPattern(String reportPattern) {
this.reportPattern = reportPattern;
}
public static final class Crap4JPluginDescriptor extends BuildStepDescriptor<Publisher> {
public static final String ACTION_ICON_PATH = "/plugin/crap4j/icons/crap-32x32.png";
private HealthBuilder healthBuilder;
Crap4JPluginDescriptor() {
super(Crap4JPublisher.class);
this.healthBuilder = new HealthBuilder();
}
public HealthBuilder getHealthBuilder() {
return this.healthBuilder;
}
@Override
public String getDisplayName() {
return "Report Crap";
}
@Override
public Crap4JPublisher newInstance(StaplerRequest req, JSONObject object) throws FormException {
Crap4JPublisher instance = req.bindParameters(Crap4JPublisher.class, "crap4j.");
return instance;
}
@Override
public boolean configure(StaplerRequest req, JSONObject object) throws FormException {
try {
double healthThreshold = Double.parseDouble(req.getParameter("crap4j.healthThreshold"));
this.healthBuilder = new HealthBuilder(healthThreshold);
} catch (NumberFormatException e) {
throw new FormException("health threshold field must be a positive Double",
"crap4j.healthThreshold");
} catch (IllegalArgumentException e) {
throw new FormException("health threshold field must be a positive Double",
"crap4j.healthThreshold");
}
save();
return super.configure(req, object);
}
@SuppressWarnings("unchecked")
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return (FreeStyleProject.class.isAssignableFrom(jobType) || MatrixProject.class.isAssignableFrom(jobType));
}
}
}