001package com.streamconverter.command.rule.impl.composite;
002
003import com.streamconverter.command.rule.IRule;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.List;
007
008/**
009 * Chains multiple transformation rules to be applied in sequence.
010 *
011 * <p>This rule allows combining multiple {@link IRule} instances to create complex transformations
012 * by applying them one after another. The output of each rule becomes the input for the next rule
013 * in the chain.
014 *
015 * <p>Examples:
016 *
017 * <pre>{@code
018 * // Trim whitespace then convert to snake_case
019 * IRule chainedRule = ChainRule.builder()
020 *     .addRule(new TrimRule())
021 *     .addRule(CamelToSnakeCaseRule.create())
022 *     .build();
023 *
024 * // Multiple transformations
025 * IRule complexRule = ChainRule.of(
026 *     new TrimRule(),
027 *     new LowerCaseRule(),
028 *     CamelToSnakeCaseRule.create()
029 * );
030 * }</pre>
031 *
032 * <p>Thread Safety: This class is thread-safe as long as the individual rules in the chain are
033 * thread-safe.
034 *
035 * @since 1.0
036 */
037public class ChainRule implements IRule {
038
039  /** List of rules to apply in sequence */
040  private final List<IRule> rules;
041
042  /** Private constructor for builder pattern */
043  private ChainRule(List<IRule> rules) {
044    if (rules == null || rules.isEmpty()) {
045      throw new IllegalArgumentException("Chain must contain at least one rule");
046    }
047    this.rules = Collections.unmodifiableList(new ArrayList<>(rules));
048  }
049
050  @Override
051  public String apply(String input) {
052    String result = input;
053
054    // Apply each rule in sequence
055    for (IRule rule : rules) {
056      result = rule.apply(result);
057    }
058
059    return result;
060  }
061
062  /**
063   * Gets the number of rules in this chain.
064   *
065   * @return number of rules
066   */
067  public int size() {
068    return rules.size();
069  }
070
071  /**
072   * Gets an unmodifiable view of the rules in this chain.
073   *
074   * @return unmodifiable list of rules
075   */
076  public List<IRule> getRules() {
077    return rules;
078  }
079
080  /**
081   * Creates a builder for configuring the ChainRule.
082   *
083   * @return new builder instance
084   */
085  public static Builder builder() {
086    return new Builder();
087  }
088
089  /**
090   * Creates a ChainRule with the specified rules.
091   *
092   * @param rules rules to chain together
093   * @return new ChainRule instance
094   * @throws IllegalArgumentException if rules array is null or empty
095   */
096  public static ChainRule of(IRule... rules) {
097    if (rules == null || rules.length == 0) {
098      throw new IllegalArgumentException("Chain must contain at least one rule");
099    }
100    return new Builder().addRules(rules).build();
101  }
102
103  /**
104   * Creates a ChainRule with the specified rules.
105   *
106   * @param rules list of rules to chain together
107   * @return new ChainRule instance
108   * @throws IllegalArgumentException if rules list is null or empty
109   */
110  public static ChainRule of(List<IRule> rules) {
111    return new Builder().addRules(rules).build();
112  }
113
114  /** Builder class for ChainRule configuration. */
115  public static class Builder {
116    private final List<IRule> rules = new ArrayList<>();
117
118    /**
119     * Adds a rule to the end of the chain.
120     *
121     * @param rule rule to add (null rules are ignored)
122     * @return this builder
123     */
124    public Builder addRule(IRule rule) {
125      if (rule != null) {
126        this.rules.add(rule);
127      }
128      return this;
129    }
130
131    /**
132     * Adds multiple rules to the end of the chain.
133     *
134     * @param rules rules to add (null rules are ignored)
135     * @return this builder
136     */
137    public Builder addRules(IRule... rules) {
138      if (rules != null) {
139        for (IRule rule : rules) {
140          addRule(rule);
141        }
142      }
143      return this;
144    }
145
146    /**
147     * Adds multiple rules to the end of the chain.
148     *
149     * @param rules list of rules to add (null rules are ignored)
150     * @return this builder
151     */
152    public Builder addRules(List<IRule> rules) {
153      if (rules != null) {
154        for (IRule rule : rules) {
155          addRule(rule);
156        }
157      }
158      return this;
159    }
160
161    /**
162     * Inserts a rule at the specified position in the chain.
163     *
164     * @param index position to insert at
165     * @param rule rule to insert
166     * @return this builder
167     * @throws IndexOutOfBoundsException if index is out of range
168     */
169    public Builder insertRule(int index, IRule rule) {
170      if (rule != null) {
171        this.rules.add(index, rule);
172      }
173      return this;
174    }
175
176    /**
177     * Removes all rules from the chain.
178     *
179     * @return this builder
180     */
181    public Builder clear() {
182      this.rules.clear();
183      return this;
184    }
185
186    /**
187     * Gets the current number of rules in the builder.
188     *
189     * @return number of rules
190     */
191    public int size() {
192      return rules.size();
193    }
194
195    /**
196     * Builds the ChainRule with current configuration.
197     *
198     * @return configured rule instance
199     * @throws IllegalArgumentException if no rules have been added
200     */
201    public ChainRule build() {
202      return new ChainRule(rules);
203    }
204  }
205
206  @Override
207  public String toString() {
208    return String.format("ChainRule{rules=%d, chain=%s}", rules.size(), rules);
209  }
210
211  @Override
212  public boolean equals(Object obj) {
213    if (this == obj) return true;
214    if (obj == null || getClass() != obj.getClass()) return false;
215    ChainRule chainRule = (ChainRule) obj;
216    return rules.equals(chainRule.rules);
217  }
218
219  @Override
220  public int hashCode() {
221    return rules.hashCode();
222  }
223}