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 this.rules = Collections.unmodifiableList(new ArrayList<>(rules)); 045 } 046 047 @Override 048 public String apply(String input) { 049 String result = input; 050 051 // Apply each rule in sequence 052 for (IRule rule : rules) { 053 result = rule.apply(result); 054 } 055 056 return result; 057 } 058 059 /** 060 * Gets the number of rules in this chain. 061 * 062 * @return number of rules 063 */ 064 public int size() { 065 return rules.size(); 066 } 067 068 /** 069 * Gets an unmodifiable view of the rules in this chain. 070 * 071 * @return unmodifiable list of rules 072 */ 073 public List<IRule> getRules() { 074 return rules; 075 } 076 077 /** 078 * Creates a builder for configuring the ChainRule. 079 * 080 * @return new builder instance 081 */ 082 public static Builder builder() { 083 return new Builder(); 084 } 085 086 /** 087 * Creates a ChainRule with the specified rules. 088 * 089 * @param rules rules to chain together 090 * @return new ChainRule instance 091 * @throws IllegalArgumentException if rules array is null or empty 092 */ 093 public static ChainRule of(IRule... rules) { 094 if (rules == null || rules.length == 0) { 095 throw new IllegalArgumentException("Chain must contain at least one rule"); 096 } 097 return new Builder().addRules(rules).build(); 098 } 099 100 /** 101 * Creates a ChainRule with the specified rules. 102 * 103 * @param rules list of rules to chain together 104 * @return new ChainRule instance 105 * @throws IllegalArgumentException if rules list is null or empty 106 */ 107 public static ChainRule of(List<IRule> rules) { 108 return new Builder().addRules(rules).build(); 109 } 110 111 /** Builder class for ChainRule configuration. */ 112 public static class Builder { 113 private final List<IRule> rules = new ArrayList<>(); 114 115 /** 116 * Adds a rule to the end of the chain. 117 * 118 * @param rule rule to add (null rules are ignored) 119 * @return this builder 120 */ 121 public Builder addRule(IRule rule) { 122 if (rule != null) { 123 this.rules.add(rule); 124 } 125 return this; 126 } 127 128 /** 129 * Adds multiple rules to the end of the chain. 130 * 131 * @param rules rules to add (null rules are ignored) 132 * @return this builder 133 */ 134 public Builder addRules(IRule... rules) { 135 if (rules != null) { 136 for (IRule rule : rules) { 137 addRule(rule); 138 } 139 } 140 return this; 141 } 142 143 /** 144 * Adds multiple rules to the end of the chain. 145 * 146 * @param rules list of rules to add (null rules are ignored) 147 * @return this builder 148 */ 149 public Builder addRules(List<IRule> rules) { 150 if (rules != null) { 151 for (IRule rule : rules) { 152 addRule(rule); 153 } 154 } 155 return this; 156 } 157 158 /** 159 * Inserts a rule at the specified position in the chain. 160 * 161 * @param index position to insert at 162 * @param rule rule to insert 163 * @return this builder 164 * @throws IndexOutOfBoundsException if index is out of range 165 */ 166 public Builder insertRule(int index, IRule rule) { 167 if (rule != null) { 168 this.rules.add(index, rule); 169 } 170 return this; 171 } 172 173 /** 174 * Removes all rules from the chain. 175 * 176 * @return this builder 177 */ 178 public Builder clear() { 179 this.rules.clear(); 180 return this; 181 } 182 183 /** 184 * Gets the current number of rules in the builder. 185 * 186 * @return number of rules 187 */ 188 public int size() { 189 return rules.size(); 190 } 191 192 /** 193 * Builds the ChainRule with current configuration. 194 * 195 * @return configured rule instance 196 * @throws IllegalArgumentException if no rules have been added 197 */ 198 public ChainRule build() { 199 if (rules.isEmpty()) { 200 throw new IllegalArgumentException("Chain must contain at least one rule"); 201 } 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}