package com.intellij.codeInspection.ex; import com.intellij.codeInspection.InspectionApplication; import com.intellij.codeInspection.InspectionsReportConverter; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.io.URLUtil; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.xml.transform.*; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.*; import java.net.URL; import java.util.List; import java.util.Map; /** * @author Roman.Chernyatchik */ public class PlainTextFormatter implements InspectionsReportConverter { public static final String NAME = "plain"; private static final String FILE_ELEMENT = "file"; private static final String LINE_ELEMENT = "line"; private static final String PROBLEM_ELEMENT = "problem"; private static final String DESCRIPTION_ELEMENT = "description"; private static final String PROBLEM_CLASS_ELEMENT = "problem_class"; private static final String SEVERITY_ATTRIBUTE = "severity"; @Override public String getFormatName() { return NAME; } @Override public boolean useTmpDirForRawData() { return true; } @Override public void convert(@NotNull final String rawDataDirectoryPath, @Nullable final String outputPath, @NotNull final Map<String, Tools> tools, @NotNull final List<File> inspectionsResults) throws ConversionException { final SAXTransformerFactory transformerFactory = (SAXTransformerFactory)TransformerFactory.newInstance(); final URL descrExtractorXsltUrl = getClass().getResource("description-text.xsl"); final Source xslSource; final Transformer transformer; try { xslSource = new StreamSource(URLUtil.openStream(descrExtractorXsltUrl)); transformer = transformerFactory.newTransformer(xslSource); } catch (IOException e) { throw new ConversionException("Cannot find inspection descriptions converter."); } catch (TransformerConfigurationException e) { throw new ConversionException("Fail to load inspection descriptions converter."); } // write to file/stdout: final Writer w; if (outputPath != null) { final File outputFile = new File(outputPath); try { w = new FileWriter(outputFile); } catch (IOException e) { throw new ConversionException("Cannot edit file: " + outputFile.getPath()); } } else { w = new PrintWriter(System.out); } try { for (File inspectionData : inspectionsResults) { if (inspectionData.isDirectory()) { warn("Folder isn't expected here: " + inspectionData.getName()); continue; } final String fileNameWithoutExt = FileUtil.getNameWithoutExtension(inspectionData); if (InspectionApplication.DESCRIPTIONS.equals(fileNameWithoutExt)) { continue; } InspectionToolWrapper toolWrapper = tools.get(fileNameWithoutExt).getTool(); // Tool name and group w.append(getToolPresentableName(toolWrapper)).append("\n"); // Description is HTML based, need to be converted in plain text writeInspectionDescription(w, toolWrapper, transformer); // separator before file list w.append("\n"); // parse xml and output results final SAXBuilder builder = new SAXBuilder(); try { final Document doc = builder.build(inspectionData); final Element root = doc.getRootElement(); final List problems = root.getChildren(PROBLEM_ELEMENT); // let's count max file path & line_number length to align problem descriptions final int maxFileColonLineLength = getMaxFileColonLineNumLength(inspectionData, toolWrapper, problems); for (Object problem : problems) { // Format: // file_path:line_num [severity] problem description final Element fileElement = ((Element)problem).getChild(FILE_ELEMENT); final String filePath = getPath(fileElement); // skip suppressed results if (resultsIgnored(inspectionData, toolWrapper)) { continue; } final Element lineElement = ((Element)problem).getChild(LINE_ELEMENT); final Element problemDescrElement = ((Element)problem).getChild(DESCRIPTION_ELEMENT); final String severity = ((Element)problem).getChild(PROBLEM_CLASS_ELEMENT).getAttributeValue(SEVERITY_ATTRIBUTE); final String fileLineNum = lineElement.getText(); w.append(" ").append(filePath).append(':').append(fileLineNum); // align descriptions for (int i = maxFileColonLineLength - 1 - filePath.length() - fileLineNum.length() + 4; i >= 0; i--) { w.append(' '); } w.append("[").append(severity).append("] "); w.append(problemDescrElement.getText()).append('\n'); } } catch (JDOMException e) { throw new ConversionException("Unknown results format, file = " + inspectionData.getPath() + ". Error: " + e.getMessage()); } // separator between neighbour inspections w.append("\n"); } } catch (IOException e) { throw new ConversionException("Cannot write inspection results: " + e.getMessage()); } finally { if (w != null) { try { w.close(); } catch (IOException e) { warn("Cannot save inspection results: " + e.getMessage()); } } } } private int getMaxFileColonLineNumLength(@NotNull final File inspectionResultData, @NotNull final InspectionToolWrapper toolWrapper, @NotNull final List problems) { int maxFileColonLineLength = 0; for (Object problem : problems) { final Element fileElement = ((Element)problem).getChild(FILE_ELEMENT); final Element lineElement = ((Element)problem).getChild(LINE_ELEMENT); final String filePath = getPath(fileElement); // skip suppressed results if (resultsIgnored(inspectionResultData, toolWrapper)) { continue; } maxFileColonLineLength = Math.max(maxFileColonLineLength, filePath.length() + 1 + lineElement.getText().length()); } return maxFileColonLineLength; } private void warn(String msg) { System.err.println(msg); } private boolean resultsIgnored(@NotNull final File file, @NotNull final InspectionToolWrapper toolWrapper) { // TODO: check according to config return false; } @NotNull protected String getPath(@NotNull final Element fileElement) { return fileElement.getText().replace("file://$PROJECT_DIR$", "."); } protected void writeInspectionDescription(@NotNull final Writer w, @NotNull final InspectionToolWrapper toolWrapper, @NotNull final Transformer transformer) throws IOException, ConversionException { final StringWriter descrWriter = new StringWriter(); String descr = toolWrapper.loadDescription(); if (descr == null) { return; } // convert line ends to xml form descr = descr.replace("<br>", "<br/>"); try { transformer.transform(new StreamSource(new StringReader(descr)), new StreamResult(descrWriter)); } catch (TransformerException e) { // Not critical problem, just inspection error cannot be loaded warn("ERROR: Cannot load description for inspection: " + getToolPresentableName(toolWrapper) + ".\n Error message: " + e.getMessage()); return; } final String trimmedDesc = descrWriter.toString().trim(); final String[] descLines = StringUtil.splitByLines(trimmedDesc); if (descLines.length > 0) { for (String descLine : descLines) { w.append(" ").append(descLine.trim()).append("\n"); } } } @NotNull protected String getToolPresentableName(@NotNull final InspectionToolWrapper toolWrapper) throws IOException { final StringBuilder buff = new StringBuilder(); // inspection name buff.append(toolWrapper.getDisplayName()).append(" ("); // group name final String[] groupPath = toolWrapper.getGroupPath(); for (int i = 0, groupPathLength = groupPath.length; i < groupPathLength; i++) { if (i != 0) { buff.append(" | "); } buff.append(groupPath[i]); } buff.append(")"); return buff.toString(); } }