/*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.analyze.alter; import com.github.xbn.lang.RuleType; import com.github.xbn.array.CrashIfArray; import com.github.xbn.array.NullElement; import com.github.xbn.lang.CrashIfObject; import com.github.xbn.text.padchop.EscapeAction; import com.github.xbn.text.padchop.NewVzblPadChopFor; import com.github.xbn.text.padchop.VzblPadChop; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.Objects; import static com.github.xbn.lang.XbnConstants.*; /** <p>For a series of alterers that potentially expire--once an alter-element is expired, it is removed from the list. When all items are removed, the list itself is expired. This is intended for use in high-iteration loops, where one or more alterations are made once, or a limited number of times. An example is making replacements to each line in a large text file, where only the first match should be replaced.</p> {@.codelet.and.out com.github.xbn.examples.linefilter.alter.ExpirableTextLineAlterListXmpl%eliminateCommentBlocksAndPackageDecl()} * @see com.github.xbn.lang.Expirable#isExpired() * @see com.github.xbn.linefilter.alter.ExpirableTextLineAlterList * @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 ExpirableAlterList<V,A> extends AbstractValueAlterer<V,A> { //state private final List<ValueAlterer<V,A>> alterList; private final boolean bShrtCrct; //internal private static final VzblPadChop VPC_DBG = NewVzblPadChopFor.trimEscChopWithDDD(true, EscapeAction.ESCAPE, 50); /* <p>Create a new instance in which expirable elements are optional, and {@code null} elements are forbidden.</p> * <p>Equal to <br/>     <code>{@link #ExpirableAlterList(ValueAlterer[], ExpirableElements, MultiAlterType) this}(xprbl_alterArray, {@link com.github.xbn.analyze.alter.ExpirableElements ExpirableElements}.{@link com.github.xbn.analyze.alter.ExpirableElements#OPTIONAL OPTIONAL}, multi_alterType)</code></p> public ExpirableAlterList(ValueAlterer<V,A>[] xprbl_alterArray, MultiAlterType multi_alterType) { this(xprbl_alterArray, ExpirableElements.OPTIONAL, multi_alterType); } */ /** <p>Create a new instance.</p> * @param multi_alterType May not be {@code null}. Get (the resulting boolean) with {@link com.github.xbn.analyze.alter.MultiAlterType#isShortCircuit() isShortCircuit}{@code ()}. If {@link com.github.xbn.analyze.alter.MultiAlterType#SHORT_CIRCUIT SHORT_CIRCUIT}, then only one element at a maximum may alter the value (the first element whose {@link com.github.xbn.analyze.alter.Alterer#wasAltered() wasAltered}{@code ()} is true, causes <code><!-- GENERIC PARAMETERS FAIL IN @link --><a href="#getAlteredPostResetCheck(V, A)">getAlteredPostResetCheck</a>(V,A)</code> to exit.) If {@link com.github.xbn.analyze.alter.MultiAlterType#CUMULATIVE CUMULATIVE}, then the value is altered by all elements in each call to {@code getAlteredPostResetCheck(V,A)}. * @param xprbl_lmntsAre If {@code ExpirableElements.}{@code com.github.xbn.analyze.alter.ExpirableElements#REQUIRED REQUIRED}, then no element in {@code xprbl_alterArray} may be {@link com.github.xbn.lang.Expirable#isExpired() expired}. * @param xprbl_alterArray May not be {@code null}, empty, or contain duplicates, and no element may be {@code null}. Each object in this array is {@link com.github.xbn.lang.Copyable#getObjectCopy() duplicated} internally. * @exception IllegalArgumentException If {@code xprbl_lmntsAre.}{@link com.github.xbn.analyze.alter.ExpirableElements#isRequired() isRequired}{@code ()} is {@code true} and any element's {@link com.github.xbn.lang.Expirable#doesExpire() doesExpire}{@code ()} is {@code false}. * @see #ExpirableAlterList(ExpirableAlterList) this(xal) */ public ExpirableAlterList(ValueAlterer<V,A>[] xprbl_alterArray, ExpirableElements xprbl_lmntsAre, MultiAlterType multi_alterType, Appendable debug_ifNonNull) { super(); if(multi_alterType == null) { throw new NullPointerException("multi_alterType"); } bShrtCrct = multi_alterType.isShortCircuit(); CrashIfArray.nullEmpty(xprbl_alterArray, "xprbl_alterArray"); alterList = new ArrayList<ValueAlterer<V,A>>(xprbl_alterArray.length); for(int i = 0; i < xprbl_alterArray.length; i++) { ValueAlterer<V,A> avo = xprbl_alterArray[i]; try { if(xprbl_lmntsAre.isRequired() && !avo.doesExpire()) { throw new IllegalArgumentException("xprbl_alterArray[" + i + "].doesExpire() is false, and xprbl_lmntsAre.isRequired() is true."); } if(avo.isExpired()) { throw new IllegalArgumentException("xprbl_alterArray[" + i + "].issExpired() is true."); } } catch(RuntimeException rx) { Objects.requireNonNull(avo, "xprbl_alterArray[" + i + "]"); throw CrashIfObject.nullOrReturnCause(xprbl_lmntsAre, "xprbl_lmntsAre", null, rx); } if(alterList.contains(avo)) { throw new IllegalArgumentException("Duplicate: xprbl_alterArray[" + i + "]: " + avo); } alterList.add(avo.getObjectCopy()); //Defensive copy } setDebug(debug_ifNonNull, (debug_ifNonNull != null)); } /** <p>Create a new instance as a duplicate of another.</p> <p>This<ol> <li>Calls {@link com.github.xbn.analyze.alter.AbstractValueAlterer#AbstractValueAlterer(ValueAlterer) super}(to_copy)</li> <li>Duplicates the internal list of alters (including state and counts)</li> <li>Sets {@link #doShortCircuit() doShortCircuit}{@code ()} to {@code to_copy.doShortCircuit()}</li> </ol></p> * @param to_copy May not be {@code null} * @see #ExpirableAlterList(ValueAlterer[], ExpirableElements, MultiAlterType, Appendable) */ public ExpirableAlterList(ExpirableAlterList<V,A> to_copy) { super(to_copy); alterList = new ArrayList<ValueAlterer<V,A>>(to_copy.alterList); bShrtCrct = to_copy.doShortCircuit(); } /** <p>Should the object be altered, at most, by a single element, or should it be cumulatively altered by every element, every time?.</p> * @return <ul> <li>{@code true}: At the most, only one element may alter the object in a single call to <code><!-- GENERIC PARAMETERS FAIL IN @link --><a href="#getAltered(V, A)">getAltered</a>(V,A)</code></li> <li>{@code false}: All elements alter the object, cumulatively, in each call to {@code getAltered(V,A)}.</li> </ul> * @see MultiAlterType */ public boolean doShortCircuit() { return bShrtCrct; } /** <p>The number of remaining--non-expired--alter-elements.</p> */ public int size() { return alterList.size(); } /** <p>Alter the object, and remove expired alter elements.</p> <p>For every alter-element, this calls <br/>     <code><!-- GENERIC PARAMETERS FAIL IN @link --><a href="AbstractValueAlterer.html#getAltered(V, A)">getAltered</a>(to_validate, to_alter)</code></p> <p>When {@code to_alter} is altered by an element and {@link #doShortCircuit() doShortCircuit}{@code ()} is {@code true} (or that alteration is "{@link com.github.xbn.analyze.alter.Alterer#needsToBeDeleted() needs to be deleted}"), this function exits. Otherwise, the now-altered value of {@code to_alter} is passed to the next element to be further ({@link com.github.xbn.analyze.alter.MultiAlterType#CUMULATIVE cumulatively}) altered.</p> * @see <code><a href="AbstractValueAlterer.html">AbstractValueAlterer</a>.<!-- GENERIC PARAMETERS FAIL IN @link --><a href="AbstractValueAlterer.html#getAltered(V, A)">getAltered</a>(V,A)</code> */ public A getAlteredPostResetCheck(V to_validate, A to_alter) { // if(isDebugOn()) { // getDebugAptr().appentln("<XAL> (" + size() + " elements, " + // (isExpired() ? "" : "un") + "expired) to_alter=[" + // VPC_DBG.get(to_alter) + "]"); // } for(int i = 0; i < size(); i++) { ValueAlterer<V,A> avo = alterList.get(i); if(avo.isExpired()) { alterList.remove(i--); continue; } try { to_alter = avo.getAltered(to_validate, to_alter); } catch(RuntimeException rx) { throw new RuntimeException("Attempting [element " + i + "].getAltered(to_validate, to_alter)." + LINE_SEP + " - Element " + i + "=" + avo + LINE_SEP + " - to_validate=[" + to_validate + "]" + LINE_SEP + " - to_alter=[" + to_alter + "]", rx); } if(avo.wasAltered()) { if(isDebugOn()) { getDebugAptr().appentln("<XAL> Altered by element " + i + ": to_alter=[" + VPC_DBG.get(to_alter) + "]"); } declareAltered(Altered.YES, NeedsToBeDeleted.NO); } else if(avo.needsToBeDeleted()) { if(isDebugOn()) { getDebugAptr().appentln("<XAL> Altered by element " + i + ": to_alter=[" + VPC_DBG.get(to_alter) + "]"); } declareAltered(Altered.NO, NeedsToBeDeleted.YES); } if(avo.isExpired()) { alterList.remove(i--); if(isDebugOn()) { getDebugAptr().appentln("<XAL> Element " + (i + 1) + " expired. " + alterList.size() + " remain"); } } if((wasAltered() && doShortCircuit()) || needsToBeDeleted()) { // if(isDebugOn()) { getDebugAptr().appentln("<XAL> EXITING because either doShortCircuit() (currently " + doShortCircuit() + ") or needsToBeDeleted() (currently " + needsToBeDeleted() + ") is true."); } break; } } if(isExpired() && isDebugOn()) { getDebugAptr().appentln("<XAL> EXPIRED"); } return to_alter; } public void crashIfIncomplete(String msgPrefix_ifNonNull) { if(!isComplete()) { throw new AlterationNotMadeException( ((msgPrefix_ifNonNull == null) ? "" : msgPrefix_ifNonNull + ": ") + appendIncompleteInfo((new StringBuilder())).toString()); } } /** <p>Did any alterer not make an alteration?. This is intended for use after all alterations are attempted.</p> * @return {@code false} If at least one {@linkplain Alterer#isRequired() required} element's {@linkplain com.github.xbn.analyze.alter.Alterer#getAlteredCount() alteration count} is zero. */ public boolean isComplete() { for(ValueAlterer<V,A> alterer : alterList) { if(alterer.isRequired() && alterer.getAlteredCount() == 0) { return false; } } return true; } /** <p>Append the {@code toString()}s of all elements that made no alteration.</p> */ public StringBuilder appendIncompleteInfo(StringBuilder to_appendTo) { if(isComplete()) { throw new IllegalStateException("isComplete() is true."); } to_appendTo.append("Alterations attempted but not made:").append(LINE_SEP); for(ValueAlterer<V,A> alterer : alterList) { if(alterer.getAlteredCount() == 0) { to_appendTo.append(" -"). append(alterer.isRequired() ? "" : "[optional]-"). append(" "); alterer.appendToString(to_appendTo); to_appendTo.append(LINE_SEP); } } to_appendTo.delete((to_appendTo.length() - LINE_SEP.length()), to_appendTo.length()); return to_appendTo; } /** * @return {@code true} */ public boolean doesExpire() { return true; } /** * @return <code>({@link #size() size}() == 0)</code> */ public boolean isExpired() { return (size() == 0); } /** * <p>Get a duplicate of this object.</p> * @return <code>(new {@link #ExpirableAlterList(ExpirableAlterList) ExpirableAlterList}<V,A>(this))</code> */ public ExpirableAlterList<V,A> getObjectCopy() { return (new ExpirableAlterList<V,A>(this)); } public StringBuilder appendToString(StringBuilder to_appendTo) { super.appendToString(to_appendTo).append("Unexpired alterers:").append(LINE_SEP); for(ValueAlterer<V,A> av : alterList) { if(av.isExpired()) { continue; } to_appendTo.append(av).append(LINE_SEP); } return to_appendTo; } /** <p>Reset state in preparation for the next alteration.</p> <p>This calls<ol> <li><code>{@link com.github.xbn.analyze.alter.AbstractValueAlterer super}.{@link com.github.xbn.analyze.alter.AbstractValueAlterer#resetState() resetState}()</code></li> <li>For every element: {@link com.github.xbn.analyze.alter.Alterer#resetState() resetState}{@code ()}</li> </ol></p> */ public void resetState() { super.resetState(); for(ValueAlterer<V,A> av : alterList) { av.resetState(); } } /** <p>Reset alter counts to zero.</p> <p>This calls<ol> <li><code>{@link com.github.xbn.analyze.alter.AbstractValueAlterer super}.{@link com.github.xbn.analyze.alter.AbstractValueAlterer#resetCounts() resetCounts}()</code></li> <li>For every element: {@link com.github.xbn.analyze.alter.Alterer#resetCounts() resetCounts}{@code ()}</li> </ol></p> */ public void resetCounts() { super.resetCounts(); for(ValueAlterer<V,A> av : alterList) { av.resetCounts(); } } /** <p>Reset state after a deletion is made.</p> <p>This calls<ol> <li><code>{@link com.github.xbn.analyze.alter.AbstractValueAlterer super}.{@link com.github.xbn.analyze.alter.AbstractValueAlterer#resetForDeletion() resetForDeletion}()</code></li> <li>For every element: {@link com.github.xbn.analyze.alter.Alterer#resetForDeletion() resetForDeletion}{@code ()}</li> </ol></p> */ public void resetForDeletion() { super.resetForDeletion(); for(ValueAlterer<V,A> av : alterList) { av.resetForDeletion(); } } /** <p>Set overall debugging. This does not turn debugging on in any alter-element.</p> <p>This calls<ol> <li>{@link com.github.xbn.io.SimpleDebuggable#setDebug(Appendable, boolean) setDebug}{@code (destination, is_on)}</li> </ol></p> */ public void setDebug(Appendable destination, boolean is_on) { super.setDebug(destination, is_on); } /** <p>Set extra error information.</p> <p>This calls<ol> <li>{@link com.github.xbn.analyze.alter.AbstractValueAlterer#setExtraErrInfo(Object) setExtraErrInfo}{@code (info)}</li> <li>For every element: {@link com.github.xbn.lang.ExtraErrInfoable#setExtraErrInfo(Object) setExtraErrInfo}{@code (info)}</li> </ol></p> */ public void setExtraErrInfo(Object info) { super.setExtraErrInfo(info); for(ValueAlterer<V,A> av : alterList) { av.setExtraErrInfo(info); } } /** <p>Get a new alterer array from the provided <i>ordered</i> elements, optionally excluding {@code null} elements.</p> * @param orderedAlterers_whichMayBeNull May not be {@code null} and <i>should</i> not be empty or contain duplicates. * @param null_element May not be {@code null}. If {@link com.github.xbn.array.NullElement#OK OK}, {@code null} elements are excluded from the returned array. If {@link com.github.xbn.array.NullElement#BAD BAD}, all elements must be non-{@code null}. * @param class_ofTypeL May not be {@code null}. * @param alterers_varName Descriptive name of {@code orderedAlterers_whichMayBeNull}. <i>Should</i> not be {@code null} or empty. * @return A new non-{@code null} array of line-alterers containing all non-{@code null} parameters, if any. * @exception NullPointerException If {@code null_element.isBad()} and an element is null. */ public static final <V,A,L extends ValueAlterer<V,A>> L[] getAltererArrayFromOrderedElementsIfNonNull(L[] orderedAlterers_whichMayBeNull, NullElement null_element, Class<L> class_ofTypeL, String alterers_varName) { List<L> alterList = null; try { alterList = new ArrayList<L>(orderedAlterers_whichMayBeNull.length); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(orderedAlterers_whichMayBeNull, "orderedAlterers_whichMayBeNull", null, rx); } for(int i = 0; i < orderedAlterers_whichMayBeNull.length; i++) { L alterer = orderedAlterers_whichMayBeNull[i]; if(alterer != null) { alterList.add(alterer); } else { try { if(null_element.isBad()) { throw new NullPointerException("Element " + i + " in " + alterers_varName + "."); } } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(null_element, "null_element", null, rx); } } } try { @SuppressWarnings("unchecked") L[] alterers = (L[])Array.newInstance(class_ofTypeL, alterList.size()); return alterList.toArray(alterers); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(class_ofTypeL, "class_ofTypeL", null, rx); } } public RuleType getRuleType() { if(isExpired()) { return RuleType.UNRESTRICTED; } int impossibleCount = 0; boolean atLeast1IsRestricted = false; for(ValueAlterer<V,A> alterer : alterList) { RuleType type = alterer.getRuleType(); if(type.isRestricted()) { atLeast1IsRestricted = true; } else if(type.isImpossible()) { impossibleCount++; } } if(impossibleCount == alterList.size()) { return RuleType.IMPOSSIBLE; } return (atLeast1IsRestricted ? RuleType.RESTRICTED : RuleType.UNRESTRICTED); } /** * @return <code>{@link #appendRules(StringBuilder) appendRules}(new StringBuilder()).toString()</code> */ public String getRules() { return appendRules(new StringBuilder()).toString(); } /** * @param to_appendTo May not be {@code null}. * @see #getRules() */ public StringBuilder appendRules(StringBuilder to_appendTo) { try { to_appendTo.append(LINE_SEP); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(to_appendTo, "to_appendTo", null, rx); } int sizeMinus1 = alterList.size() - 1; for(int i = 0; i < alterList.size(); i++) { to_appendTo.append(" -" + i + "- "); alterList.get(i).appendRules(to_appendTo); if(i < sizeMinus1) { to_appendTo.append(LINE_SEP); } } return to_appendTo; } }