001package com.streamconverter.command.impl.analysis;
002
003import com.fasterxml.jackson.dataformat.csv.CsvMapper;
004import com.fasterxml.jackson.dataformat.csv.CsvSchema;
005import com.streamconverter.command.AbstractStreamCommand;
006import com.streamconverter.command.impl.analysis.PmdXmlToMarkdownCommand.PmdViolation;
007import java.io.IOException;
008import java.io.InputStream;
009import java.io.OutputStream;
010import java.util.*;
011import javax.xml.parsers.DocumentBuilder;
012import javax.xml.parsers.DocumentBuilderFactory;
013import org.w3c.dom.Document;
014import org.w3c.dom.Element;
015import org.w3c.dom.NodeList;
016
017/**
018 * PMD XML レポートを CSV 形式に変換するコマンド
019 *
020 * <p>StreamConverter アーキテクチャ準拠の実装例として、PMD XMLレポートを スプレッドシート分析に適したCSV形式に変換します。Jackson CSV
021 * mapperを使用した 型安全で効率的なCSV生成を実装しています。
022 *
023 * <p><strong>出力CSV形式:</strong>
024 *
025 * <pre>
026 * File,Line,Rule,Category,Priority,Description,Class,Method,Variable
027 * </pre>
028 *
029 * <p><strong>特徴:</strong>
030 *
031 * <ul>
032 *   <li>Jackson CsvMapper による型安全なCSV生成
033 *   <li>適切なCSVエスケープ処理
034 *   <li>StreamConverter設計原則準拠
035 *   <li>ストリーミング処理対応
036 * </ul>
037 *
038 * <p><strong>使用例:</strong>
039 *
040 * <pre>
041 * // パイプライン使用例
042 * StreamConverter converter = new StreamConverter(
043 *     new PmdXmlToCsvCommand()
044 * );
045 * converter.run(pmdXmlInputStream, csvOutputStream);
046 * </pre>
047 */
048public class PmdXmlToCsvCommand extends AbstractStreamCommand {
049
050  private final CsvMapper csvMapper;
051
052  /** Creates a new command instance. */
053  public PmdXmlToCsvCommand() {
054    this.csvMapper = new CsvMapper();
055  }
056
057  /**
058   * PMD XML InputStream を CSV OutputStream に変換
059   *
060   * @param input PMD XML レポートの入力ストリーム
061   * @param output CSV レポートの出力ストリーム
062   * @throws IOException XML解析エラーまたはI/O例外の場合
063   */
064  @Override
065  protected void executeInternal(InputStream input, OutputStream output) throws IOException {
066    try {
067      // StreamConverter原則に従った変換処理
068      List<PmdViolation> violations = parseXmlStream(input);
069      generateCsvOutput(violations, output);
070
071    } catch (Exception e) {
072      throw new IOException("Failed to convert PMD XML to CSV: " + e.getMessage(), e);
073    }
074  }
075
076  @Override
077  protected String getCommandDetails() {
078    return "PmdXmlToCsvCommand: Converts PMD XML reports to CSV format using Jackson CsvMapper";
079  }
080
081  /**
082   * PMD XML ストリームからバイオレーション情報を解析
083   *
084   * @param input PMD XML入力ストリーム
085   * @return 解析されたバイオレーションのリスト
086   * @throws Exception XML解析エラーの場合
087   */
088  private List<PmdViolation> parseXmlStream(InputStream input) throws Exception {
089    DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
090    Document doc = builder.parse(input);
091
092    NodeList fileNodes = doc.getElementsByTagName("file");
093    List<PmdViolation> violations = new ArrayList<>();
094
095    for (int i = 0; i < fileNodes.getLength(); i++) {
096      Element fileElement = (Element) fileNodes.item(i);
097      String fileName = fileElement.getAttribute("name");
098
099      NodeList violationNodes = fileElement.getElementsByTagName("violation");
100      for (int j = 0; j < violationNodes.getLength(); j++) {
101        Element violationElement = (Element) violationNodes.item(j);
102
103        PmdViolation violation =
104            new PmdViolation(
105                extractRelativePath(fileName),
106                Integer.parseInt(violationElement.getAttribute("beginline")),
107                violationElement.getAttribute("rule"),
108                violationElement.getAttribute("ruleset"),
109                Integer.parseInt(violationElement.getAttribute("priority")),
110                violationElement.getTextContent().trim(),
111                violationElement.getAttribute("class"),
112                violationElement.getAttribute("method"),
113                violationElement.getAttribute("variable"));
114        violations.add(violation);
115      }
116    }
117
118    return violations;
119  }
120
121  /** StreamConverterプロジェクト内の相対パスを抽出 */
122  private String extractRelativePath(String fullPath) {
123    int index = fullPath.indexOf("streamconverter-");
124    return index != -1 ? fullPath.substring(index) : fullPath;
125  }
126
127  /**
128   * Jackson CsvMapper を使用してCSV出力を生成
129   *
130   * @param violations バイオレーション情報
131   * @param output 出力ストリーム
132   * @throws IOException CSV生成エラーの場合
133   */
134  private void generateCsvOutput(List<PmdViolation> violations, OutputStream output)
135      throws IOException {
136    // CSV スキーマ定義(ヘッダー付き)
137    CsvSchema schema =
138        csvMapper
139            .schemaFor(PmdViolation.class)
140            .withHeader()
141            .withColumnSeparator(',')
142            .withQuoteChar('"')
143            .withEscapeChar('\\')
144            .withLineSeparator("\n");
145
146    // Jackson CsvMapper による型安全なCSV生成 - Listタイプ用のwriter使用
147    csvMapper.writer(schema).writeValue(output, violations);
148  }
149}