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}