/*
 * LpPrinter.java
 *
 * Copyright (C) 2006 - 2007 Martin Slota
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * History:
 * v0.1 (2007-01-18): initial version
 * v0.2 (2007-01-18):
 * - basic implementation
 * - documentation added
 * v0.2.1 (2007-01-20):
 * - moved to a separate package lp.struct.print
 * - now also implements the Appendable interface so that other things that
 *   LpStructureUnit can be appended, too
 * - reset() renamed to reset()
 * - getSb() made private (no need for overriding classes to access sb directly
 *   as the append() and reset() methods are public)
 * - the append(LpStructureUnit) now returns LpPrinter instead of void
 * - documentation updated
 * - tests added
 * v0.2.2 (2007-03-06):
 * - visit() moved to subclass again
 * - append(LpStructureUnit) made abstract
 * v1.0.0 (2007-05-04):
 * - completely rewritten, now behaves as a writer with extra capabilities
 */

package lp.struct.util;

import java.io.IOException;
import java.io.Writer;
import lp.struct.LpStructureUnit;
import lp.struct.LpStructureUnitVisitor;
import lp.util.ExceptionAdapter;

/**
 * A base class for various printers of object representations of logic
 * programs. The implementing classes implement the methods from
 * {@link LpStructureUnitVisitor} to customize the textual representation of
 * {@link LpStructureUnit}s.
 *
 * The class as a regular {@link Writer} with the added
 * possibility to write/append a textual representation of a
 * {@link LpStructureUnit} (see the {@link #append(LpStructureUnit)} and
 * {@link #write(LpStructureUnit)} methods).
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LpStructureUnitVisitor
 */
public abstract class LpPrinter<W extends Writer> extends Writer
		implements LpStructureUnitVisitor {
	/**
	 * The underlying {@link Writer} into which the class writes.
	 */
	private W out;
	
	/**
	 * Creates a new instance that writes everything into {@code W}.
	 *
	 * @param out the {@link Writer} into which this instance will write
	 */
	protected LpPrinter(W out) {
		this.out = out;
	}
	
	/**
	 * Returns the underlying {@code Writer} into which this writes the
	 * output.
	 *
	 * @return the underlying {@code Writer} into which this writes the
	 * output
	 * @see #setOut(Writer)
	 */
	public W getOut() {
		return out;
	}
	
	/**
	 * Sets the underlying {@code Writer} into which this writes the
	 * output.
	 *
	 * @param out the new {@code Writer} into which this
	 * {@code LpPrinter} writes its output
	 * @see #getOut()
	 */
	public void setOut(W out) {
		this.out = out;
	}
	
	/**
	 * Appends a textual representation of {@code unit} to this
	 * {@code LpPrinter}. The string is constructed in the {@code #visit()}
	 * methods. If {@code unit} is {@code null}, the string "null" is appended.
	 *
	 * @param unit the {@code LpStructureUnit} that should be represented
	 * as a string and appended to the underlying {@code Writer}
	 * @return a reference to this object after appending
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	public LpPrinter append(LpStructureUnit unit) {
		if (unit == null)
			append("null");
		else
			write(unit);
		return this;
	}
	
	/**
	 * Writes a textual representation of {@code unit} to this
	 * {@code LpPrinter}. The string is constructed in the {@code #visit()}
	 * methods.
	 *
	 * @param unit the {@code LpStructureUnit} that should be represented
	 * as a string and appended to the underlying {@code Writer}
	 * @throws IllegalArgumentException if {@code unit} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	public void write(LpStructureUnit unit) {
		if (unit == null)
			throw new IllegalArgumentException(
					"The object to be written must not be null!");
		unit.accept(this);
	}
	
	/**
	 * Writes a single character to the underlying {@code Writer}.
	 *
	 * @param c int specifying a character to be written
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	public void write(int c) {
		try {
			getOut().write(c);
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
	}
	
	/**
	 * Writes a character array to the underlying {@code Writer}.
	 *
	 * @param cbuf array of characters to be written
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	public void write(char[] cbuf) {
		try {
			getOut().write(cbuf);
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
	}
	
	/**
	 * Writes a portion of a character array to the underlying {@link Writer}
	 * instance.
	 *
	 * @param cbuf array of characters to be written
	 * @param off offset from which to start writing characters
	 * @param len number of characters to write
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	public void write(char[] cbuf, int off, int len) {
		try {
			getOut().write(cbuf, off, len);
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
	}
	
	/**
	 * Writes a string to the underlying {@code Writer}.
	 *
	 * @param str string to be written
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	public void write(String str) {
		try {
			getOut().write(str);
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
	}
	
	/**
	 * Writes a string to the underlying {@code Writer}.
	 *
	 * @param str string to be written
	 * @param off offset from which to start writing characters
	 * @param len number of characters to write
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	public void write(String str, int off, int len) {
		try {
			getOut().write(str, off, len);
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
	}
	
	/**
	 * Appends a character sequence to the underlying {@code Writer}.
	 *
	 * @param csq character sequence to be written
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	public LpPrinter append(CharSequence csq) {
		try {
			getOut().append(csq);
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
		return this;
	}
	
	/**
	 * Appends a portion of a character sequence to the underlying
	 * {@code Writer}.
	 *
	 * @param csq character sequence to be written
	 * @param start the index of the first character in the subsequence
	 * @param end the index of the character following the last character in the
	 * subsequence
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	public LpPrinter append(CharSequence csq, int start, int end) {
		try {
			getOut().append(csq, start, end);
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
		return this;
	}
	
	/**
	 * Appends a single character to the underlying {@code Writer}.
	 *
	 * @param c character to be written
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	public LpPrinter append(char c) {
		try {
			getOut().append(c);
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
		return this;
	}
	
	/**
	 * Flushes the underlying {@code Writer}.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while flushing the underlying {@link Writer}
	 * instance
	 */
	public void flush() {
		try {
			getOut().flush();
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
	}
	
	/**
	 * Closes the underlying {@code Writer}.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while closing the underlying {@link Writer}
	 * instance
	 */
	public void close() {
		try {
			getOut().close();
		} catch (IOException e) {
			throw new ExceptionAdapter(e);
		}
	}
}