/*
* WPCleaner: A tool to help on Wikipedia maintenance tasks.
* Copyright (C) 2015 Nicolas Vervelle
*
* See README.txt file for licensing information.
*/
package org.wikipediacleaner.gui.swing.worker;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.wikipediacleaner.api.API;
import org.wikipediacleaner.api.APIException;
import org.wikipediacleaner.api.APIFactory;
import org.wikipediacleaner.api.constants.EnumWikipedia;
import org.wikipediacleaner.api.data.DataManager;
import org.wikipediacleaner.api.data.Namespace;
import org.wikipediacleaner.api.data.Page;
import org.wikipediacleaner.api.data.PageAnalysis;
import org.wikipediacleaner.api.data.PageElementTemplate;
import org.wikipediacleaner.api.data.TemplateData;
import org.wikipediacleaner.i18n.GT;
/**
* Tools for checking articles.
*/
public class CheckArticleTools {
/** Wiki. */
private final EnumWikipedia wiki;
/** All reports. */
private final List<CheckArticleReport> reports;
/** Current report. */
private CheckArticleReport currentReport;
public CheckArticleTools(EnumWikipedia wiki) {
this.wiki = wiki;
reports = new ArrayList<CheckArticleReport>();
}
// ==========================================================================
// Check
// ==========================================================================
/**
* @param page Page to be checked.
* @throws APIException
*/
public void checkArticle(Page page)
throws APIException {
API api = APIFactory.getAPI();
api.retrieveContents(wiki, Collections.singletonList(page), false, false);
checkArticle(page, page.getContents());
}
/**
* @param page Page to be checked.
* @param contents Current contents.
* @throws APIException
*/
public void checkArticle(Page page, String contents)
throws APIException {
initCurrentReport(page, contents);
checkTemplates();
}
/**
* @param template Template.
* @throws APIException
*/
public void checkTemplate(PageElementTemplate template)
throws APIException {
initCurrentReport(null, null);
CheckArticleStep step = currentReport.setCurrentStep(null);
TemplateData templateData = retrieveTemplateData(template);
checkTemplate(step, template, templateData, false);
}
// ==========================================================================
// Templates analysis
// ==========================================================================
/**
* Check templates in the current page.
*
* @throws APIException
*/
private void checkTemplates() throws APIException {
PageAnalysis analysis = currentReport.getAnalysis();
List<PageElementTemplate> templates = analysis.getTemplates();
if (templates == null) {
return;
}
CheckArticleStep step = currentReport.setCurrentStep(GT._("Templates"));
// Check the existence of TemplateData blocks for every template
CheckArticleElement element = step.setCurrentElement(GT._("Existence of {0} blocks", "TemplateData"));
Map<String, TemplateData> templateDataMap = new HashMap<String, TemplateData>();
Map<String, TemplateData> backupTemplateDataMap = new HashMap<String, TemplateData>();
for (PageElementTemplate template : templates) {
String templateName = template.getTemplateName();
if (!templateDataMap.containsKey(templateName)) {
TemplateData templateData = retrieveTemplateData(template);
templateDataMap.put(templateName, templateData);
if (templateData == null) {
String message = GT._(
"Template \"{0}\" has no {1} block defined.",
new Object[] { template.getTemplateName(), "TemplateData" });
element.addWarning(message);
templateData = computeTemplateData(template);
if (templateData != null) {
backupTemplateDataMap.put(templateName, templateData);
}
}
}
}
// Check each template
for (PageElementTemplate template : templates) {
TemplateData templateData = templateDataMap.get(template.getTemplateName());
if (templateData != null) {
checkTemplate(step, template, templateData, false);
} else {
templateData = backupTemplateDataMap.get(template.getTemplateName());
if (templateData != null) {
checkTemplate(step, template, templateData, true);
}
}
}
// Clean up
step.cleanup();
}
/**
* Retrieve TemplateData.
*
* @param template Template.
* @return TemplateData.
* @throws APIException
*/
private TemplateData retrieveTemplateData(
PageElementTemplate template) throws APIException {
String templateName = template.getTemplateName();
String title = wiki.getWikiConfiguration().getPageTitle(
Namespace.TEMPLATE, templateName);
Page templatePage = DataManager.getPage(
wiki, title, null, null, null);
API api = APIFactory.getAPI();
return api.retrieveTemplateData(wiki, templatePage);
}
/**
* Compute TemplateData.
*
* @param template Template.
* @return TemplateData.
* @throws APIException
*/
private TemplateData computeTemplateData(
PageElementTemplate template) throws APIException {
String templateName = template.getTemplateName();
String title = wiki.getWikiConfiguration().getPageTitle(
Namespace.TEMPLATE, templateName);
Page templatePage = DataManager.getPage(
wiki, title, null, null, null);
API api = APIFactory.getAPI();
api.retrieveContents(wiki, Collections.singletonList(templatePage), false, true);
return TemplateData.createFromContent(
templatePage.getAnalysis(templatePage.getContents(), false));
}
/**
* @param step Current step.
* @param template Template.
* @param templateData Template data.
* @param correct True if the template data is generated.
*/
private void checkTemplate(
CheckArticleStep step,
PageElementTemplate template,
TemplateData templateData, boolean generated) {
String templateName = template.getTemplateName();
String message = GT._(
"Template \"{0}\" ({1}-{2})",
new Object[] { templateName, template.getBeginIndex(), template.getEndIndex() });
CheckArticleElement element = step.setCurrentElement(message);
// Check TemplateData
if (templateData == null) {
String warning = GT._(
"Template \"{0}\" has no {1} block defined.",
new Object[] { template.getTemplateName(), "TemplateData" });
element.addWarning(warning);
return;
}
// Check each parameter defined in TemplateData.
for (TemplateData.Parameter param : templateData.getParameters()) {
String aliases = listAliases(param);
// Find parameters matching the TemplateData parameter.
List<PageElementTemplate.Parameter> parameters = new ArrayList<PageElementTemplate.Parameter>();
for (int i = 0; i < template.getParameterCount(); i++) {
PageElementTemplate.Parameter parameter = template.getParameter(i);
if (param.isPossibleName(parameter.getComputedName())) {
parameters.add(parameter);
// Check parameter type
if (param.getType() != null) {
TemplateData.EnumParameterType enumType = param.getType();
if (!enumType.isCompatible(parameter.getStrippedValue())) {
String value = parameter.getValue();
if (aliases != null) {
element.addWarning(GT._(
"Parameter defined as \"{0}\" (aliases {1}) in {2} should be of type \"{3}\", but actual value is \"{4}\".",
new Object[] { param.getName(), aliases, "TemplateData", enumType.toString(), value }));
} else {
element.addWarning(GT._(
"Parameter defined as \"{0}\" in {1} should be of type \"{2}\", but actual value is \"{3}\".",
new Object[] { param.getName(), "TemplateData", enumType.toString(), value }));
}
}
}
// Check deprecated parameter
if (param.isDeprecated()) {
String value = parameter.getValue();
if (aliases != null) {
message = GT._(
"Parameter defined as \"{0}\" (aliases {1}) in {2} is deprecated, but still present with value \"{3}\".",
new Object[] { param.getName(), aliases, "TemplateData", value });
} else {
message = GT._(
"Parameter defined as \"{0}\" in {1} is deprecated, but still present with value \"{2}\".",
new Object[] { param.getName(), "TemplateData", value });
}
if (param.getDeprecatedText() != null) {
message += " (" + param.getDeprecatedText() + ")";
}
element.addWarning(message);
}
}
}
// Check mandatory parameters
if (param.isRequired() && parameters.isEmpty()) {
if (aliases != null) {
element.addWarning(GT._(
"Parameter defined as \"{0}\" (aliases {1}) in {2} is required, but is missing.",
new Object[] { param.getName(), aliases, "TemplateData" }));
} else {
element.addWarning(GT._(
"Parameter defined as \"{0}\" in {1} is required, but is missing.",
new Object[] { param.getName(), "TemplateData" }));
}
}
// Check duplicate parameters
if (parameters.size() > 1) {
StringBuilder buffer = new StringBuilder();
for (PageElementTemplate.Parameter parameter : parameters) {
if (buffer.length() > 0) {
buffer.append(", ");
}
buffer.append(parameter.getComputedName());
}
if (aliases != null) {
element.addWarning(GT._(
"Parameter defined as \"{0}\" (aliases {1}) in {2} is present several times: {3}.",
new Object[] { param.getName(), aliases, "TemplateData", buffer.toString() }));
} else {
element.addWarning(GT._(
"Parameter defined as \"{0}\" in {1} is present several times: {2}.",
new Object[] { param.getName(), "TemplateData", buffer.toString() }));
}
}
}
// Check each parameter
if (!generated) {
for (int i = 0; i < template.getParameterCount(); i++) {
PageElementTemplate.Parameter parameter = template.getParameter(i);
TemplateData.Parameter param = templateData.getParameter(parameter.getComputedName());
if (param == null) {
element.addWarning(GT._(
"Parameter \"{0}\" is not defined in {1}.",
new Object[] { parameter.getComputedName(), "TemplateData" }));
}
}
}
}
/**
* @param param TemplateData parameter.
* @return String representation of parameter aliases.
*/
private String listAliases(TemplateData.Parameter param) {
if (param == null) {
return null;
}
List<String> aliases = param.getAliases();
if ((aliases == null) || aliases.isEmpty()) {
return null;
}
StringBuilder bufferAliases = new StringBuilder();
for (String alias : aliases) {
if (bufferAliases.length() > 0) {
bufferAliases.append(", ");
}
bufferAliases.append(alias);
}
return bufferAliases.toString();
}
// ==========================================================================
// Report management
// ==========================================================================
/**
* @param page Page to be checked.
*/
private void initCurrentReport(Page page, String contents) {
currentReport = new CheckArticleReport(page, contents);
reports.add(currentReport);
}
/**
* @return True if a problem is reported.
*/
public boolean isProblemReported() {
for (CheckArticleReport report : reports) {
if (report.hasWarnings()) {
return true;
}
}
return false;
}
/**
* @return Full report.
*/
public String getReport() {
StringBuilder result = new StringBuilder();
// Detail each report
for (CheckArticleReport report : reports) {
String reportTitle = report.getTitle();
String reportExtra = "";
if (reportTitle != null) {
reportExtra = "=";
result.append("== ");
result.append(reportTitle);
result.append(" ==\n");
}
if (!report.hasWarnings()) {
result.append(GT._("No problem has been detected."));
result.append("\n\n");
} else {
// Detail each step
for (CheckArticleStep step : report.getSteps()) {
String stepTitle = step.getTitle();
String stepExtra = "";
if (stepTitle != null) {
stepExtra = "=";
result.append(reportExtra);
result.append("== ");
result.append(stepTitle);
result.append(" ==");
result.append(reportExtra);
result.append("\n");
}
if (!step.hasWarnings()) {
result.append(GT._("No problem has been detected."));
result.append("\n\n");
} else {
// Detail each element
for (CheckArticleElement element : step.getElements()) {
String elementTitle = element.getTitle();
if (elementTitle != null) {
result.append(reportExtra);
result.append(stepExtra);
result.append("== ");
result.append(elementTitle);
result.append(" ==");
result.append(stepExtra);
result.append(reportExtra);
result.append("\n");
}
if (!element.hasWarnings()) {
result.append(GT._("No problem has been detected."));
result.append("\n\n");
} else {
List<String> warnings = element.getWarnings();
result.append(GT.__(
"The following problem has been detected:",
"The following problems have been detected:",
warnings.size(),
(Object[]) null));
result.append("\n");
for (String warning : warnings) {
result.append("* ");
result.append(warning);
result.append("\n");
}
result.append("\n");
}
}
}
}
}
}
return result.toString();
}
// ==========================================================================
// Reports
// ==========================================================================
/**
* Utility class to store the results of checking an article.
*/
private static class CheckArticleReport {
/** Report title. */
private final String title;
/** Page analysis. */
private final PageAnalysis analysis;
/** Steps in page check. */
private final List<CheckArticleStep> steps;
/** Current stage in page check. */
private CheckArticleStep currentStep;
/**
* @param page Checked page.
* @param contents Page contents.
*/
public CheckArticleReport(Page page, String contents) {
this.title = (page != null) ? ("[[:" + page.getTitle() + "]]") : null;
this.analysis = (page != null) ?
((contents != null) ?
page.getAnalysis(contents, false) :
page.getAnalysis(page.getContents(), false)) :
null;
this.steps = new ArrayList<CheckArticleTools.CheckArticleStep>();
}
/**
* @return Report title
*/
public String getTitle() {
return title;
}
/**
* @return Page analysis.
*/
public PageAnalysis getAnalysis() {
return analysis;
}
/**
* @param step Current step description.
*/
public CheckArticleStep setCurrentStep(String step) {
currentStep = new CheckArticleStep(step);
steps.add(currentStep);
return currentStep;
}
/**
* @return List of steps.
*/
public List<CheckArticleStep> getSteps() {
return steps;
}
/**
* @return True if a problem is reported.
*/
public boolean hasWarnings() {
for (CheckArticleStep step : steps) {
if (step.hasWarnings()) {
return true;
}
}
return false;
}
}
/**
* Utility class to store the results of a step in checking an article.
*/
private static class CheckArticleStep {
/** Step title. */
private final String title;
/** Elements checked. */
private final List<CheckArticleElement> elements;
/** Current element. */
private CheckArticleElement currentElement;
/**
* @param title Step title.
*/
public CheckArticleStep(String title) {
this.title = title;
this.elements = new ArrayList<CheckArticleElement>();
}
/**
* @return Step title.
*/
public String getTitle() {
return title;
}
/**
* @param element Current element description.
*/
public CheckArticleElement setCurrentElement(String element) {
currentElement = new CheckArticleElement(element);
elements.add(currentElement);
return currentElement;
}
/**
* @return List of elements.
*/
public List<CheckArticleElement> getElements() {
return elements;
}
/**
* @return True if a problem is reported.
*/
public boolean hasWarnings() {
for (CheckArticleElement element : elements) {
if (element.hasWarnings()) {
return true;
}
}
return false;
}
/**
* Clean up step for unnecessary elements.
*/
public void cleanup() {
currentElement = null;
Iterator<CheckArticleElement> itElement = elements.iterator();
while (itElement.hasNext()) {
CheckArticleElement element = itElement.next();
if (!element.hasWarnings()) {
itElement.remove();
}
}
}
}
/**
* Utility class to store the results for an element in checking an article.
*/
private static class CheckArticleElement {
/** Element title. */
private final String title;
/** List of warnings. */
private final List<String> warnings;
/**
* @param title Title of the element.
*/
public CheckArticleElement(String title) {
this.title = title;
this.warnings = new ArrayList<String>();
}
/**
* @return Element title.
*/
public String getTitle() {
return title;
}
/**
* @return List of warnings.
*/
public List<String> getWarnings() {
return warnings;
}
/**
* @param warning Warning.
*/
public void addWarning(String warning) {
if ((warning != null) && (warning.trim().length() > 0)) {
warnings.add(warning.trim());
}
}
/**
* @return True if element has warnings.
*/
public boolean hasWarnings() {
return !warnings.isEmpty();
}
}
}