/* * Copyright 2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * 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.grails.buffer; import groovy.lang.GroovyObject; import groovy.lang.MetaClass; import groovy.lang.Writable; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.grails.charsequences.CharSequences; import org.grails.encoder.EncodedAppender; import org.grails.encoder.EncodedAppenderFactory; import org.grails.encoder.EncodedAppenderWriter; import org.grails.encoder.EncodedAppenderWriterFactory; import org.grails.encoder.Encoder; import org.grails.encoder.EncodingStateRegistry; import org.grails.encoder.StreamingEncoder; import org.grails.encoder.StreamingEncoderWriter; import org.codehaus.groovy.runtime.GStringImpl; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; /** * PrintWriter implementation that doesn't have synchronization. null object * references are ignored in print methods (nothing gets printed) * * @author Lari Hotari, Sagire Software Oy */ public class GrailsPrintWriter extends Writer implements GrailsWrappedWriter, EncodedAppenderWriterFactory, GroovyObject { protected static final Log LOG = LogFactory.getLog(GrailsPrintWriter.class); protected static final char CRLF[] = { '\r', '\n' }; protected boolean trouble = false; protected Writer out; protected boolean allowUnwrappingOut = true; protected boolean usageFlag = false; protected Writer streamCharBufferTarget = null; protected Writer previousOut = null; public GrailsPrintWriter(Writer out) { this.metaClass = InvokerHelper.getMetaClass(this.getClass()); setOut(out); } public boolean isAllowUnwrappingOut() { return allowUnwrappingOut; } public Writer unwrap() { if (isAllowUnwrappingOut()) { return getOut(); } return this; } public boolean isDestinationActivated() { return out != null; } public Writer getOut() { return out; } public void setOut(Writer newOut) { this.out = unwrapWriter(newOut); this.lock = this.out != null ? this.out : this; this.streamCharBufferTarget = null; this.previousOut = null; } protected Writer unwrapWriter(Writer writer) { if (writer instanceof GrailsWrappedWriter ) { return ((GrailsWrappedWriter)writer).unwrap(); } return writer; } /** * Provides Groovy << left shift operator, but intercepts call to make sure * nulls are converted to "" strings * * @param obj The value * @return Returns this object * @throws IOException */ public GrailsPrintWriter leftShift(Object obj) throws IOException { if (trouble || obj == null) { usageFlag = true; return this; } Class<?> clazz = obj.getClass(); if (clazz == String.class) { write((String)obj); } else if (clazz == StreamCharBuffer.class) { write((StreamCharBuffer)obj); } else if (clazz == GStringImpl.class) { write((Writable)obj); } else if (obj instanceof Writable) { write((Writable)obj); } else if (obj instanceof CharSequence) { try { usageFlag = true; CharSequences.writeCharSequence(getOut(), (CharSequence) obj); } catch (IOException e) { handleIOException(e); } } else { InvokerHelper.write(this, obj); } return this; } public GrailsPrintWriter plus(Object value) throws IOException { usageFlag = true; return leftShift(value); } /** * Flush the stream if it's not closed 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. */ public boolean checkError() { return trouble; } public void setError() { trouble = true; } /** * Flush the stream. * * @see #checkError() */ @Override public synchronized void flush() { if (trouble) { return; } if (isDestinationActivated()) { try { getOut().flush(); } catch (IOException e) { handleIOException(e); } } } boolean isTrouble() { return trouble; } void handleIOException(IOException e) { if (trouble) { return; } if (LOG.isDebugEnabled()) { LOG.debug("I/O exception in GrailsPrintWriter: " + e.getMessage(), e); } trouble = true; setError(); } /** * Print an object. The string produced by the <code>{@link * java.lang.String#valueOf(Object)}</code> method is translated into bytes * according to the platform's default character encoding, and these bytes * are written in exactly the manner of the <code>{@link #write(int)}</code> * method. * * @param obj The <code>Object</code> to be printed * @see java.lang.Object#toString() */ public void print(final Object obj) { if (trouble || obj == null) { usageFlag = true; return; } Class<?> clazz = obj.getClass(); if (clazz == String.class) { write((String)obj); } else if (clazz == StreamCharBuffer.class) { write((StreamCharBuffer)obj); } else if (clazz == GStringImpl.class) { write((Writable)obj); } else if (obj instanceof Writable) { write((Writable)obj); } else if (obj instanceof CharSequence) { try { usageFlag = true; CharSequences.writeCharSequence(getOut(), (CharSequence)obj); } catch (IOException e) { handleIOException(e); } } else { write(String.valueOf(obj)); } } /** * Print a string. If the argument is <code>null</code> then the string * <code>""</code> is printed. Otherwise, the string's characters are * converted into bytes according to the platform's default character * encoding, and these bytes are written in exactly the manner of the * <code>{@link #write(int)}</code> method. * * @param s The <code>String</code> to be printed */ public void print(final String s) { if (s == null) { usageFlag = true; return; } write(s); } /** * Writes a string. If the argument is <code>null</code> then the string * <code>""</code> is printed. * * @param s The <code>String</code> to be printed */ @Override public void write(final String s) { usageFlag = true; if (trouble || s == null) { return; } try { getOut().write(s); } catch (IOException e) { handleIOException(e); } } /** * Write a single character. * * @param c int specifying a character to be written. */ @Override public void write(final int c) { usageFlag = true; if (trouble) return; try { getOut().write(c); } catch (IOException e) { handleIOException(e); } } /** * Write a portion of an array of characters. * * @param buf Array of characters * @param off Offset from which to start writing characters * @param len Number of characters to write */ @Override public void write(final char buf[], final int off, final int len) { usageFlag = true; if (trouble || buf == null || len == 0) return; try { getOut().write(buf, off, len); } catch (IOException e) { handleIOException(e); } } /** * Write a portion of a string. * * @param s A String * @param off Offset from which to start writing characters * @param len Number of characters to write */ @Override public void write(final String s, final int off, final int len) { usageFlag = true; if (trouble || s == null || s.length() == 0) return; try { getOut().write(s, off, len); } catch (IOException e) { handleIOException(e); } } @Override public void write(final char buf[]) { write(buf, 0, buf.length); } /** delegate methods, not synchronized **/ public void print(final boolean b) { if (b) { write("true"); } else { write("false"); } } public void print(final char c) { write(c); } public void print(final int i) { write(String.valueOf(i)); } public void print(final long l) { write(String.valueOf(l)); } public void print(final float f) { write(String.valueOf(f)); } public void print(final double d) { write(String.valueOf(d)); } public void print(final char s[]) { write(s); } public void println() { usageFlag = true; write(CRLF); } public void println(final boolean b) { print(b); println(); } public void println(final char c) { print(c); println(); } public void println(final int i) { print(i); println(); } public void println(final long l) { print(l); println(); } public void println(final float f) { print(f); println(); } public void println(final double d) { print(d); println(); } public void println(final char c[]) { print(c); println(); } public void println(final String s) { print(s); println(); } public void println(final Object o) { print(o); println(); } @Override public GrailsPrintWriter append(final char c) { try { usageFlag = true; getOut().append(c); } catch (IOException e) { handleIOException(e); } return this; } @Override public GrailsPrintWriter append(final CharSequence csq, final int start, final int end) { try { usageFlag = true; if (csq == null) appendNullCharSequence(); else CharSequences.writeCharSequence(getOut(), csq, start, end); } catch (IOException e) { handleIOException(e); } return this; } protected void appendNullCharSequence() throws IOException { getOut().append(null); } @Override public GrailsPrintWriter append(final CharSequence csq) { try { usageFlag = true; if (csq == null) appendNullCharSequence(); else CharSequences.writeCharSequence(getOut(), csq); } catch (IOException e) { handleIOException(e); } return this; } public GrailsPrintWriter append(final Object obj) { print(obj); return this; } @Override protected Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public void write(final StreamCharBuffer otherBuffer) { usageFlag = true; if (trouble) return; try { otherBuffer.writeTo(findStreamCharBufferTarget(true)); } catch (IOException e) { handleIOException(e); } } protected Writer findStreamCharBufferTarget(boolean markUsed) { boolean allowCaching = markUsed; Writer currentOut = getOut(); if (allowCaching && streamCharBufferTarget != null && previousOut == currentOut) { return streamCharBufferTarget; } Writer target = currentOut; while (target instanceof GrailsWrappedWriter) { GrailsWrappedWriter gpr = ((GrailsWrappedWriter)target); if (gpr.isAllowUnwrappingOut()) { if (markUsed) { gpr.markUsed(); } target = gpr.unwrap(); } else { break; } } Writer result; if (target instanceof StreamCharBuffer.StreamCharBufferWriter) { result = target; } else { result = currentOut; } if (allowCaching) { streamCharBufferTarget = result; previousOut = currentOut; } return result; } public void print(final StreamCharBuffer otherBuffer) { write(otherBuffer); } public void append(final StreamCharBuffer otherBuffer) { write(otherBuffer); } public void println(final StreamCharBuffer otherBuffer) { write(otherBuffer); println(); } public GrailsPrintWriter leftShift(final StreamCharBuffer otherBuffer) { if (otherBuffer != null) { write(otherBuffer); } return this; } public void write(final Writable writable) { writeWritable(writable); } protected void writeWritable(final Writable writable) { if(writable.getClass() == StreamCharBuffer.class) { write((StreamCharBuffer)writable); return; } usageFlag = true; if (trouble) return; try { writable.writeTo(getOut()); } catch (IOException e) { handleIOException(e); } } public void print(final Writable writable) { writeWritable(writable); } public GrailsPrintWriter leftShift(final Writable writable) { writeWritable(writable); return this; } public void print(final GStringImpl gstring) { writeWritable(gstring); } public GrailsPrintWriter leftShift(final GStringImpl gstring) { writeWritable(gstring); return this; } public GrailsPrintWriter leftShift(final String string) { print(string); return this; } public boolean isUsed() { if (usageFlag) { return true; } Writer target = findStreamCharBufferTarget(false); if (target instanceof StreamCharBuffer.StreamCharBufferWriter) { StreamCharBuffer buffer = ((StreamCharBuffer.StreamCharBufferWriter)target).getBuffer(); if (!buffer.isEmpty()) { return true; } } return usageFlag; } public void setUsed(boolean newUsed) { usageFlag = newUsed; } public boolean resetUsed() { boolean prevUsed = usageFlag; usageFlag = false; return prevUsed; } @Override public void close() { if (isDestinationActivated()) { try { getOut().close(); } catch (IOException e) { handleIOException(e); } } } public void markUsed() { setUsed(true); } public Object asType(Class<?> clazz) { if (clazz == PrintWriter.class) { return asPrintWriter(); } if (clazz == Writer.class) { return this; } return DefaultTypeTransformation.castToType(this, clazz); } public PrintWriter asPrintWriter() { return GrailsPrintWriterAdapter.newInstance(this); } public Writer getWriterForEncoder(Encoder encoder, EncodingStateRegistry encodingStateRegistry) { Writer target = null; if (getOut() instanceof EncodedAppenderWriterFactory && getOut() != this) { target = getOut(); } else { target = findStreamCharBufferTarget(false); } if (target instanceof EncodedAppenderWriterFactory && target != this) { return ((EncodedAppenderWriterFactory)target).getWriterForEncoder(encoder, encodingStateRegistry); } else if (target instanceof EncodedAppenderFactory) { EncodedAppender encodedAppender=((EncodedAppenderFactory)target).getEncodedAppender(); if (encodedAppender != null) { return new EncodedAppenderWriter(encodedAppender, encoder, encodingStateRegistry); } } if (target != null) { if (encoder instanceof StreamingEncoder) { return new StreamingEncoderWriter(target, (StreamingEncoder)encoder, encodingStateRegistry); } else { return new CodecPrintWriter(target, encoder, encodingStateRegistry); } } else { return null; } } // GroovyObject interface implementation to speed up metaclass operations private transient MetaClass metaClass; public Object getProperty(String property) { return getMetaClass().getProperty(this, property); } public void setProperty(String property, Object newValue) { getMetaClass().setProperty(this, property, newValue); } public Object invokeMethod(String name, Object args) { return getMetaClass().invokeMethod(this, name, args); } public MetaClass getMetaClass() { if (metaClass == null) { metaClass = InvokerHelper.getMetaClass(getClass()); } return metaClass; } public void setMetaClass(MetaClass metaClass) { this.metaClass = metaClass; } }