001package org.javasimon.utils; 002 003import java.util.regex.Pattern; 004 005/** 006 * Replacer stores {@code from} regex as pattern and its {@link #process} method than returns 007 * string with all {@code from} replaced with {@code to}. Using the replacer should be faster 008 * because the regex is compiled only once. {@link java.util.regex.Pattern#matcher(CharSequence)} 009 * and {@link java.util.regex.Matcher#replaceAll(String)} is used internally, hence {@code to} 010 * parameter is regex as well and can reference matched group from the {@code from} String. 011 * <p/> 012 * Behavior can be further modified by {@link Modificator}s. 013 * 014 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a> 015 */ 016public final class Replacer { 017 018 private final Pattern from; 019 private String to; 020 private boolean repeatUntilUnchanged; 021 private boolean ignoreCase; 022 023 /** 024 * Creates the replacer with from->to regex specification. 025 * 026 * @param from replaced regex 027 * @param to replacement string 028 * @param modificators list of modificators of the behavior or pattern treatment 029 */ 030 public Replacer(final String from, final String to, Modificator... modificators) { 031 processModificators(modificators); 032 033 this.from = ignoreCase ? Pattern.compile(from, Pattern.CASE_INSENSITIVE) : Pattern.compile(from); 034 this.to = to; 035 } 036 037 private void processModificators(Modificator... modificators) { 038 for (Modificator modificator : modificators) { 039 switch (modificator) { 040 case IGNORE_CASE: 041 ignoreCase = true; 042 break; 043 case REPEAT_UNTIL_UNCHANGED: 044 repeatUntilUnchanged = true; 045 break; 046 default: 047 } 048 } 049 } 050 051 /** 052 * Returns replacement string. 053 * 054 * @return replacement string 055 */ 056 public String getTo() { 057 return to; 058 } 059 060 /** 061 * Sets replacement string - this can be changed anytime with any subsequent {@link #process(String)} calls 062 * reflecting this change immediatelly. 063 * 064 * @param to new replacement string 065 */ 066 public void setTo(String to) { 067 this.to = to; 068 } 069 070 /** 071 * Processes input, replaces all as expected and returns the result. 072 * 073 * @param in input string 074 * @return replaced string 075 */ 076 public String process(final String in) { 077 String retVal = in; 078 String old; 079 do { 080 old = retVal; 081 retVal = from.matcher(retVal).replaceAll(to); 082 } while (repeatUntilUnchanged && !old.equals(retVal)); 083 084 return retVal; 085 } 086 087 /** 088 * Flags for modifying the default replacer behavior. 089 */ 090 public enum Modificator { 091 /** 092 * Enforces repetitive replacer application until there is no other change to perform. 093 * Attention should be paid to provide from/to combination that converges to the final 094 * result otherwise the process method may trigger infinite loop and/or excessive 095 * memory consumption. 096 */ 097 REPEAT_UNTIL_UNCHANGED, 098 099 /** 100 * Applies {@link Pattern#CASE_INSENSITIVE} flag to the pattern. 101 */ 102 IGNORE_CASE 103 } 104 105 @Override 106 public String toString() { 107 return "Replacer{" + 108 "from=" + from + 109 ", to='" + to + '\'' + 110 ", repeatUntilUnchanged=" + repeatUntilUnchanged + 111 ", ignoreCase=" + ignoreCase + 112 '}'; 113 } 114}