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}