/*license*\ XBN-Java: Copyright (C) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com) This software is dual-licensed under the: - Lesser General Public License (LGPL) version 3.0 or, at your option, any later version; - Apache Software License (ASL) version 2.0. Either license may be applied at your discretion. More information may be found at - http://en.wikipedia.org/wiki/Multi-licensing. The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at: - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt \*license*/ package com.github.xbn.regexutil; import com.github.xbn.text.padchop.z.VzblPadChop_Cfg; import com.github.xbn.analyze.validate.ValidatorComposer; import com.github.xbn.analyze.validate.ValueValidator; import com.github.xbn.io.Debuggable; import com.github.xbn.io.IOUtil; import com.github.xbn.io.RTIOException; import com.github.xbn.io.SimpleDebuggable; import com.github.xbn.io.TextAppenter; import com.github.xbn.lang.Copyable; import com.github.xbn.lang.CrashIfObject; import com.github.xbn.lang.ObjectOrCrashIfNull; import com.github.xbn.lang.RuleType; import com.github.xbn.lang.ToStringAppendable; import com.github.xbn.text.padchop.VzblPadChop; import com.github.xbn.regexutil.z.RegexReplacer_Fieldable; import java.io.IOException; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; /** <p>For regular expression replacements where each match can be individually manipulated. The idea for this is based on in <code>e.util.{@link com.github.xbn.regexutil.Rewriter Rewriter}</code>, with the additions of distinguishing between "<a href="#indirect">indirect</a>" and "<a href="#direct">direct</a>" replacements, and the ability to choose exactly which matches should be replaced (see "Which terms" in the below list).</p> <A NAME="cfg"></a><h3>Builder Configuration: {@link com.github.xbn.regexutil.z.RegexReplacer_Cfg RegexReplacer_Cfg}</h3> <p><ul> <li><b>Search terms:</b><ul> <li><b>Find what and direct replace-with:</b> <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#direct(Pattern, Object) direct}(p,O)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#direct(String, Object) direct}(s,O)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#direct(String, int, Object) direct}(s,i,O)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#directLiteral(String, Object) directLiteral}(s,O)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#directReplacement(Object) directReplacement}(O)</code></li> <li><b>Find what:</b> <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#findWhat(Pattern) pattern}(p)</code> ,<code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#findWhat(String) findWhat}(s)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#findWhat(String, int) findWhat}(s,i)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#findWhatLiteral(String) findWhatLiteral}(s)</code></li> </ul></li> <li><b>Which terms:</b><ul> <li><b>Match numbers:</b> <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#matchNumbers(ValueValidator) matchNumbers}(vv)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#matchRange(LengthInRange) matchRange}(lir)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#matchRange(int, int) matchRange}(i,i)</code></li> <li><b>Until:</b> <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#until() until}()</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#until(int) until}(i)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#until(int, MaxUntilLoopsExceeded) until}(i,muxi)</code></li> <li><b>Other:</b> <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#all() all}()</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#first() first}()</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#lookingAt() lookingAt}()</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#matches() matches}()</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#replaceWhatNotMatchNums(ReplacedInEachInput) replaceWhatNotMatchNums}(rw)</code></li> </ul></li> <li><b>Other:</b> <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#chainID(boolean, Object) chainID}(b,o)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#debugTo(Appendable) debugTo}(apbl)</code>, <code>{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#reset() reset}()</code></li> </ul></p> <h3>Example: Direct replacement: Regex</h3> {@.codelet.and.out com.github.xbn.examples.regexutil.RegexReplacerDirectXmpl%eliminateCommentBlocksAndPackageDecl()} <h3>Example: Direct replacement: Literal</h3> {@.codelet.and.out com.github.xbn.examples.regexutil.RegexReplacerLiteralXmpl%eliminateCommentBlocksAndPackageDecl()} </li> </ul></p> <A NAME="indirect"></a><h4><code>[<a href="#skip-navbar_top">top </a>]</code>   <u>RegexReplacer: Definition: Indirect</u></h4> <p><i>Indirect replacements are made with {@link IndirectRegexReplacer}.</i></p> <p>The function at the heart of {@code RegexReplacer} is {@link #getIndirectReplacement() getIndirectReplacement}{@code ()}, and is normally overridden in an inner class. It is the equivalent of {@link Rewriter}'s {@link com.github.xbn.regexutil.Rewriter#replacement() replacement}{@code ()}</p> <p>The advantage of indirect replacements is that each capture-group can be manipulated (such as changed to upper-case in this example).</p> {@.codelet.and.out com.github.xbn.examples.regexutil.RegexReplacerIndirectXmpl1of2%eliminateCommentBlocksAndPackageDecl()} <p>Internally, for each match, indirect replacements are done in two steps:<ol> <li>Everything up-to-but-not-including the next match is appended to the {@code StringBuffer}: <br/>     {@code <i>[Matcher]</i>.appendReplacement(to_appendTo, "")} <br/>The match is "replaced" with <i>nothing</i>--the empty-string.</li> <li>The indirect replacement is then appended to the {@code StringBuffer} with <br/>     {@code to_appendTo.append(getIndirectReplacement())}</li> </ol></p> <A NAME="direct"></a><h4><code>[<a href="#skip-navbar_top">top</a>]</code>   <u>RegexReplacer: Definition: Direct</u></h4> <p>When capture groups do not need to be manipulated, the {@code Matcher} can make the replacement itself--"directly". Internally, this is equivalent to the above step one, changed to <br/>     {@code <i>[Matcher]</i>.appendReplacement(to_appendTo, <i>[the-replacement-string]</i>)}.</p> <A NAME="whichmatches"></a><h3><code>[<a href="#skip-navbar_top">top</a>]</code>   Choosing which matches to replace</h3> {@.codelet.and.out com.github.xbn.examples.regexutil.RegexReplacerSpcfcTermsXmpl%eliminateCommentBlocksAndPackageDecl()} <p>Here is the example code from {@code Rewriter}, which runs against {@code RegexReplacer} after only changing function-names:</p> {@.codelet.and.out com.github.xbn.examples.regexutil.RegexReplacerXmplsFromRewriter%eliminateCommentBlocksAndPackageDecl()} * @since 0.1.0 * @author Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://xbnjava.aliteralmind.com">{@code http://xbnjava.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/xbnjava">{@code https://github.com/aliteralmind/xbnjava}</a> **/ public class RegexReplacer extends SimpleDebuggable implements Debuggable, Copyable, PatternHaser, ToStringAppendable, RegexReplacer_Fieldable { //config: mutable private final SimplePatternHaser sph ; //Composition for implementing // PatternHaser private Object oRWDrct ; //Direct replace-with string //config: immutable private final ReplacedInEachInput erw ; //What is replaced? private final ValueValidator<Integer> vvMatchNum ; //If replacing MATCH_NUMBERS private final int iMaxUntilLoops; //If replacing UNTIL private final boolean bMaxUCrash ; //If replacing UNTIL //state private Matcher m ; //Reusable matcher private ReplaceWithAppender rwApndr ; //Internal delta private int iRplcs ; //The number of replacements made. //internal private static final VzblPadChop VPC_DBG = new VzblPadChop_Cfg(). cfgChop(true, 100).inMiddle().ddd().endCfg(). trim().build(); /** <p>To replace the match with itself: "{@code $0}"</p> */ public static final String RPLC_WITH_SELF_DLR0 = "$0"; /** <p>The default number of maximum "until" loops: {@code 20}.</p> */ public static final int DEFAULT_MAX_UNTIL_LOOPS = 20; //constructors...START /** <p>Create a new {@code RegexReplacer}, for either direct or indirect replacements.</p> * @param fieldable May not be {@code null}, and must contain valid fields as documented by the <a href="#cfg">builder's setter functions</a>. If <code>fieldable.{@link com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#getDirectReplacement() getDirectReplacement}()</code> is non-{@code null}, then this is an <a href="#direct">direct</a> replacement. If {@code null}, <a href="#indirect">indirect</a>. * @see #RegexReplacer(RegexReplacer) this(rr) @see #RegexReplacer(RegexReplacer, String, String) this(rr,s,s) @see #RegexReplacer(RegexReplacer, String, int, String) this(rr,s,i,s) * @see #RegexReplacer(RegexReplacer, Pattern, String) this(rr,p,s) */ public RegexReplacer(RegexReplacer_Fieldable fieldable) { //Transfer RegexReplacer_CfgForNeeder fields to THIS try { sph = (new SimplePatternHaser()). pattern(fieldable.getPattern(), "fieldable.getPattern()"). matcherUsesFromReplacedInEachInputWhat(fieldable.getReplacedInEachInput()); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(fieldable, "fieldable", null, rx); } vvMatchNum = fieldable.getMatchNumValidator(); iMaxUntilLoops = fieldable.getMaxUntilLoops(); bMaxUCrash = fieldable.doCrashIfMaxUntilExceeded(); erw = fieldable.getReplacedInEachInput(); ValidatorComposer.ciForbiddenRuleType(vvMatchNum, RuleType.IMPOSSIBLE, "fieldable.getMatchNumValidator()", null); if(iMaxUntilLoops < 1) { throw new IllegalArgumentException("fieldable.getMaxUntilLoops() (" + iMaxUntilLoops + ") is less than one."); } findWhat(fieldable.getPattern()); replaceWith(fieldable.getDirectReplacement()); setDebug(fieldable.getDebugApbl(), (fieldable.getDebugApbl() != null)); iRplcs = 0; } /** <p>Create a new instance as a duplicate of another--with the same search-terms, and the same direct-or-indirect-ness.</p> * @param to_copy May not be <code>null</code>. * @see <a href="#direct">Definition: direct replacement</a> * @see <a href="#indirect">Definition: indirect replacement</a> * @see #RegexReplacer(RegexReplacer, Pattern, String) this(rr,p,s) */ public RegexReplacer(RegexReplacer to_copy) { this(true, to_copy, null, null); } /** <p>Create a new instance as a duplicate of another, with new search terms, but the same direct-or-indirect-ness.</p> * <p>Equal to <br/>     <code>{@link #RegexReplacer(RegexReplacer, Pattern, String) this}(to_copy, {@link com.github.xbn.regexutil.NewPatternFor NewPatternFor}.{@link com.github.xbn.regexutil.NewPatternFor#regex(String, String) regex}(findWhat_regex), new_directRplcWith)</code></p> * @param findWhat_regex May not be <code>null</code>. */ public RegexReplacer(RegexReplacer to_copy, String findWhat_regex, String new_directRplcWith) { this(to_copy, NewPatternFor.regex(findWhat_regex, "findWhat_regex"), new_directRplcWith); } /** <p>Create a new instance as a duplicate of another, with new search terms, but the same direct-or-indirect-ness.</p> * <p>Equal to <br/>     <code>{@link #RegexReplacer(RegexReplacer, Pattern, String) this}(to_copy, {@link com.github.xbn.regexutil.NewPatternFor NewPatternFor}.{@link NewPatternFor#regex(String, int, String) regex}(findWhat_regex, bit_flags), new_directRplcWith)</code></p> * @param findWhat_regex May not be <code>null</code>. */ public RegexReplacer(RegexReplacer to_copy, String findWhat_regex, int bit_flags, String new_directRplcWith) { this(to_copy, NewPatternFor.regex(findWhat_regex, bit_flags, "findWhat_regex"), new_directRplcWith); } /** <p>Create a new instance as a duplicate of another, with new search terms, but the same direct-or-indirect-ness.</p> <p>To duplicate a <code>RegexReplacer</code> entirely (using the same search terms, and the same <a href="#direct">direct</a>-or-<a href="#indirect">indirect</a>-ness), use {@link #getObjectCopy() getObjectCopy}{@code ()}.</p> * @param to_copy May not be <code>null</code>. * @param new_findWhat May not be <code>null</code>. Get with {@link #getPattern() getPattern}{@code ()}. * @param new_directRplcWith If {@code null}, then <code>to_copy.{@link #getDirectReplacement() getDirectReplacement}()</code> is used. * @see #RegexReplacer(RegexReplacer_Fieldable) this(rr_f) * @see #RegexReplacer(RegexReplacer) this(rr) */ public RegexReplacer(RegexReplacer to_copy, Pattern new_findWhat, String new_directRplcWith) { this(false, to_copy, new_findWhat, new_directRplcWith); } private RegexReplacer(boolean null_ptrnOk, RegexReplacer to_copy, Pattern new_findWhat, String new_directRplcWith) { super(to_copy); if(new_findWhat == null && !null_ptrnOk) { throw new NullPointerException("new_findWhat"); } erw = to_copy.getReplacedInEachInput(); sph = (new SimplePatternHaser()). //pattern(...). //<--Set in findWhat(p) matcherUsesFromReplacedInEachInputWhat(erw); findWhat(RegexUtil.getPatternCopyOrNewIfNNull(to_copy.getPattern(), new_findWhat, "to_copy.getPattern()")); replaceWith(((new_directRplcWith != null) ? new_directRplcWith //Strings are immutable. Copy unnecessary : to_copy.getDirectReplacement())); @SuppressWarnings("unchecked") ValueValidator<Integer> vvi2 = (ValueValidator<Integer>)ObjectOrCrashIfNull.<ValueValidator>getCopy(to_copy.getMatchNumValidator(), ValueValidator.class, "to_copy.getMatchNumValidator()"); vvMatchNum = vvi2; iMaxUntilLoops = to_copy.getMaxUntilLoops(); bMaxUCrash = to_copy.doCrashIfMaxUntilExceeded(); iRplcs = 0; } //constructors...END /** <p>Define the find-what search-term with a regular-expression string.</p> * @param regex May not be <code>null</code>. * @return <code>{@link #findWhat(Pattern) findWhat}({@link com.github.xbn.regexutil.NewPatternFor NewPatternFor}.{@link NewPatternFor#regex(String, String) regex}(regex))</code> */ public RegexReplacer findWhat(String regex) { return findWhat(NewPatternFor.regex(regex, "regex")); } /** <p>Define the find-what search-term with a regular-expression string.</p> * @param regex May not be <code>null</code>. * @return <code>{@link #findWhat(Pattern) findWhat}({@link com.github.xbn.regexutil.NewPatternFor NewPatternFor}.{@link NewPatternFor#regex(String, int, String) regex}(regex, bit_flags))</code> */ public RegexReplacer findWhat(String regex, int bit_flags) { return findWhat(NewPatternFor.regex(regex, bit_flags, "regex")); } /** <p>Define the find-what search-term with a pattern.</p> * @param pattern_toFind May not be <code>null</code>. Get with {@link #getPattern() getPattern}{@code ()}. * @see #findWhat(String) findWhat(s) * @see #findWhat(String, int) findWhat(s,i) * @see #replaceWith(Object) replaceWith(O) */ public RegexReplacer findWhat(Pattern pattern_toFind) { m = RegexUtil.newMatcherForPatternCINull(pattern_toFind, "", "pattern_toFind"); sph.pattern(pattern_toFind); return this; } /** <p>Declare a new replacement, which will be indirect.</p> * @return <code>{@link #replaceWith(Object) replaceWith}(null)</code> * @see <a href="#indirect">indirect replacement</a> */ public RegexReplacer declareIndirect() { return replaceWith(null); } /** <p>Declare a new direct replacement string.</p> * @param direct_rplcWithOrNull If {@code null}, this is an <a href="#indirect">indirect</a> replacement. If non-{@code null}, <a href="#direct">direct</a> Get with {@link #getDirectReplacement() getDirectReplacement}{@code ()}. * @see #declareIndirect() * @see #findWhat(Pattern) findWhat(p) */ public RegexReplacer replaceWith(Object direct_rplcWithOrNull) { if(direct_rplcWithOrNull == null) { oRWDrct = ""; rwApndr = (new RWAIndirect()); } else { oRWDrct = direct_rplcWithOrNull; rwApndr = (new RWADirect()); } return this; } /** <p>The find what pattern. This is used only for duplicating this {@code RegexReplacer}, and for inclusion in the {@link #toString() toString}{@code ()}.</p> * @see #findWhat(Pattern) */ //Composition implementation: null...START public Pattern getPattern() { return sph.getPattern(); } public int getMatchedIndex() { return sph.getMatchedIndex(); } public int getMatchCount() { return sph.getMatchCount(); } public boolean wasJustMatched() { return sph.wasJustMatched(); } /** <p>The {@code MatcherUses} equivalent, derived from {@code getReplacedInEachInput()}.</p> * @return {@code true} The string is matched as a whole. * @see #getReplacedInEachInput() */ public MatcherUses getMatcherUses() { return sph.getMatcherUses(); } //Composition implementation: null...END /** <p>Which matches should be replaced?.</p> * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#first() RegexReplacer_Cfg#first() * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#lookingAt() RegexReplacer_Cfg#lookingAt() * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#matches() RegexReplacer_Cfg#matches() * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#matchNumbers(ValueValidator) RegexReplacer_Cfg#matchNumbers(vv) * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#all() RegexReplacer_Cfg#all() * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#until(int, MaxUntilLoopsExceeded) RegexReplacer_Cfg#until(i,muxi) * @see #getMatcherUses() */ public ReplacedInEachInput getReplacedInEachInput() { return erw; } /** <p>Determines the specific matches to be replaced, when {@code getReplacedInEachInput().isMatchNumbers()} is {@code true}. The matches-to-replaced is based on their <i>numeric location</i> in the search string.</p> {@.codelet.and.out com.github.xbn.examples.regexutil.RegexReplacerValidTermXmpl%eliminateCommentBlocksAndPackageDecl()} * @return An int-validator, where a <!-- GENERIC PARAMETERS FAIL IN @link --><a href="{@docRoot}/com/github/xbn/analyze/validate/ValueValidator.html#isValid(O)">valid</a> int indicates the match should be replaced. * @see #getReplacedInEachInput() * @see ReplacedInEachInput#isMatchNumbers() * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#matchNumbers(ValueValidator) xbn.regexutil.z.RegexReplacer_CfgForNeeder#matchNumbers(vv) */ public ValueValidator<Integer> getMatchNumValidator() { return vvMatchNum; } /** <p>The maximum number of loops to attempt for "until" replacements. When this maximum is exceeded, the action taken is determined by {@link #doCrashIfMaxUntilExceeded() doCrashIfMaxUntilExceeded}{@code ()}.</p> * @return An int one or greater. * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#until(int, MaxUntilLoopsExceeded) xbn.regexutil.z.RegexReplacer_CfgForNeeder#until(i,muxi) */ public int getMaxUntilLoops() { return iMaxUntilLoops; } /** <p>When the maximum number of "until" replacements is exceeded, should an exception be thrown?.</p> * @return <ul> <li>{@code true}: An {@code IllegalStateException} is thrown</li> <li>{@code false}: Execution stops.</li> </ul> * @see #getMaxUntilLoops() * @see com.github.xbn.regexutil.z.RegexReplacer_CfgForNeeder#until(int, MaxUntilLoopsExceeded) xbn.regexutil.z.RegexReplacer_CfgForNeeder#until(i,muxi) */ public boolean doCrashIfMaxUntilExceeded() { return bMaxUCrash; } /** <p>Get a capture group in the current match--intended for use only by {@code getIndirectReplacement()}.</p> * @param group_num Must be a valid given {@link #getGroupCount() getGroupCount}{@code ()}. * @return <code>[getPattern().matcher(...)].{@link java.util.regex.Matcher#group(int) group}(group_num)</code> * @see #getIndirectReplacement() */ public String getGroup(int group_num) { try { return m.group(group_num); } catch(RuntimeException rx) { RegexUtil.crashIfBadGroupNumberForCount(group_num, m, "group_num", "[getPattern().matcher(s)]"); throw rx; } } /** <p>The number of groups in the current match.--intended for use only by {@code getIndirectReplacement()}.</p> * @return <code><i>[{@link #getPattern() getPattern}().{@link java.util.regex.Pattern#matcher(CharSequence) matcher}(...)]</i>.{@link java.util.regex.Matcher#groupCount groupCount}()</code></i> * @see #getGroup(int) getGroup(i) * @see #getIndirectReplacement() */ public int getGroupCount() { return m.groupCount(); } /** <p>The direct replacement string. See <a href="#direct">direct</a>. This is the string provided directly to <code><i>{@link java.util.regex.Matcher Matcher}</i>.{@link java.util.regex.Matcher#appendReplacement(StringBuffer, String) appendReplacement}(sb,s)</code>, as its second parameter.</p> * @return <ul> <li>A non-{@code null}, non-empty string: For <a href="#direct">direct</a> replacements.</li> <li>"": For <a href="#indirect">indirect</a> replacements.</li> </ul> * @see #getIndirectReplacement() */ public Object getDirectReplacement() { return oRWDrct; } /** <p>The indirect replacement for a single match.</p> <p>In contrast with <a href="#direct">direct</a> replacements, "<a href="#indirect">indirect</a>" replacements allow for each capture group to be individually manipulated:</p> {@.codelet.and.out com.github.xbn.examples.regexutil.RegexReplacerIndirectXmpl2of2%eliminateCommentBlocksAndPackageDecl()} * @exception IllegalStateException This function must be overridden for indirect replacements. * @see #getDirectReplacement() * @see #getGroup(int) getGroup(i) * @see #getGroupCount() */ public String getIndirectReplacement() { throw new IllegalStateException("getIndirectReplacement()"); } /** <p>How many replacements were made during the most-recent call to {@link #appendReplacedX(Appendable, Object) appendReplacedX}{@code (apbl,o)}</p> */ public int getReplacementCount() { return iRplcs; } /** <p>Get the entire replacement result, as configured.</p> * @param text_toRplc May not be {@code null}. * @return {@link #appendReplaced(Appendable, Object) appendReplaced}{@code (new StringBuffer(text_toRplc.length()), text_toRplc).toString()} * @exception RTIOException If an {@link java.io.IOException IOException} is thrown */ public String getReplaced(Object text_toRplc) { String sOrig = null; try { sOrig = text_toRplc.toString(); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(text_toRplc, "text_toRplc", null, rx); } if(isDebugOn()) { getDebugAptr().appentln("<RR> getReplaced(O): text_toRplc=\"" + VPC_DBG.get(sOrig) + "\", this=" + this); } m.reset(sOrig); int iLen = sOrig.length(); sph.declareNotMatched(); if(!m.find()) { if(isDebugOn()) { getDebugAptr().appentln("<RR> Not found"); } //Don't bother creating the StringBuffer, appending text_toRplc to it, //and then returning its toString(). return sOrig; } else { //Do bother. try { return appendReplaced1stFoundX((new StringBuffer(iLen)), sOrig).toString(); } catch(IOException iox) { throw new RTIOException("getReplaced", iox); } } } /** <p>Append the replacement as configured, with a new-line at the end, and with runtime errors only. Intended for debugging and testing.</p> * @return <code>{@link #appendReplacedlns(int, Appendable, Object) appendReplacedln}(1, to_appendTo, text_toRplc)</code> */ public Appendable appendReplacedln(Appendable to_appendTo, Object text_toRplc) { return appendReplacedlns(1, to_appendTo, text_toRplc); } /** <p>Append the replacement as configured, with a new-line at the end, and with runtime errors only. Intended for debugging and testing.</p> * @return <code>{@link #appendReplacedX(Appendable, Object) appendReplacedX}(to_appendTo, text_toRplc)</code> * @exception RTIOException If an {@link java.io.IOException IOException} is thrown * @see #appendReplacedln(Appendable, Object) appendReplacedln(apbl,O) */ public Appendable appendReplacedlns(int newLine_count, Appendable to_appendTo, Object text_toRplc) { try { appendReplacedX(to_appendTo, text_toRplc); return IOUtil.appendNewLinesX(newLine_count, to_appendTo); } catch(IOException iox) { throw new RTIOException("appendReplacedln", iox); } } /** <p>Append the replacement as configured, with runtime errors only.</p> * @return <code>{@link #appendReplacedX(Appendable, Object) appendReplacedX}(to_appendTo, text_toRplc)</code> * @exception RTIOException If an {@link java.io.IOException IOException} is thrown */ public Appendable appendReplaced(Appendable to_appendTo, Object text_toRplc) { try { return appendReplacedX(to_appendTo, text_toRplc); } catch(IOException iox) { throw new RTIOException("appendReplaced", iox); } } /** <p>Append the replacement as configured.</p> * @param to_appendTo May not be <code>null</code> * @return <code>to_appendTo.{@link java.lang.Appendable#append(CharSequence) append}({@link #getReplaced(Object) getReplaced}(text_toRplc))</code>     <i>(<code>{@link java.util.regex.Matcher Matcher}.{@link java.util.regex.Matcher#appendReplacement(StringBuffer, String) appendReplacement}(sb,s)</code> requires a <code>{@link java.lang.StringBuffer StringBuffer}</code>)</i> * @see #getReplacementCount() */ public Appendable appendReplacedX(Appendable to_appendTo, Object text_toRplc) throws IOException { try { return to_appendTo.append(getReplaced(text_toRplc)); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(to_appendTo, "to_appendTo", null, rx); } } /** <p>Get the replacement result, as configured.</p> * @param to_appendTo May not be {@code null}. <i>{@link java.util.regex.Matcher Matcher} requires a {@code StringBuffer}. This should ideally be an {@code Appendable}.</i> * @param text_toRplc May not be {@code null}. * @return <code>to_appendTo</code> * @exception RTIOException If an {@link java.io.IOException IOException} is thrown * @see #getReplaced(Object) getReplaced(cs) * @see #appendReplaced(Appendable, Object) getReplaced(apbl,O) * @see #appendReplacedX(Appendable, Object) appendReplacedX(apbl,O) * @see #appendReplacedln(Appendable, Object) appendReplacedln(apbl,O) * @see #appendReplacedlns(int, Appendable, Object) appendReplacedlns(i,apbl,O) */ public StringBuffer appendReplaced(StringBuffer to_appendTo, Object text_toRplc) { String sOrig = null; try { sOrig = text_toRplc.toString(); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(text_toRplc, "text_toRplc", null, rx); } if(isDebugOn()) { getDebugAptr().appentln("<RR> appendReplaced(sd,O): text_toRplc=\"" + VPC_DBG.get(sOrig) + "\", this=" + this); } m.reset(sOrig); sph.declareNotMatched(); //Finding the first instance here, before calling //appendReplaced1stFound(q,sb,ow) is primarily beneficial for //getReplaced(q) if(!m.find()) { if(isDebugOn()) { getDebugAptr().appentln("<RR> Not found"); } return to_appendTo.append(sOrig); } else { try { return appendReplaced1stFoundX(to_appendTo, sOrig); } catch(IOException iox) { throw new RTIOException("appendReplaced", iox); } } } private void debugFindWhat() { if(isDebugOn()) { getDebugAptr().appentln("<RR> - Found at index " + m.start() + ": \"" + VPC_DBG.get(m.group()) + "\""); } } private StringBuffer appendReplaced1stFoundX(StringBuffer to_appendTo, String text_toRplc) throws IOException { if(getReplacedInEachInput().isMatches()) { if(m.start() == 0 && m.end() == text_toRplc.length()) { sph.matchedIndex(0); rwApndr.appendDirect(m, to_appendTo, getDirectReplacement(), getDebugAptr(), isDebugOn()); //No tail. Matches encompasses the entire string. iRplcs++; } else { to_appendTo.append(text_toRplc); } return to_appendTo; } if(getReplacedInEachInput().isLookingAt()) { if(m.start() == 0 ) { sph.matchedIndex(0); rwApndr.appendDirect(m, to_appendTo, getDirectReplacement(), getDebugAptr(), isDebugOn()); m.appendTail(to_appendTo); iRplcs++; } else { to_appendTo.append(text_toRplc); } return to_appendTo; } if(getReplacedInEachInput().isFirst()) { sph.matchedIndex(m.start()); debugFindWhat(); //Append everything up-to-but-not-including the next //match. Replace it with NOTHING. rwApndr.appendDirect(m, to_appendTo, getDirectReplacement(), getDebugAptr(), isDebugOn()); //Now, getIndirectReplacement() populates that spot. rwApndr.appendIndirect(to_appendTo, this, getDebugAptr(), isDebugOn()); m.appendTail(to_appendTo); iRplcs++; return to_appendTo; } if(getReplacedInEachInput().isAll()) { do { sph.matchedIndex(m.start()); debugFindWhat(); //Append everything up-to-but-not-including the next //match. Replace it with NOTHING. rwApndr.appendDirect(m, to_appendTo, getDirectReplacement(), getDebugAptr(), isDebugOn()); //Now, getIndirectReplacement() populates that spot. rwApndr.appendIndirect(to_appendTo, this, getDebugAptr(), isDebugOn()); iRplcs++; } while(m.find()); m.appendTail(to_appendTo); return to_appendTo; } if(getReplacedInEachInput().isMatchNumbers()) { int iSubsq = 0; do { iSubsq++; if(getMatchNumValidator().isValid(iSubsq)) { sph.matchedIndex(m.start()); debugFindWhat(); //Do replace this sub-sequence. //Append everything up-to-but-not-including the next //match. Replace it with NOTHING. rwApndr.appendDirect(m, to_appendTo, getDirectReplacement(), getDebugAptr(), isDebugOn()); //Now, getIndirectReplacement() populates that spot. rwApndr.appendIndirect(to_appendTo, this, getDebugAptr(), isDebugOn()); iRplcs++; } else { //Don't replace this sub-sequence. Repopulate it with itself. m.appendReplacement(to_appendTo, m.group()); } getMatchNumValidator().resetState(); } while(m.find()); m.appendTail(to_appendTo); return to_appendTo; } //getReplacedInEachInput().isUntil() is true StringBuffer sbTemp = new StringBuffer(text_toRplc.length()); int iUntilLoops = 0; boolean bFound = false; while(true) { bFound = false; iUntilLoops++; do { sph.matchedIndex(m.start()); debugFindWhat(); bFound = true; //Append everything up-to-but-not-including the next //match. Replace it with NOTHING. rwApndr.appendDirect(m, sbTemp, getDirectReplacement(), getDebugAptr(), isDebugOn()); //Now, getIndirectReplacement() populates that spot. rwApndr.appendIndirect(sbTemp, this, getDebugAptr(), isDebugOn()); iRplcs++; } while(m.find()); m.appendTail(sbTemp); if(iUntilLoops >= getMaxUntilLoops()) { //The NEXT "while(m.find())" loop would exceed the maximum. if(doCrashIfMaxUntilExceeded()) { throw new IllegalStateException("Maximum number of 'until' loops reached. getMaxUntilLoops()=" + getMaxUntilLoops() + ". Original text_toRplc=\"" + text_toRplc + "\". Current replacement: [" + sbTemp + "]. -- this=[" + this + "]"); } //ELSE: Stop to_appendTo.append(sbTemp); break; } if(!bFound) { to_appendTo.append(sbTemp); break; } m.reset(sbTemp.toString()); if(!m.find()) { to_appendTo.append(sbTemp); break; } sbTemp.setLength(0); } return to_appendTo; } public String toString() { return appendToString((new StringBuilder())).toString(); } public StringBuilder appendToString(StringBuilder to_appendTo) { try { to_appendTo.append("replaced " + getMatchCount() + " times, find-what=\"").append(getPattern()).append("\" "); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(to_appendTo, "to_appendTo", null, rx); } if(getPattern().flags() != 0) { to_appendTo.append("(flags=").append(getPattern().flags()).append(") "); } to_appendTo.append("replace-with="); if(getDirectReplacement() != null) { to_appendTo.append("\"").append(VPC_DBG.get(getDirectReplacement())).append("\""); } else { to_appendTo.append("[indirect replacement]"); } to_appendTo.append(", ReplacedInEachInput.").append(getReplacedInEachInput()); if(getReplacedInEachInput().isMatchNumbers()) { to_appendTo.append(" getMatchNumValidator()=[" + getMatchNumValidator() + "]"); } else if(getReplacedInEachInput().isUntil()) { to_appendTo.append(" (").append(doCrashIfMaxUntilExceeded() ? "crash" : "stop"). append(" at ").append(getMaxUntilLoops()).append(")"); } return to_appendTo; } /** <p>Duplicate this {@code RegexReplacer}.</p> * @return <code>(new {@link #RegexReplacer(RegexReplacer) RegexReplacer}(this))</code> */ public RegexReplacer getObjectCopy() { return (new RegexReplacer(this)); } } abstract class ReplaceWithAppender { public final void appendDirect(Matcher matcher, StringBuffer to_appendTo, Object rplc_withStr, TextAppenter dbg_apntr, boolean do_debug) { try { //to_appendTo.length(); //Needed to trigger an npx???? Why else would it be needed? matcher.appendReplacement(to_appendTo, rplc_withStr.toString()); } catch(RuntimeException rx) { Objects.requireNonNull(rplc_withStr, "rplc_withStr"); throw CrashIfObject.nullOrReturnCause(to_appendTo, "to_appendTo", null, rx); } } public void appendIndirect(StringBuffer ignored1, RegexReplacer ignored3, TextAppenter ignored4, boolean ignored5) { //Do nothing! } } class RWADirect extends ReplaceWithAppender { } class RWAIndirect extends RWADirect { public void appendIndirect(StringBuffer to_appendTo, RegexReplacer rr_containingIndirect, TextAppenter dbg_apntr, boolean do_debug) { String sir = rr_containingIndirect.getIndirectReplacement(); to_appendTo.append(sir); } }