blob: 43b5a86e86acbbbdb9bc59b1f9c1df646ab524d4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2018 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.api.tools.internal.tasks;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.api.tools.internal.IApiXmlConstants;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.util.Util;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.ibm.icu.text.MessageFormat;
/**
* This task can be used to convert all reports generated by the
* apitooling.analysis task into html reports.
*/
public class AnalysisReportConversionTask extends Task {
static final class Problem {
String message;
int severity;
public Problem(String message, int severity) {
this.message = message;
this.severity = severity;
}
public String getHtmlMessage() {
StringBuilder buffer = new StringBuilder();
char[] chars = this.message.toCharArray();
for (char character : chars) {
switch (character) {
case '<':
buffer.append("&lt;"); //$NON-NLS-1$
break;
case '>':
buffer.append("&gt;"); //$NON-NLS-1$
break;
case '&':
buffer.append("&amp;"); //$NON-NLS-1$
break;
case '"':
buffer.append("&quot;"); //$NON-NLS-1$
break;
default:
buffer.append(character);
}
}
return String.valueOf(buffer);
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("Problem : ").append(this.message).append(' ').append(this.severity); //$NON-NLS-1$
return String.valueOf(buffer);
}
}
static final class ConverterDefaultHandler extends DefaultHandler {
String category;
boolean debug;
Report report;
public ConverterDefaultHandler(boolean debug) {
this.debug = debug;
}
public Report getReport() {
return this.report;
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
if (IApiXmlConstants.ELEMENT_API_TOOL_REPORT.equals(name)) {
String componentID = attributes.getValue(IApiXmlConstants.ATTR_COMPONENT_ID);
if (this.debug) {
System.out.println("component id : " + String.valueOf(componentID)); //$NON-NLS-1$
}
this.report = new Report(componentID);
} else if (IApiXmlConstants.ATTR_CATEGORY.equals(name)) {
this.category = attributes.getValue(IApiXmlConstants.ATTR_VALUE);
if (this.debug) {
System.out.println("category : " + this.category); //$NON-NLS-1$
}
} else if (IApiXmlConstants.ELEMENT_API_PROBLEM.equals(name)) {
String message = attributes.getValue(IApiXmlConstants.ATTR_MESSAGE);
if (this.debug) {
System.out.println("problem message : " + message); //$NON-NLS-1$
}
int severity = Integer.parseInt(attributes.getValue(IApiXmlConstants.ATTR_SEVERITY));
if (this.debug) {
System.out.println("problem severity : " + severity); //$NON-NLS-1$
}
this.report.addProblem(this.category, new Problem(message, severity));
} else if (IApiXmlConstants.ELEMENT_RESOLVER_ERROR.equals(name)) {
String message = attributes.getValue(IApiXmlConstants.ATTR_MESSAGE);
if (this.debug) {
System.out.println("Resolver error : " + message); //$NON-NLS-1$
}
this.report.addProblem(this.category, new Problem(message, ApiPlugin.SEVERITY_WARNING));
} else if (IApiXmlConstants.ELEMENT_BUNDLE.equals(name)) {
String bundleName = attributes.getValue(IApiXmlConstants.ATTR_NAME);
if (this.debug) {
System.out.println("bundle name : " + bundleName); //$NON-NLS-1$
}
this.report.addNonApiBundles(bundleName);
} else if (this.debug) {
if (!IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENTS.equals(name) && !IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENT.equals(name) && !IApiXmlConstants.ELEMENT_API_PROBLEMS.equals(name) && !IApiXmlConstants.ELEMENT_PROBLEM_EXTRA_ARGUMENT.equals(name) && !IApiXmlConstants.ELEMENT_PROBLEM_EXTRA_ARGUMENTS.equals(name)) {
System.out.println("unknown element : " + String.valueOf(name)); //$NON-NLS-1$
}
}
}
}
static private final class Report {
String componentID;
String link;
List<String> nonApiBundles;
Map<String, List<Problem>> problemsPerCategories;
Report(String componentID) {
this.componentID = componentID;
}
public void addNonApiBundles(String bundleName) {
if (this.nonApiBundles == null) {
this.nonApiBundles = new ArrayList<>();
}
this.nonApiBundles.add(bundleName);
}
public void addProblem(String category, Problem problem) {
if (this.problemsPerCategories == null) {
this.problemsPerCategories = new HashMap<>();
}
List<Problem> problemsList = this.problemsPerCategories.get(category);
if (problemsList == null) {
problemsList = new ArrayList<>();
this.problemsPerCategories.put(category, problemsList);
}
problemsList.add(problem);
}
public String[] getNonApiBundles() {
if (this.nonApiBundles == null || this.nonApiBundles.isEmpty()) {
return NO_NON_API_BUNDLES;
}
String[] nonApiBundlesNames = new String[this.nonApiBundles.size()];
this.nonApiBundles.toArray(nonApiBundlesNames);
return nonApiBundlesNames;
}
public Problem[] getProblems(String category) {
if (this.problemsPerCategories == null) {
return NO_PROBLEMS;
}
List<Problem> problemsList = this.problemsPerCategories.get(category);
int size = problemsList == null ? 0 : problemsList.size();
if (size == 0) {
return NO_PROBLEMS;
}
Problem[] problems = new Problem[size];
problemsList.toArray(problems);
return problems;
}
public int getProblemSize(String category) {
if (this.problemsPerCategories == null) {
return 0;
}
List<Problem> problemsList = this.problemsPerCategories.get(category);
return problemsList == null ? 0 : problemsList.size();
}
public boolean isNonApiBundlesReport() {
return this.componentID == null;
}
public void setLink(String link) {
this.link = link;
}
}
static private final class Summary {
int apiUsageNumber;
int bundleVersionNumber;
int compatibilityNumber;
int componentResolutionNumber;
String componentID;
String link;
public Summary(Report report) {
super();
this.apiUsageNumber = report.getProblemSize(APIToolsAnalysisTask.USAGE);
this.bundleVersionNumber = report.getProblemSize(APIToolsAnalysisTask.BUNDLE_VERSION);
this.compatibilityNumber = report.getProblemSize(APIToolsAnalysisTask.COMPATIBILITY);
this.componentResolutionNumber = report.getProblemSize(APIToolsAnalysisTask.COMPONENT_RESOLUTION);
this.componentID = report.componentID;
this.link = report.link;
}
@Override
public String toString() {
return MessageFormat.format("{0} : compatibility {1}, api usage {2}, bundle version {3}, resolution {4}, link {5}", //$NON-NLS-1$
this.componentID,
Integer.toString(this.compatibilityNumber),
Integer.toString(this.apiUsageNumber),
Integer.toString(this.bundleVersionNumber),
Integer.toString(this.componentResolutionNumber),
this.link );
}
}
static final Problem[] NO_PROBLEMS = new Problem[0];
static final String[] NO_NON_API_BUNDLES = new String[0];
boolean debug;
private String htmlReportsLocation;
private File htmlRoot;
private File reportsRoot;
private String xmlReportsLocation;
private void dumpFooter(PrintWriter writer) {
writer.println(Messages.W3C_page_footer);
}
private void dumpHeader(PrintWriter writer, Report report) {
writer.println(MessageFormat.format(Messages.fullReportTask_apiproblemheader, report.componentID));
// dump the summary
writer.println(MessageFormat.format(Messages.fullReportTask_apiproblemsummary,
Integer.toString(report.getProblemSize(APIToolsAnalysisTask.COMPATIBILITY)),
Integer.toString(report.getProblemSize(APIToolsAnalysisTask.USAGE)),
Integer.toString(report.getProblemSize(APIToolsAnalysisTask.BUNDLE_VERSION))));
if (report.getProblemSize(APIToolsAnalysisTask.COMPONENT_RESOLUTION) == 1) {
writer.println(MessageFormat.format(Messages.fullReportTask_resolutiondetailsSingle,
report.componentID,
Integer.toString(report.getProblemSize(APIToolsAnalysisTask.COMPONENT_RESOLUTION))));
} else if (report.getProblemSize(APIToolsAnalysisTask.COMPONENT_RESOLUTION) > 1) {
writer.println(MessageFormat.format(Messages.fullReportTask_resolutiondetails,
report.componentID,
Integer.toString(report.getProblemSize(APIToolsAnalysisTask.COMPONENT_RESOLUTION))));
}
}
private void dumpIndexEntry(int i, PrintWriter writer, Summary summary) {
if (debug) {
System.out.println(summary);
}
if ((i % 2) == 0) {
writer.println(MessageFormat.format(Messages.fullReportTask_indexsummary_even,
summary.componentID,
Integer.toString(summary.compatibilityNumber),
Integer.toString(summary.apiUsageNumber),
Integer.toString(summary.bundleVersionNumber),
summary.link));
} else {
writer.println(MessageFormat.format(Messages.fullReportTask_indexsummary_odd,
summary.componentID,
Integer.toString(summary.compatibilityNumber),
Integer.toString(summary.apiUsageNumber),
Integer.toString(summary.bundleVersionNumber),
summary.link));
}
if (summary.componentResolutionNumber > 0) {
if ((i % 2) == 0) {
writer.println(MessageFormat.format(Messages.fullReportTask_resolutionsummary_even,
summary.componentID,
Integer.toString(summary.componentResolutionNumber) ));
} else {
writer.println(MessageFormat.format(Messages.fullReportTask_resolutionsummary_odd,
summary.componentID,
Integer.toString(summary.componentResolutionNumber) ));
}
}
}
/**
* Write out the index file
*
* @param reportsRoot
* @param summaries
* @param allNonApiBundleSummary
*/
private void dumpIndexFile(File reportsRoot, Summary[] summaries, Summary allNonApiBundleSummary) {
File htmlFile = new File(this.htmlReportsLocation, "index.html"); //$NON-NLS-1$
PrintWriter writer = null;
try {
FileWriter fileWriter = new FileWriter(htmlFile);
writer = new PrintWriter(new BufferedWriter(fileWriter));
if (allNonApiBundleSummary != null) {
writer.println(MessageFormat.format(Messages.fullReportTask_indexheader, NLS.bind(Messages.fullReportTask_nonApiBundleSummary, allNonApiBundleSummary.link)));
} else {
writer.println(MessageFormat.format(Messages.fullReportTask_indexheader, Messages.fullReportTask_apiBundleSummary));
}
Arrays.sort(summaries, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
Summary summary1 = (Summary) o1;
Summary summary2 = (Summary) o2;
return summary1.componentID.compareTo(summary2.componentID);
}
});
for (int i = 0, max = summaries.length; i < max; i++) {
dumpIndexEntry(i, writer, summaries[i]);
}
writer.println(Messages.fullReportTask_indexfooter);
writer.flush();
} catch (IOException e) {
throw new BuildException(NLS.bind(Messages.ioexception_writing_html_file, htmlFile.getAbsolutePath()));
} finally {
if (writer != null) {
writer.close();
}
}
}
private void dumpNonApiBundles(PrintWriter writer, Report report) {
writer.println(Messages.fullReportTask_bundlesheader);
String[] nonApiBundleNames = report.getNonApiBundles();
for (int i = 0; i < nonApiBundleNames.length; i++) {
String bundleName = nonApiBundleNames[i];
if ((i % 2) == 0) {
writer.println(MessageFormat.format(Messages.fullReportTask_bundlesentry_even, bundleName));
} else {
writer.println(MessageFormat.format(Messages.fullReportTask_bundlesentry_odd, bundleName));
}
}
writer.println(Messages.fullReportTask_bundlesfooter);
}
private void dumpProblems(PrintWriter writer, String categoryName, Problem[] problems, boolean printEmptyCategory) {
if (problems != null && problems.length != 0) {
writer.println(MessageFormat.format(Messages.fullReportTask_categoryheader, categoryName));
for (int i = 0, max = problems.length; i < max; i++) {
Problem problem = problems[i];
if ((i % 2) == 0) {
switch (problem.severity) {
case ApiPlugin.SEVERITY_ERROR:
writer.println(MessageFormat.format(Messages.fullReportTask_problementry_even_error, problem.getHtmlMessage()));
break;
case ApiPlugin.SEVERITY_WARNING:
writer.println(MessageFormat.format(Messages.fullReportTask_problementry_even_warning, problem.getHtmlMessage()));
break;
default:
break;
}
} else {
switch (problem.severity) {
case ApiPlugin.SEVERITY_ERROR:
writer.println(MessageFormat.format(Messages.fullReportTask_problementry_odd_error, problem.getHtmlMessage()));
break;
case ApiPlugin.SEVERITY_WARNING:
writer.println(MessageFormat.format(Messages.fullReportTask_problementry_odd_warning, problem.getHtmlMessage()));
break;
default:
break;
}
}
}
writer.println(Messages.fullReportTask_categoryfooter);
} else if (printEmptyCategory) {
writer.println(MessageFormat.format(Messages.fullReportTask_category_no_elements, categoryName));
}
}
private void dumpReport(File xmlFile, Report report) {
// create file writer
// generate the html file name from the xml file name
String htmlName = extractNameFromXMLName(xmlFile);
report.setLink(extractLinkFrom(htmlName));
File htmlFile = new File(htmlName);
File parent = htmlFile.getParentFile();
if (!parent.exists()) {
if (!parent.mkdirs()) {
throw new BuildException(NLS.bind(Messages.could_not_create_file, htmlName));
}
}
PrintWriter writer = null;
try {
FileWriter fileWriter = new FileWriter(htmlFile);
writer = new PrintWriter(new BufferedWriter(fileWriter));
if (report.isNonApiBundlesReport()) {
dumpNonApiBundles(writer, report);
} else {
dumpHeader(writer, report);
dumpProblems(writer, Messages.AnalysisReportConversionTask_component_resolution_header, report.getProblems(APIToolsAnalysisTask.COMPONENT_RESOLUTION), false);
dumpProblems(writer, Messages.fullReportTask_compatibility_header, report.getProblems(APIToolsAnalysisTask.COMPATIBILITY), true);
dumpProblems(writer, Messages.fullReportTask_api_usage_header, report.getProblems(APIToolsAnalysisTask.USAGE), true);
dumpProblems(writer, Messages.fullReportTask_bundle_version_header, report.getProblems(APIToolsAnalysisTask.BUNDLE_VERSION), true);
dumpFooter(writer);
}
writer.flush();
} catch (IOException e) {
throw new BuildException(NLS.bind(Messages.ioexception_writing_html_file, htmlName));
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* Run the ant task
*/
@Override
public void execute() throws BuildException {
if (this.debug) {
System.out.println("xmlReportsLocation : " + this.xmlReportsLocation); //$NON-NLS-1$
System.out.println("htmlReportsLocation : " + this.htmlReportsLocation); //$NON-NLS-1$
}
if (this.xmlReportsLocation == null) {
throw new BuildException(Messages.missing_xml_files_location);
}
this.reportsRoot = new File(this.xmlReportsLocation);
if (!this.reportsRoot.exists() || !this.reportsRoot.isDirectory()) {
throw new BuildException(NLS.bind(Messages.invalid_directory_name, this.xmlReportsLocation));
}
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = null;
try {
parser = factory.newSAXParser();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
if (parser == null) {
throw new BuildException(Messages.could_not_create_sax_parser);
}
if (this.htmlReportsLocation == null) {
this.htmlReportsLocation = this.xmlReportsLocation;
}
this.htmlRoot = new File(this.htmlReportsLocation);
if (!this.htmlRoot.exists()) {
if (!this.htmlRoot.mkdirs()) {
throw new BuildException(NLS.bind(Messages.could_not_create_file, this.htmlReportsLocation));
}
}
if (this.debug) {
System.out.println("output name :" + this.htmlReportsLocation); //$NON-NLS-1$
}
try {
// retrieve all xml reports
File[] allFiles = Util.getAllFiles(reportsRoot, new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory() || pathname.getName().endsWith(".xml"); //$NON-NLS-1$
}
});
if (allFiles != null) {
int length = allFiles.length;
List<Summary> summariesList = new ArrayList<>(length);
Summary nonApiBundleSummary = null;
for (int i = 0; i < length; i++) {
File file = allFiles[i];
ConverterDefaultHandler defaultHandler = new ConverterDefaultHandler(this.debug);
parser.parse(file, defaultHandler);
Report report = defaultHandler.getReport();
if (report == null) {
// ignore that file. It could be the counts.xml file.
if (this.debug) {
System.out.println("Skipped file : " + file.getAbsolutePath()); //$NON-NLS-1$
}
continue;
}
dumpReport(file, report);
if (report.isNonApiBundlesReport()) {
nonApiBundleSummary = new Summary(report);
} else {
summariesList.add(new Summary(report));
}
}
// dump index file if there is at least one summary or non api
// tools bundles (ignore count.xml)
if (!summariesList.isEmpty() || nonApiBundleSummary != null) {
Summary[] summaries = new Summary[summariesList.size()];
summariesList.toArray(summaries);
dumpIndexFile(reportsRoot, summaries, nonApiBundleSummary);
}
}
} catch (SAXException e) {
// ignore
} catch (IOException e) {
// ignore
}
}
private String extractLinkFrom(String fileName) {
StringBuilder buffer = new StringBuilder();
buffer.append('.').append(fileName.substring(this.htmlRoot.getAbsolutePath().length()).replace('\\', '/'));
return String.valueOf(buffer);
}
private String extractNameFromXMLName(File xmlFile) {
String fileName = xmlFile.getAbsolutePath();
int index = fileName.lastIndexOf('.');
StringBuilder buffer = new StringBuilder();
buffer.append(fileName.substring(this.reportsRoot.getAbsolutePath().length(), index)).append(".html"); //$NON-NLS-1$
File htmlFile = new File(this.htmlReportsLocation, String.valueOf(buffer));
return htmlFile.getAbsolutePath();
}
/**
* Set the debug value.
* <p>
* The possible values are: <code>true</code>, <code>false</code>
* </p>
* <p>
* Default is <code>false</code>.
* </p>
*
* @param debugValue the given debug value
*/
public void setDebug(String debugValue) {
this.debug = Boolean.toString(true).equals(debugValue);
}
/**
* Set the location where the html reports are generated.
*
* <p>
* This is optional. If not set, the html files are created in the same
* folder as the xml files.
* </p>
* <p>
* The location is set using an absolute path.
* </p>
*
* @param htmlFilesLocation the given the location where the html reports
* are generated
*/
public void setHtmlFiles(String htmlFilesLocation) {
this.htmlReportsLocation = htmlFilesLocation;
}
/**
* Set the location where the xml reports are retrieved.
*
* <p>
* The location is set using an absolute path.
* </p>
*
* @param xmlFilesLocation the given location to retrieve the xml reports
*/
public void setXmlFiles(String xmlFilesLocation) {
this.xmlReportsLocation = xmlFilesLocation;
}
}