/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.civilian-framework.org/license.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.civilian.template; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import org.civilian.util.ArrayUtil; import org.civilian.util.Check; import org.civilian.util.ClassUtil; import org.civilian.util.ClosedWriter; /** * TemplateWriter is a PrintWriter to write pretty indented files. * TemplateWriter maintains a {@link #getTabCount() tab count}. When a new line is started * then tab characters are automatically inserted at the beginning of the line, according * to the tab count. The default tab characters is a single '\t' character, but you can * chose any string instead (e.g. " ").<p> * A TemplateWriter can be {@link #addContext(Object) associated} with multiple context objects. */ public class TemplateWriter extends PrintWriter { /** * Sets the default characters used by a new TemplateWriter to indent a line. * By default a indent consists of a single tab character. * @see #setTabChars(String) */ public static void setDefaultTabChars(String chars) { defaultTabChars_ = getChars(chars, "chars"); } /** * Returns the default characters used by a new TemplateWriter to indent a line. */ public static String getDefaultTabChars() { return new String(defaultTabChars_); } /** * Sets the default line separator used by a new TemplateWriter. * By default this is the OS dependent line separator. * But when used within a Civilian web application, it is set to a single '\n' character. */ public static void setDefaultLineSeparator(String separator) { defaultLineSeparator_ = getChars(separator, "separator"); } /** * Returns the default line separator used by a new TemplateWriter. */ public static String getDefaultLineSeparator() { return new String(defaultLineSeparator_); } /** * Creates a new TemplateWriter. * @param out a Writer */ public TemplateWriter(Writer out) { this(out, false); } /** * Creates a TemplateWriter. * @param out a Writer * @param autoFlush - a boolean; if true, the println() methods will flush * the output buffer */ public TemplateWriter(Writer out, boolean autoFlush) { super(out, false); autoFlush_ = autoFlush; } /** * Sets the string used for a tab. * If you do not explicitly specify the tab chars, * the value of {@link #getDefaultTabChars()} is used. */ public void setTabChars(String chars) { tabChars_ = getChars(chars, "chars"); } /** * Sets the characters used to separate lines. The default is * system dependent (e.g. "0xD0xA" on Windows). */ public void setLineSeparator(String separator) { lineSeparator_ = getChars(separator, "separator"); } /** * Increases the indent tab. */ public void increaseTab() { tabCount_++; } /** * Decreases the indent tab. */ public void decreaseTab() { if (tabCount_ > 0) tabCount_--; } /** * Returns the current number of indent tabs. */ public int getTabCount() { return tabCount_; } public void setTabCount(int count) { if (count < 0) throw new IllegalArgumentException(); tabCount_ = count; } protected void writeNewLineTab() { for (int j=tabCount_; j>0; j--) super.write(tabChars_, 0, tabChars_.length); newLineStarted_ = false; } public boolean newLineStarted() { return newLineStarted_; } //------------------------ // context //------------------------ /** * Associates the TemplateWriter with an arbitrary context object. * When the TemplateWriter is constructed within a Civilian request * the Response is automatically added as context object. */ public void addContext(Object context) { Check.notNull(context, "context"); contexts_ = contexts_ == null ? new Object[] { context } : ArrayUtil.addLast(contexts_, context); } /** * Returns the first context object of the TemplateWriter that has * the given class. * @return the context object or null. */ public <T> T getContext(Class<? extends T> cls) { if (contexts_ != null) { for (Object context : contexts_) { T t = ClassUtil.unwrap(context, cls); if (t != null) return t; } } return null; } /** * Returns the first context object of the TemplateWriter that has * the given class. * @throws IllegalStateException if there is no such object, */ public <T> T getSafeContext(Class<? extends T> cls) { T context = getContext(cls); if (context != null) return context; throw new IllegalStateException("no context object with " + cls.getName()); } //------------------------------------------------------- // In fact the whole printwriter class is duplicated here // since this class should be a printwriter but // not increase the performance overhead significantly // because of the newline mechanism //------------------------------------------------------- /** * Closes the stream. */ @Override public void close() { try { flush(); synchronized(lock) { Writer out = this.out; this.out = ClosedWriter.INSTANCE; if (out != null) out.close(); } } catch(IOException e) { setError(e); } } /** * Flushes the stream and check its error state. Errors are cumulative; * once the stream encounters an error, this routine will return true on * all successive calls. * @return true if the print stream has encountered an error, either on the * underlying output stream or during a format conversion. */ @Override public boolean checkError() { if (out != ClosedWriter.INSTANCE) flush(); return error_ != null; } /** * Indicates that an error has occurred. */ protected void setError(IOException e) { if (error_ == null) error_ = e; } /** * Returns the error or null if none has occurred. */ public IOException getError() { return error_; } /** * Writes a single character. */ @Override public void write(int c) { if (newLineStarted_) writeNewLineTab(); super.write(c); } /** * Writes a portion of an array of characters. */ @Override public void write(char buf[], int off, int len) { if (newLineStarted_) writeNewLineTab(); super.write(buf, off, len); } /** * Writes a portion of a string. */ @Override public void write(String s, int off, int length) { if (newLineStarted_) writeNewLineTab(); super.write(s, off, length); } /** * Writes a string. This method cannot be inherited from the Writer class * because it must suppress I/O exceptions. */ @Override public void write(String s) { write(s, 0, s.length()); } @Override public void print(char c) { write(c); } /** * Write a line end, but only if the current line is not empty. */ public void printlnIfNotEmpty() { if (!newLineStarted_) println(); } @Override public void println() { // empty lines are not indented if (newLineStarted_) newLineStarted_ = false; write(lineSeparator_, 0, lineSeparator_.length); if (autoFlush_) flush(); newLineStarted_ = true; } @Override public void println(boolean value) { print(value); println(); } @Override public void println(char value) { print(value); println(); } @Override public void println(int value) { print(value); println(); } @Override public void println(long value) { print(value); println(); } @Override public void println(float value) { print(value); println(); } @Override public void println(double value) { print(value); println(); } @Override public void println(char[] value) { print(value); println(); } @Override public void println(String value) { print(value); println(); } @Override public void println(Object value) { print(value); println(); } /** * Checks if the object is a printable and in that case calls * print(Printable), else it just calls the default implementation. */ @Override public void print(Object object) { if (object instanceof Printable) print((Printable)object); else super.print(object); } /** * If a not-null Printable is passed to the TemplateWriter, * then the printable is asked to print itself. */ public void print(Printable printable) { if (printable != null) { try { printable.print(this); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new IllegalStateException("error when printing '" + printable.getClass().getName() + "'", e); } } } /** * Printable is a interface for print-aware classes who * implement a custom print strategy. Templates and form controls * are examples of Printables. * If you pass a Printable to {@link TemplateWriter#print(Object)} or * TemplateWriter#print(Printable), then the {@link #print(TemplateWriter)} * method of the Printable is called by the TemplateWriter, allowing * to Printable to print itself. */ public static interface Printable { public void print(TemplateWriter out) throws Exception; } private static char[] getChars(String s, String what) { Check.notNull(s, what); return s.toCharArray(); } /** * Calls and returns toString() of the wrapped writer. */ @Override public String toString() { return out.toString(); } private boolean newLineStarted_ = true; private int tabCount_ = 0; private char tabChars_[] = defaultTabChars_; private char lineSeparator_[] = defaultLineSeparator_; private boolean autoFlush_; private Object[] contexts_; private IOException error_; private static char[] defaultTabChars_ = { '\t' }; private static char[] defaultLineSeparator_ = getChars(System.getProperty("line.separator"), "separator"); }