/*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.io;
import java.io.InputStream;
import java.util.Scanner;
import java.io.IOException;
/**
<p><a href="http://en.wikipedia.org/wiki/Decorator_pattern">Decorator</a> for {@link java.lang.Appendable}-s, including conditional printing, with runtime-only exceptions. This is a companion to {@code TextAppender}.</p>
<p><i>(Scroll down for examples.)</i></p>
<p>I've never used a debugger in my life <i>(not true any more, as of 11/2014!)</i>. I exclusively develop in <a href="http://textpad.com">TextPad</a>, and use <a href="http://phraseexpress.com">PhraseExpress</a> for auto-completes. I will likely start using an IDE, such as Eclipse or the free version of IntelliJ, only for the purpose of using a debugger. Not using a debugger is not something I'm necessarily proud of (well...maybe a little), it's just something I've not gotten around to doing. Despite this, I've built some reasonably large projects. My primary form of debugging has always been <code>System.out.println()</code>-s and this utility: {@code TextAppenter}.</p>
<p><code>TextAppenter</code> is a <a href="http://en.wikipedia.org/wiki/Decorator_pattern#Java">decorator</a> for <code>java.lang.<a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Appendable.html">Appendables</a></code>. <code>Appendables</code>s only have the following three functions:<ul>
<li><code>append(char)</code></li>
<li><code>append(CharSequence)</code></li>
<li><code>append(CharSequence, int, int)</code></li>
</ul></p>
<p><code>TextAppenter</code> provides ("decorates" it with) additional functionality but, most importantly, it crash with only runtime errors. Hence the "<code>appent</code>", with a <code>'t'</code>, prefix to all function names (such as {@link #appentln(Object) appentln}).</p>
<p>In addition, <code>TextAppenter</code>s may be "{@linkplain #isUseable() unusable}", which is a convenience when an object <i>optionally</i> outputs debugging information--when the debugging statements should remain in production code, but be off by default.</p>
<h3>Example 1 of 2: Optionally debugging an object</h3>
<p>Demonstrates an object that has optional-but-permanent debugging. It is off by default. This example shows an "off" example, followed by an "on"</p>
{@.codelet.and.out com.github.xbn.examples.io.TextAppenterObjectDebugging%eliminateCommentBlocksAndPackageDecl()}
<h3>Example 2 of 2: Optionally debuging an application with potential levels</h3>
<p>This trivial Java application uses a TextAppenter to provide three possible levels of debugging: Off, minimal, and verbose:</p>
{@.codelet com.github.xbn.examples.io.TextAppenterWithLevelsAppExample%eliminateCommentBlocksAndPackageDecl()}
<p><b>Output for:</b> <code>java TextAppenterWithLevelsAppExample debugoff</code></p>
<p><i>Not applicable: No output</i></p>
<p><b>Output for:</b> <code>java TextAppenterWithLevelsAppExample debugminimal</code></p>
<blockquote><PRE style="background-color:#EEEEEE;">debugminimal --> DebugLevel.ONE
Minimally-important debugging information goes here...</pre></blockquote>
<!--
{@.codelet.out com.github.xbn.examples.io.TextAppenterWithLevelsAppExample("debugminimal")}
-->
<p><b>Output for:</b> <code>java TextAppenterWithLevelsAppExample debugverbose</code></p>
<blockquote><PRE style="background-color:#EEEEEE;">debugverbose --> DebugLevel.TWO
Minimally-important debugging information goes here...
Very very verbose and highly-important debugging information...</pre></blockquote>
<!--
{@.codelet.out com.github.xbn.examples.io.TextAppenterWithLevelsAppExample("debugverbose")}
-->
* @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 TextAppenter {
private final TextAppender tapndr;
/**
<p>An unusable appenter.</p>
* <p>Equal to
<br/> <code>new {@link #TextAppenter() TextAppenter}()</code></p>
* @see #isUseable()
*/
public static final TextAppenter UNUSABLE = new TextAppenter();
/**
<p>An appenter that is useable, but discards all output.</p>
* <p>Equal to
<br/> <code>new {@link #TextAppenter(TextAppender) TextAppenter}({@link TAAppendable}.{@link TAAppendable#SUPPRESS SUPPRESS})</code></p>
* @see #UNUSABLE
* @see #isUseable()
*/
public static final TextAppenter SUPPRESS = new TextAppenter(TAAppendable.SUPPRESS);
/**
<p>An appenter that outputs to {@code System.out}.</p>
* <p>Equal to
<br/> <code>new {@link #TextAppenter(TextAppender) TextAppenter}({@link TAAppendable}.{@link TAAppendable#CONSOLE CONSOLE})</code></p>
* @see #isUseable()
*/
public static final TextAppenter CONSOLE = new TextAppenter(TAAppendable.CONSOLE);
/**
<p>Create a new <i>and unusable</i> appenter.</p>
<p>This sets {@link #getTextAppender() getTextAppender}{@code ()} to {@code null}.</p>
* @see #TextAppenter(TextAppender)
* @see #UNUSABLE
* @see #isUseable()
*/
private TextAppenter() {
tapndr = null;
}
/**
<p>Create a new instance with a text appender.</p>
* @param to_adapt May not be {@code null}. Get with {@link #getTextAppender() getTextAppender}{@code ()}.
* @see #TextAppenter()
*/
public TextAppenter(TextAppender to_adapt) {
if(to_adapt == null) {
throw new NullPointerException("to_adapt");
}
tapndr = to_adapt;
}
/**
<p>The text appender.</p>
* @see #TextAppenter(TextAppender)
* @see #TextAppenter()
* @see #isUseable()
*/
public TextAppender getTextAppender() {
return tapndr;
}
/**
<p>Is this appenter useable?.</p>
* @return {@code true}: If {@code appent*} can be safely called. Specifically, this returns
<br/> <code>({@link #getTextAppender() getTextAppender}() != null)</code>
*/
public boolean isUseable() {
return (getTextAppender() != null);
}
/**
<p>Get the appendable object at the heard of the text appender--<i>This must only be called when it is known that {@code getTextAppender()} is a {@code TAAppendable}</i>.</p>
* @return <code>(({@link TAAppendable}<Appendable>){@link #getTextAppender() getTextAppender}()).{@link TAAppendable#getAppendable() getAppendable}()</code>
* @exception UnusableAppenterException If {@link #isUseable() isUseable}{@code ()} is {@code false}
* @exception ClassCastException If {@code getTextAppender()} is not a {@code TAAppendable}.
*/
@SuppressWarnings("unchecked")
public Appendable getAppendable() {
try {
return ((TAAppendable<Appendable>)getTextAppender()).getAppendable();
} catch(NullPointerException npx) {
throw crashIfUnusableOrReturnNpxCause(npx);
} catch(ClassCastException ccx) {
throw new ClassCastException("getTextAppender() is not a TAAppendable<Appendable>. getTextAppender()=[" + getTextAppender() + "], original error: " + ccx);
}
}
/**
<p>Pause the application (that one using this appenter), with a message, until enter is pressed.</p>
* <p>Equal to
<br/> <code>{@link #pauseIfUseable(InputStream, Object) pauseIfUseable}({@link java.lang.System}.{@link java.lang.System#in in}, message)</code></p>
*/
public void pauseIfUseable(Object message) {
pauseIfUseable(System.in, message);
}
/**
<p>Pause the application (that one using this appenter), with a message, until enter is pressed. If this appenter is {@linkplain #isUseable() unusable}, this does nothing.</p>
<p>This calls<ol>
<li><code>appent(Object)((message == null) ? "Paused. Press enter to proceed." : message)</code></li>
<li><code>new {@link java.util.Scanner#Scanner(InputStream) Scanner}(source).{@link java.util.Scanner#nextLine() nextLine}()</code></li>
</ol></p>
*/
public void pauseIfUseable(InputStream source, Object message) {
if(!isUseable()) {
return;
}
appent((message == null) ? "Paused. Press enter to proceed." : message);
new Scanner(source).nextLine();
}
/**
<p>Output a new line only.</p>
* @exception UnusableAppenterException If {@link #isUseable() isUseable}{@code ()} is {@code false}
* @exception RTIOException If an {@link java.io.IOException IOException} is thrown
*/
public void appentln() {
try {
getTextAppender().appendln();
} catch(NullPointerException npx) {
throw crashIfUnusableOrReturnNpxCause(npx);
} catch(IOException iox) {
throw new RTIOException("Attempting getTextAppender().appendln()", iox);
}
}
/**
<p>Output a message followed by a new line.</p>
* @exception UnusableAppenterException If {@link #isUseable() isUseable}{@code ()} is {@code false}
* @exception RTIOException If an {@link java.io.IOException IOException} is thrown
*/
public void appentln(Object message) {
try {
getTextAppender().appendln(message);
} catch(NullPointerException npx) {
throw crashIfUnusableOrReturnNpxCause(npx);
} catch(IOException iox) {
throw new RTIOException("Attempting getTextAppender().appendln(Object), message=\"" + message + "\"", iox);
}
}
/**
<p>Output a message.</p>
* @exception UnusableAppenterException If {@link #isUseable() isUseable}{@code ()} is {@code false}
* @exception RTIOException If an {@link java.io.IOException IOException} is thrown
*/
public TextAppenter appent(Object message) {
try {
getTextAppender().append(message);
} catch(NullPointerException npx) {
throw crashIfUnusableOrReturnNpxCause(npx);
} catch(IOException iox) {
throw new RTIOException("Attempting getTextAppender().append(Object), message=\"" + message + "\"", iox);
}
return this;
}
/**
<p>Output a message followed by a new line, only if a condition is met.</p>
* @exception UnusableAppenterException If {@link #isUseable() isUseable}{@code ()} is {@code false}
* @exception RTIOException If an {@link java.io.IOException IOException} is thrown
*/
public void appentlnIfTrue(boolean condition, Object message) {
try {
getTextAppender().appendlnIfTrue(condition, message);
} catch(NullPointerException npx) {
throw crashIfUnusableOrReturnNpxCause(npx);
} catch(IOException iox) {
throw new RTIOException("Attempting getTextAppender().appendlnIfTrue(condition, message), condition=" + condition + ", message=\"" + message + "\"", iox);
}
}
/**
<p>Output a message, only if a condition is met.</p>
* @exception UnusableAppenterException If {@link #isUseable() isUseable}{@code ()} is {@code false}
* @exception RTIOException If an {@link java.io.IOException IOException} is thrown
*/
public TextAppenter appentIfTrue(boolean condition, Object message) {
try {
getTextAppender().appendIfTrue(condition, message);
} catch(NullPointerException npx) {
throw crashIfUnusableOrReturnNpxCause(npx);
} catch(IOException iox) {
throw new RTIOException("Attempting getTextAppender().appendIfTrue(condition, message), condition=" + condition + ", message=\"" + message + "\"", iox);
}
return this;
}
/**
<p>Output multiple new lines only, with no text.</p>
* @exception UnusableAppenterException If {@link #isUseable() isUseable}{@code ()} is {@code false}
* @exception RTIOException If an {@link java.io.IOException IOException} is thrown
*/
public void appentlns(int newLine_count) {
try {
getTextAppender().appendlns(newLine_count);
} catch(NullPointerException npx) {
throw crashIfUnusableOrReturnNpxCause(npx);
} catch(IOException iox) {
throw new RTIOException("Attempting getTextAppender().appentlns(newLine_count), newLine_count=" + newLine_count, iox);
}
}
public String toString() {
return this.getClass().getName() + ": getTextAppender()=[" + getTextAppender() + "]";
}
/**
<p>Flush the appendable, if it's flushable.</p>
*/
public void flushRtx() {
tapndr.flushRtx();
}
/**
<p>(Flush and then) close the appendable, if it's (flushable and) closeable.</p>
<p>First calls {@link #flushRtx() flushRtx}{@code ()}</p>
*/
public void closeRtx() {
flushRtx();
tapndr.closeRtx();
}
private final RuntimeException crashIfUnusableOrReturnNpxCause(NullPointerException cause) {
if(isUseable()) {
return cause;
}
return new UnusableAppenterException();
}
}