001/** 002 * Copyright (c) 2001, Sergey A. Samokhodkin 003 * All rights reserved. 004 * <p> 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * <p> 008 * - Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * - Redistributions in binary form 011 * must reproduce the above copyright notice, this list of conditions and the following 012 * disclaimer in the documentation and/or other materials provided with the distribution. 013 * - Neither the name of jregex nor the names of its contributors may be used 014 * to endorse or promote products derived from this software without specific prior 015 * written permission. 016 * <p> 017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 018 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 019 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 020 * IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 021 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 022 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 023 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 024 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 025 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 026 * 027 * @version 1.2_01 028 */ 029 030package regexodus; 031 032import java.io.IOException; 033import java.io.Reader; 034import java.io.Serializable; 035import java.io.Writer; 036 037/** 038 * <b>The Replacer class</b> suggests some methods to replace occurrences of a pattern 039 * either by a result of evaluation of a perl-like expression, or by a plain string, 040 * or according to a custom substitution model, provided as a Substitution interface implementation.<br> 041 * A Replacer instance may be obtained either using Pattern.replacer(...) method, or by constructor:<pre> 042 * Pattern p=new Pattern("\\w+"); 043 * Replacer perlExpressionReplacer=p.replacer("[$&]"); 044 * //or another way to do the same 045 * Substitution myOwnModel=new Substitution(){ 046 * public void appendSubstitution(MatchResult match,TextBuffer tb){ 047 * tb.append('['); 048 * match.getGroup(MatchResult.MATCH,tb); 049 * tb.append(']'); 050 * } 051 * } 052 * Replacer myVeryOwnReplacer=new Replacer(p,myOwnModel); 053 * </pre> 054 * The second method is much more verbose, but gives more freedom. 055 * To perform a replacement call replace(someInput):<pre> 056 * System.out.print(perlExpressionReplacer.replace("All your base ")); 057 * System.out.println(myVeryOwnReplacer.replace("are belong to us")); 058 * //result: "[All] [your] [base] [are] [belong] [to] [us]" 059 * </pre> 060 * This code was mostly written in 2001, I hope the reference isn't too outdated... 061 * @see Substitution 062 * @see PerlSubstitution 063 * @see Replacer#Replacer(regexodus.Pattern, regexodus.Substitution) 064 */ 065 066public class Replacer implements Serializable { 067 private static final long serialVersionUID = 2528136757932720807L; 068 069 private Pattern pattern; 070 private Substitution substitution; 071 072 /** 073 * Unlikely to be used directly. 074 * @param pattern a regexodus.Pattern that determines what should be replaced 075 * @param substitution an implementation of the Substitution interface, which allows custom replacement behavior 076 */ 077 public Replacer(Pattern pattern, Substitution substitution) { 078 this.pattern = pattern; 079 this.substitution = substitution; 080 } 081 082 /** 083 * Constructs a Replacer from a Pattern and a String to replace occurrences of the Pattern with. 084 * @param pattern a regexodus.Pattern that determines what should be replaced 085 * @param substitution a String that will be used to replace occurrences of the Pattern 086 */ 087 public Replacer(Pattern pattern, String substitution) { 088 this(pattern, substitution, true); 089 } 090 091 public Replacer(Pattern pattern, String substitution, boolean isPerlExpr) { 092 this.pattern = pattern; 093 this.substitution = isPerlExpr ? new PerlSubstitution(substitution) : 094 new DummySubstitution(substitution); 095 } 096 097 public void setSubstitution(String s, boolean isPerlExpr) { 098 substitution = isPerlExpr ? new PerlSubstitution(s) : 099 new DummySubstitution(s); 100 } 101 102 /** 103 * Takes all instances in text of the Pattern this was constructed with, and replaces them with substitution. 104 * @param text a String, StringBuilder, or other CharSequence that may contain the text to replace 105 * @return the post-replacement text 106 */ 107 public String replace(CharSequence text) { 108 TextBuffer tb = wrap(new StringBuilder(text.length())); 109 replace(pattern.matcher(text), substitution, tb); 110 return tb.toString(); 111 } 112 113 public String replace(char[] chars, int off, int len) { 114 TextBuffer tb = wrap(new StringBuilder(len)); 115 replace(pattern.matcher(chars, off, len), substitution, tb); 116 return tb.toString(); 117 } 118 119 public String replace(MatchResult res, int group) { 120 TextBuffer tb = wrap(new StringBuilder()); 121 replace(pattern.matcher(res, group), substitution, tb); 122 return tb.toString(); 123 } 124 125 @GwtIncompatible 126 public String replace(Reader text, int length) throws IOException { 127 TextBuffer tb = wrap(new StringBuilder(length >= 0 ? length : 0)); 128 replace(pattern.matcher(text, length), substitution, tb); 129 return tb.toString(); 130 } 131 132 /** 133 * Takes all occurrences of the pattern this was constructed with in text and replaces them with the substitution. 134 * Appends the replaced text into sb. 135 * @param text a String, StringBuilder, or other CharSequence that may contain the text to replace 136 * @param sb the StringBuilder to append the result into 137 * @return the number of individual replacements performed; the results are applied to sb 138 */ 139 public int replace(CharSequence text, StringBuilder sb) { 140 return replace(pattern.matcher(text), substitution, wrap(sb)); 141 } 142 143 /** 144 */ 145 public int replace(char[] chars, int off, int len, StringBuilder sb) { 146 return replace(chars, off, len, wrap(sb)); 147 } 148 149 /** 150 */ 151 public int replace(MatchResult res, int group, StringBuilder sb) { 152 return replace(res, group, wrap(sb)); 153 } 154 155 /** 156 */ 157 public int replace(MatchResult res, String groupName, StringBuilder sb) { 158 return replace(res, groupName, wrap(sb)); 159 } 160 161 @GwtIncompatible 162 public int replace(Reader text, int length, StringBuilder sb) throws IOException { 163 return replace(text, length, wrap(sb)); 164 } 165 166 /** 167 */ 168 public int replace(CharSequence text, TextBuffer dest) { 169 return replace(pattern.matcher(text), substitution, dest); 170 } 171 172 /** 173 */ 174 private int replace(char[] chars, int off, int len, TextBuffer dest) { 175 return replace(pattern.matcher(chars, off, len), substitution, dest); 176 } 177 178 /** 179 */ 180 private int replace(MatchResult res, int group, TextBuffer dest) { 181 return replace(pattern.matcher(res, group), substitution, dest); 182 } 183 184 /** 185 */ 186 private int replace(MatchResult res, String groupName, TextBuffer dest) { 187 return replace(pattern.matcher(res, groupName), substitution, dest); 188 } 189 190 @GwtIncompatible 191 private int replace(Reader text, int length, TextBuffer dest) throws IOException { 192 return replace(pattern.matcher(text, length), substitution, dest); 193 } 194 195 /** 196 * Replaces all occurrences of a matcher's pattern in a matcher's target 197 * by a given substitution appending the result to a buffer.<br> 198 * The substitution starts from current matcher's position, current match 199 * not included. 200 */ 201 public static int replace(Matcher m, Substitution substitution, TextBuffer dest) { 202 boolean firstPass = true; 203 int c = 0; 204 while (m.find()) { 205 if (m.end() == 0 && !firstPass) continue; //allow to replace at "^" 206 if (m.start() > 0) m.getGroup(MatchResult.PREFIX, dest); 207 substitution.appendSubstitution(m, dest); 208 c++; 209 m.setTarget(m, MatchResult.SUFFIX); 210 firstPass = false; 211 } 212 m.getGroup(MatchResult.TARGET, dest); 213 return c; 214 } 215 216 /** 217 * Replaces the first n occurrences of a matcher's pattern, where n is equal to count, 218 * in a matcher's target by a given substitution, appending the result to a buffer. 219 * <br> 220 * The substitution starts from current matcher's position, current match not included. 221 */ 222 public static int replace(Matcher m, Substitution substitution, TextBuffer dest, int count) { 223 boolean firstPass = true; 224 int c = 0; 225 while (c < count && m.find()) { 226 if (m.end() == 0 && !firstPass) continue; //allow to replace at "^" 227 if (m.start() > 0) m.getGroup(MatchResult.PREFIX, dest); 228 substitution.appendSubstitution(m, dest); 229 c++; 230 m.setTarget(m, MatchResult.SUFFIX); 231 firstPass = false; 232 } 233 m.getGroup(MatchResult.TARGET, dest); 234 return c; 235 } 236 237 @GwtIncompatible 238 private static int replace(Matcher m, Substitution substitution, Writer out) throws IOException { 239 try { 240 return replace(m, substitution, wrap(out)); 241 } catch (WriteException e) { 242 throw e.reason; 243 } 244 } 245 246 @GwtIncompatible 247 public void replace(CharSequence text, Writer out) throws IOException { 248 replace(pattern.matcher(text), substitution, out); 249 } 250 251 @GwtIncompatible 252 public void replace(char[] chars, int off, int len, Writer out) throws IOException { 253 replace(pattern.matcher(chars, off, len), substitution, out); 254 } 255 256 @GwtIncompatible 257 public void replace(MatchResult res, int group, Writer out) throws IOException { 258 replace(pattern.matcher(res, group), substitution, out); 259 } 260 261 @GwtIncompatible 262 public void replace(MatchResult res, String groupName, Writer out) throws IOException { 263 replace(pattern.matcher(res, groupName), substitution, out); 264 } 265 266 @GwtIncompatible 267 public void replace(Reader in, int length, Writer out) throws IOException { 268 replace(pattern.matcher(in, length), substitution, out); 269 } 270 271 private static class DummySubstitution implements Substitution { 272 String str; 273 274 DummySubstitution(String s) { 275 str = s; 276 } 277 278 public void appendSubstitution(MatchResult match, TextBuffer res) { 279 if (str != null) res.append(str); 280 } 281 } 282 283 public static TextBuffer wrap(final StringBuilder sb) { 284 return new TextBuffer() { 285 public void append(char c) { 286 sb.append(c); 287 } 288 289 public void append(char[] chars, int start, int len) { 290 sb.append(chars, start, len); 291 } 292 293 public void append(String s) { 294 sb.append(s); 295 } 296 297 public String toString() { 298 return sb.toString(); 299 } 300 }; 301 } 302 303 @GwtIncompatible 304 private static TextBuffer wrap(final Writer writer) { 305 return new TextBuffer() { 306 public void append(char c) { 307 try { 308 writer.write(c); 309 } catch (IOException e) { 310 throw new WriteException(e); 311 } 312 } 313 314 public void append(char[] chars, int off, int len) { 315 try { 316 writer.write(chars, off, len); 317 } catch (IOException e) { 318 throw new WriteException(e); 319 } 320 } 321 322 public void append(String s) { 323 try { 324 writer.write(s); 325 } catch (IOException e) { 326 throw new WriteException(e); 327 } 328 } 329 }; 330 } 331 332 private static class WriteException extends RuntimeException { 333 IOException reason; 334 335 WriteException(IOException io) { 336 reason = io; 337 } 338 } 339 340 @Override 341 public boolean equals(Object o) { 342 if (this == o) return true; 343 if (o == null || getClass() != o.getClass()) return false; 344 345 Replacer replacer = (Replacer) o; 346 347 return pattern != null ? pattern.equals(replacer.pattern) : replacer.pattern == null && (substitution != null ? substitution.equals(replacer.substitution) : replacer.substitution == null); 348 349 } 350 351 @Override 352 public int hashCode() { 353 int result = pattern != null ? pattern.hashCode() : 0; 354 result = 31 * result + (substitution != null ? substitution.hashCode() : 0); 355 return result; 356 } 357 358 @Override 359 public String toString() { 360 return "Replacer{" + 361 "pattern=" + pattern + 362 ", substitution=" + substitution + 363 '}'; 364 } 365}