package com.supaham.commons; import static com.google.common.base.Preconditions.checkArgument; import com.supaham.commons.utils.StringUtils; import javax.annotation.Nonnull; /** * Represents a string scroller that functions through a {@link Runnable}. Each run increments the * position, string, etc.. <p /> A String Scroller works as follows:<br /> * <pre> * StringScroller sc = new StringScroller("ABCDEF", 4) * sc.run() * sc.getCurrentString() == "ABCD" * sc.run() * sc.getCurrentString() == "BCDE" * sc.run() * sc.getCurrentString() == "CDEF" * sc.run() * sc.getCurrentString() == "DEFA" * sc.run() * sc.getCurrentString() == "EFAB" * sc.run() * sc.getCurrentString() == "FABC" * * sc = new StringScroller("ABCDEF", 4, true) * sc.run() * sc.getCurrentString() == "ABCD" * sc.run() * sc.getCurrentString() == "BCDE" * sc.run() * sc.getCurrentString() == "CDEF" * sc.run() * sc.getCurrentString() == "ABCD" * </pre> * * @see #StringScroller(String, int) * @see #StringScroller(String, int, boolean) * @since 0.1 */ public class StringScroller implements Runnable, Cloneable { private final String string; private final int displayLength; private final boolean instantlyRepeat; protected int position = -1; private boolean reset; private String currentString; /** * Constructs a new {@link StringScroller} with a String and a display length, and {@code * instantlyRepeat} as false. * * @param string string to scroll over * @param displayLength the threshold of the scroll, this is the length of characters displayed * per scroll. */ public StringScroller(@Nonnull String string, int displayLength) { this(string, displayLength, false); } /** * Constructs a new {@link StringScroller} with a String and a display length. If {@code * instantlyRepeat} is true, the position is immediately reset once the last character of the * {@code string} is reached (at the end of the current string). See {@link StringScroller} for * more information. * * @param string string to scroll over * @param displayLength the threshold of the scroll, this is the length of characters displayed * per scroll. If this is larger than the string itself, its set to the string length. * @param instantlyRepeat whether to instantly repeat the string, * * @see StringScroller */ public StringScroller(@Nonnull String string, int displayLength, boolean instantlyRepeat) { // Safe precondition if (displayLength > string.length()) { displayLength = string.length(); } this.string = StringUtils.checkNotNullOrEmpty(string); this.displayLength = displayLength; this.instantlyRepeat = instantlyRepeat; } @Override public void run() { if (!preRun()) { return; } if (!this.instantlyRepeat) { this.reset = this.position >= this.string.length(); } else { this.reset = this.position >= this.string.length() - this.displayLength; } if (this.reset) { onReset(); this.position = -1; } this.position++; afterResetRun(); String str = string.substring(position, Math.min(position + displayLength, string.length())); if (!this.instantlyRepeat) { int aint = this.displayLength - (this.string.length() - this.position); if (aint > 0) { str += this.string.substring(0, aint); } } str = postRun(str); checkArgument(str.length() <= displayLength, "string is longer than the display length."); this.currentString = str; } @Override public StringScroller clone() { try { return ((StringScroller) super.clone()); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } @Override public boolean equals(Object obj) { if (!(obj instanceof StringScroller)) { return false; } StringScroller ss = (StringScroller) obj; return this.string.equals(ss.string) && this.displayLength == ss.displayLength && this.instantlyRepeat == ss.instantlyRepeat; } @Override public String toString() { return this.currentString; } /** * This method is called first in the {@link #run()}. Used to determine if the runnable should be * terminated or not. * * @return true to continue the runnable execution, false to terminate it */ protected boolean preRun() { return true; } // TODO HELP I NEED A BETTER NAME BEFORE RELEASE! /** * This method is called after a possible reset in the {@link #run()}. */ protected void afterResetRun() { } /** * This method is called after the next string has been generated, which is passed to this * method. * Use this method to modify the final string. By default, the string that is passed is returned * directly, causing absolutely no change. * * @param string new generated string * * @return the new current string */ protected String postRun(String string) { return string; } /** * This method is called when the position is being reset, as a result of the position being the * length of the string passed to the constructor. */ protected void onReset() { } protected int getPositionInString(int index) { return index % this.string.length(); } public boolean isFirstCharacter() { return isInstantlyRepeating() ? this.position == 0 // don't use reset in case this hasn't been run before. : getPositionInString(position + this.getDisplayLength()) == 0; } public String getFinalString() { return string; } public int getDisplayLength() { return displayLength; } public boolean isInstantlyRepeating() { return instantlyRepeat; } public int getPosition() { return position; } public boolean isReset() { return reset; } public String getCurrentString() { return currentString; } protected void setCurrentString(String currentString) { this.currentString = currentString; } }