001package com.streamconverter.path;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006
007/**
008 * 最小限のCSVPath実装
009 *
010 * <p>CSV列選択のパス一致判定のみに特化したシンプルな設計
011 *
012 * <p>複数の列セレクターをOR条件で判定する機能を提供
013 */
014public class CSVPath extends AbstractPath<Integer> {
015
016  private final List<String> selectors;
017
018  /**
019   * 単一セレクターでCSVPathを作成
020   *
021   * @param selector 列選択子(列名または数値インデックス)
022   * @throws IllegalArgumentException セレクターが不正な場合
023   */
024  public CSVPath(String selector) {
025    super(selector);
026    this.selectors = Collections.singletonList(selector.trim());
027  }
028
029  /**
030   * 複数セレクターでCSVPathを作成(OR条件)
031   *
032   * @param selectorList 列選択子のリスト
033   * @throws IllegalArgumentException セレクターが不正な場合
034   */
035  public CSVPath(List<String> selectorList) {
036    super(String.join(",", selectorList));
037    if (selectorList == null || selectorList.isEmpty()) {
038      throw new IllegalArgumentException("Selector list cannot be null or empty");
039    }
040    List<String> temp = new ArrayList<>();
041    for (String sel : selectorList) {
042      temp.add(sel.trim());
043    }
044    this.selectors = Collections.unmodifiableList(temp);
045  }
046
047  @Override
048  protected void validateAndNormalize(String rawSelector) {
049    if (isNullOrEmpty(rawSelector)) {
050      throw new IllegalArgumentException("CSV column selector cannot be null or empty");
051    }
052  }
053
054  @Override
055  public boolean matches(Integer columnIndex) {
056    if (columnIndex == null || columnIndex < 0) {
057      return false;
058    }
059
060    // OR条件:いずれかのセレクターがマッチすればtrue
061    for (String selector : selectors) {
062      if (matchesSingleSelector(selector, columnIndex)) {
063        return true;
064      }
065    }
066    return false;
067  }
068
069  /**
070   * 列ヘッダー配列との一致判定(OR条件)
071   *
072   * @param headers CSV列ヘッダー配列
073   * @param targetIndex 対象列のインデックス
074   * @return いずれかのセレクターが一致する場合true
075   */
076  public boolean matches(String[] headers, int targetIndex) {
077    if (headers == null || targetIndex < 0 || targetIndex >= headers.length) {
078      return false;
079    }
080
081    // OR条件:いずれかのセレクターがマッチすればtrue
082    for (String selector : selectors) {
083      if (matchesSingleSelector(selector, headers, targetIndex)) {
084        return true;
085      }
086    }
087    return false;
088  }
089
090  /**
091   * マッチするすべての列インデックスを取得(Don't Ask Tell準拠)
092   *
093   * @param headers CSV列ヘッダー配列
094   * @return マッチした列インデックスのリスト
095   */
096  public List<Integer> findMatchingIndices(String[] headers) {
097    List<Integer> matchingIndices = new ArrayList<>();
098    if (headers == null) {
099      return matchingIndices;
100    }
101
102    for (int i = 0; i < headers.length; i++) {
103      if (matches(headers, i)) {
104        matchingIndices.add(i);
105      }
106    }
107    return matchingIndices;
108  }
109
110  /**
111   * マッチするすべての列インデックスを取得(ヘッダーなしの場合)
112   *
113   * @param totalColumns 総列数
114   * @return マッチした列インデックスのリスト
115   */
116  public List<Integer> findMatchingIndices(int totalColumns) {
117    List<Integer> matchingIndices = new ArrayList<>();
118
119    for (int i = 0; i < totalColumns; i++) {
120      if (matches(i)) {
121        matchingIndices.add(i);
122      }
123    }
124    return matchingIndices;
125  }
126
127  /** 単一セレクターの列インデックス一致判定 */
128  private boolean matchesSingleSelector(String selector, Integer columnIndex) {
129    int parsedIndex = parseAsIndex(selector);
130    if (parsedIndex >= 0) {
131      return parsedIndex == columnIndex;
132    }
133    return false; // 列名指定はヘッダー情報が必要
134  }
135
136  /** 単一セレクターのヘッダー一致判定 */
137  private boolean matchesSingleSelector(String selector, String[] headers, int targetIndex) {
138    int parsedIndex = parseAsIndex(selector);
139    if (parsedIndex >= 0) {
140      // インデックス指定の場合
141      return parsedIndex == targetIndex;
142    } else {
143      // 列名指定の場合
144      return headers[targetIndex].trim().equalsIgnoreCase(selector.trim());
145    }
146  }
147
148  /** 文字列が数値インデックスかどうかを判定 */
149  private static int parseAsIndex(String selector) {
150    if (selector == null || selector.isEmpty()) {
151      return -1;
152    }
153    try {
154      int index = Integer.parseInt(selector.trim());
155      return index >= 0 ? index : -1;
156    } catch (NumberFormatException e) {
157      return -1;
158    }
159  }
160
161  @Override
162  public String toString() {
163    if (selectors.size() == 1) {
164      return selectors.get(0);
165    } else {
166      return String.join(",", selectors);
167    }
168  }
169}