/*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.text.padchop; import com.github.xbn.io.SimpleDebuggable; import com.github.xbn.io.Debuggable; import com.github.xbn.io.TextAppenter; import com.github.xbn.lang.ToStringAppendable; import com.github.xbn.text.padchop.z.ChopString_Fieldable; import com.github.xbn.io.IOUtil; import java.io.IOException; import com.github.xbn.lang.CrashIfObject; import static com.github.xbn.lang.XbnConstants.*; /** <p>Chop a string at a particular location (left, middle, right), and with-or-without an ellipsis.</p> {@.codelet.and.out com.github.xbn.examples.text.padchop.ChopStringXmpl%eliminateCommentBlocksAndPackageDecl()} {@.codelet.and.out com.github.xbn.examples.text.padchop.CharsBeforeChopXmpl%eliminateCommentBlocksAndPackageDecl()} <A NAME="cfg"></a><h3>Builder Configuration: {@link com.github.xbn.text.padchop.z.ChopString_Cfg ChopString_Cfg}</h3> <p><ul> <li><b>Used by:</b> {@code <a href="VzblPadChop.html#cfg">VzblPadChop</a>}</li> <li>{@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#goalLen(int) goalLen}{@code (i)}</li> <li><b>Side:</b> {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#left() left}{@code ()}, {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#right() right}{@code ()}</li> <li><b>Ellipsis (dot-dot-dot):</b><ul> <li><b>String:</b> {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#noDDD() noDDD}{@code ()}, {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#ddd() ddd}{@code ()}, {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#ddd(String) ddd}{@code (s)}</li> <li><b>Location (chars before):</b> {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#charsBefore(int) charsBefore}{@code (i)}, {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#inMiddle() inMiddle}{@code ()}, {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#atEnd() atEnd}{@code ()}</li> <li><b>Overhang:</b> {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#overhang(int) overhang}{@code (i)}, {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#noOverhang() noOverhang}{@code ()}, {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#maxOverhang() maxOverhang}{@code ()}</li> </ul></li> </ul></p> * @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 ChopString extends PadChopBase implements Debuggable, ToStringAppendable { //state private final int iChrsB4Ddd; private final int iDddOH; private final String sDdd; //internal private final SimpleDebuggable sg; //public private static final int iLOCATION_IGNORED = -3; public static final int iIN_MIDDLE = -1; public static final int iAT_END = -2; /** <p>A good test string.</p> * <p>Equal to <br/>     "{@code ---------A---------B---------C---------D---------E---------F---------G---------H---------I---------J---------K---------L---------M---------N---------O---------P---------Q---------R---------S---------T---------U---------V---------W---------X---------Y---------Z}"</p> */ public static final String sGOOD_TEST_STRING = "---------A---------B---------C---------D---------E---------F---------G---------H---------I---------J---------K---------L---------M---------N---------O---------P---------Q---------R---------S---------T---------U---------V---------W---------X---------Y---------Z"; //constructors...START /** <p>Create a new {@code ChopString}.</p> <p>This first calls {@link PadChopBase#PadChopBase(PadChopBase_Fieldable) super}{@code (cs_c)}, and sets <i>and validates</i> all internal variable as set into {@link com.github.xbn.text.padchop.z.ChopString_CfgForNeeder ChopString_CfgForNeeder}.</p> * @see #ChopString(ChopString) this(cs) * @see #ChopString(ChopString, int) this(cs,i) */ public ChopString(ChopString_Fieldable fieldable) { super(fieldable); sg = new SimpleDebuggable(fieldable); sDdd = fieldable.getEllipsis(); if(sDdd != null) { iChrsB4Ddd = fieldable.getCharsBeforeDDD(); iDddOH = fieldable.getOverhangCount(); if(getGoalLen() != -1 && getEllipsis().length() > getGoalLen()) { throw new IllegalArgumentException("ellipsis is non-null, but its length is greater than getGoalLen() (" + getGoalLen() + ")."); } if(iChrsB4Ddd < -2) { throw new IllegalArgumentException("fieldable.getCharsBeforeDDD() (" + iChrsB4Ddd + ") is less than -2."); } } else { //Not used when no ellipsis iChrsB4Ddd = ChopString.iLOCATION_IGNORED; iDddOH = -1; } } /** <p>Create a new {@code ChopString} as a duplicate of another.</p> * <p>Equal to <br/>     {@link PadChopBase#PadChopBase(PadChopBase, int) super}{@code (to_copy, -2)}</p> */ public ChopString(ChopString to_copy) { this(to_copy, -2); } /** <p>Create a new {@code ChopString} as a duplicate of another, with a potentially-new goal-length.</p> <p>This<ol> <li>Calls {@link PadChopBase#PadChopBase(PadChopBase, int) super}{@code (to_copy, new_goalLen)}</li> <li>Sets<ol> <li>{@link #getEllipsis() getEllipsis}{@code ()} to <code>to_copy.{@link #getEllipsis() getEllipsis}()</code></li> <li>{@link #getCharsBeforeDDD() getCharsBeforeDDD}{@code ()} to <code>to_copy.{@link #getCharsBeforeDDD() getCharsBeforeDDD}()</code>     <i>(This is the value returned when it is between zero and <code><i>[{@link PadChopBase super}]</i>.{@link PadChopBase#getGoalLen() getGoalLen}()</code>, inclusive)</i></li> <li>{@link #getOverhangCount() getOverhangCount}{@code ()} to <code>to_copy.{@link #getOverhangCount() getOverhangCount}()</code>     <i>(This is the value returned when it is between zero and {@code <i>[super]</i>.getGoalLen()}, inclusive)</i></li> </ol></li> </ol></p> * @param to_copy May not be {@code null}. * @see #getObjectCopy() * @see #ChopString(xbn.text.padchop.z.ChopString_Fieldable) this(cs_f) */ public ChopString(ChopString to_copy, int new_goalLen) { super(to_copy, new_goalLen); sg = new SimpleDebuggable(to_copy); sDdd = to_copy.getEllipsis(); iChrsB4Ddd = to_copy.getCharsBeforeDDD(); iDddOH = to_copy.getOverhangCount(); } //constructors...END //getters...START /** <p>The ellipsis (dot-dot-dot) that indicates the string was chopped.</p> * @return {@code null} If there is no ellipsis. * @see com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#ddd(String) ChopString_CfgForNeeder#ddd(s) */ public final String getEllipsis() { return sDdd; } /** <p>The ellipsis chars-before sub-setting.</p> * @see com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#charsBefore(int) ChopString_CfgForNeeder#charsBefore(i) */ public final int getCharsBeforeDDD() { return iChrsB4Ddd; } /** <p>The number of characters that, at a maximum, the ellipsis can exceed the chopped string.</p> * @return A number between zero and <code>{@link #getEllipsis() getEllipsis}().length()</code>, inclusive. When {@code getEllipsis()} is {@code null}, this returns zero. * @see com.github.xbn.text.padchop.z.ChopString_CfgForNeeder#overhang(int) ChopString_CfgForNeeder#overhang(i) */ public final int getOverhangCount() { return iDddOH; } //getters...END //other...START /** <p>Duplicate this {@code ChopString}.</p> * @return <code>(new {@link #ChopString(ChopString) ChopString}(this))</code> */ public ChopString getObjectCopy() { return (new ChopString(this)); } /** <p>Duplicate this {@code ChopString}, with a new goal-length.</p> * @return <code>(new {@link #ChopString(ChopString) ChopString}(this))</code> */ public ChopString getCopyNewGoalLen(int goal_len) { return (new ChopString(this, goal_len)); } /** <p>Chop the string as configured.</p> * @return <code>{@link #getChopped(int, Object) getChopped}({@link PadChopBase#getGoalLen() getGoalLen}(), str_toPad)</code> */ public String getChopped(Object str_toPad) { return getChopped(getGoalLen(), str_toPad); } /** <p>Chop the string as configured, with a specific goal-length.</p> * @return {@code appendChopped((new StringBuilder()), goal_len, str_toPad).toString()} */ public String getChopped(int goal_len, Object str_toPad) { return appendChopped((new StringBuilder()), goal_len, str_toPad).toString(); } /** <p>Chop the string as configured.</p> * @return {@link #appendChoppedX(Appendable, Object) appendChoppedX(to_appendTo, str_toPad)} */ public Appendable appendChopped(Appendable to_appendTo, Object str_toPad) { try { return appendChoppedX(to_appendTo, str_toPad); } catch(IOException iox) { throw new RuntimeException("appendChopped", iox); } } public Appendable appendChopped(Appendable to_appendTo, int goal_len, Object str_toPad) { try { return appendChoppedX(to_appendTo, goal_len, str_toPad); } catch(IOException iox) { throw new RuntimeException("appendChopped", iox); } } public Appendable appendChoppedlns(int newLine_count, Appendable to_appendTo, Object str_toPad) { try { appendChoppedX(to_appendTo, str_toPad); return IOUtil.appendNewLinesX(newLine_count, to_appendTo); } catch(IOException iox) { throw new RuntimeException("appendChoppedln(i,apbl,O)", iox); } } public Appendable appendChoppedlns(int newLine_count, Appendable to_appendTo, int goal_len, Object str_toPad) { try { appendChoppedX(to_appendTo, goal_len, str_toPad).append(LINE_SEP); return IOUtil.appendNewLinesX(newLine_count, to_appendTo); } catch(IOException iox) { throw new RuntimeException("appendChoppedln(i,apbl,i,O)", iox); } } public Appendable appendChoppedX(Appendable to_appendTo, Object str_toPad) throws IOException { return appendChoppedX(to_appendTo, getGoalLen(), str_toPad); } public Appendable appendChoppedX(Appendable to_appendTo, int goal_len, Object str_toPad) throws IOException { return ChopString.appendChoppedX(this, to_appendTo, goal_len, str_toPad); } public void setDebug(Appendable destination, boolean is_on) { sg.setDebug(destination, is_on); } public void setDebugOn(boolean is_on) { sg.setDebugOn(is_on); } public boolean isDebugOn() { return sg.isDebugOn(); } public TextAppenter getDebugAptr() { return sg.getDebugAptr(); } public Appendable getDebugApbl() { return sg.getDebugApbl(); } public final TextAppenter debug(Object message) { return sg.debug(message); } public final void debugln(Object message) { sg.debugln(message); } /** <p>Chop the string as configured, with a specific goal-length.</p> * @param to_appendTo May not be {@code null}. * @param str_toPad The string to chop. * @param goal_len The goal length to chop to. May not be less than one. * @see #appendChopped(Appendable, Object) appendChopped(apbl,o) * @see #getChopped(Object) getChopped(o) * @see #getChopped(int, Object) getChopped(o,i) */ public static final Appendable appendChoppedX(ChopString chpr, Appendable to_appendTo, int goal_len, Object str_toPad) throws IOException { goal_len = PadChopBase.getCIBGoalLenForOutput(goal_len, "Chopp"); if(str_toPad == null) { to_appendTo.append("null"); return to_appendTo; } String s = str_toPad.toString(); try { if(chpr.isDebugOn()) { chpr.getDebugAptr().appentln("<CS> goal_len="+ goal_len + ", str_toPad:" + s.length() + ", this=" + chpr); } } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(chpr, "chpr", null, rx); } int iLen = s.length(); if(goal_len >= iLen) { if(chpr.isDebugOn()) { chpr.getDebugAptr().appentln("<CS> Goal length > string length. Nothing to do."); } to_appendTo.append(s); return to_appendTo; } String sDdd = chpr.getEllipsis(); if(sDdd == null) { int ixLeft = (chpr.isSideRight() ? 0 : iLen - goal_len); int ixxRight = (chpr.isSideRight() ? goal_len : iLen); if(chpr.isDebugOn()) { chpr.getDebugAptr().appentln("<CS> No ellipsis. Chopping at end. keep-idxs=[" + ixLeft + "," + ixxRight + "]"); } to_appendTo.append(s, ixLeft, ixxRight); return to_appendTo; } //There is an ellipsis. int iDDDLen = sDdd.length(); int iGoalPlusOH = goal_len + chpr.getOverhangCount(); if(iDDDLen > iGoalPlusOH) { throw new IllegalStateException("The ellipsis won't fit. chpr.getEllipsis()=\"" + sDdd + "\", goal_len (" + goal_len + "), chpr.getOverhangCount() (" + chpr.getOverhangCount() + ")."); } //The ellipsis, including its overhang, will fit without having to overhang. if(goal_len <= iDDDLen) { to_appendTo.append(sDdd); if(chpr.isDebugOn()) { chpr.getDebugAptr().appentln("<CS> Ellipsis (length=" + sDdd.length() + ") larger than goal length. Only ellipsis printed."); } return to_appendTo; } //There's at least one character beyond the ellipsis to display. if(chpr.getCharsBeforeDDD() == iIN_MIDDLE) { int iHalfGoal = goal_len >>> 1; //Now take half of the length of the ellipsis away from that //(ignoring the overhang). iHalfGoal -= iDDDLen >>> 1; int iStart2ndHalf = s.length() - iHalfGoal + 1; if(chpr.isDebugOn()) { chpr.getDebugAptr().appentln("<CS> Chopping in middle. idxs-part1=[0," + iHalfGoal + "], part2=[" + iStart2ndHalf + ", " + s.length() + "]"); } to_appendTo.append(s, 0, iHalfGoal). append(sDdd). append(s, iStart2ndHalf, s.length()); return to_appendTo; } if(chpr.getCharsBeforeDDD() == iAT_END) { if(chpr.isSideRight()) { to_appendTo.append(s, 0, (goal_len - iDDDLen + chpr.getOverhangCount())). append(sDdd); } else { to_appendTo.append(sDdd).append(s, (goal_len + iDDDLen - chpr.getOverhangCount()), s.length()); } if(chpr.isDebugOn()) { chpr.getDebugAptr().appentln("<CS> Chopping at end (side=" + chpr.isSideRight() + ")"); } return to_appendTo; } //Chop at getCharsBeforeDDD() int iB4DDD = chpr.getCharsBeforeDDD(); if(chpr.isDebugOn()) { chpr.getDebugAptr().appentln("<CS> Chopping at specific number of characters: chpr.getCharsBeforeDDD()=" + chpr.getCharsBeforeDDD()); } if((iB4DDD + iDDDLen) > iGoalPlusOH) { iB4DDD -= (iB4DDD + iDDDLen) - iGoalPlusOH; } int iB4PlusDDD = iB4DDD + iDDDLen; int iPostDDDChars = goal_len - iB4PlusDDD; if(iPostDDDChars < 0) { //There's an overhang. iPostDDDChars = 0; } if(chpr.isSideRight()) { to_appendTo.append(s, 0, iB4DDD).append(sDdd). append(s, (s.length() - iPostDDDChars), s.length()); } else { to_appendTo.append(sDdd).append(s, 0, iPostDDDChars). append(s, (s.length() - iB4DDD), s.length()); } return to_appendTo; } public String toString() { return appendToString(new StringBuilder()).toString(); } public StringBuilder appendToString(StringBuilder to_appendTo) { try { to_appendTo.append(super.toString()); } catch(RuntimeException rx) { throw CrashIfObject.nullOrReturnCause(to_appendTo, "to_appendTo", null, rx); } if(getEllipsis() == null) { return to_appendTo.append(", no ellipsis (other settings ignored)"); } to_appendTo.append(", ddd=\"").append(getEllipsis()).append("\""); to_appendTo.append(", location="); if(getCharsBeforeDDD() == ChopString.iIN_MIDDLE) { to_appendTo.append("middle"); } else if(getCharsBeforeDDD() == ChopString.iAT_END) { to_appendTo.append("end"); } else { to_appendTo.append("after " + getCharsBeforeDDD() + " chars"); } if(getOverhangCount() > 0) { to_appendTo.append(", overhang=").append(getOverhangCount()); } return to_appendTo; } //other...END }