View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2001-2005 Open World Ltd
4   // Copyright (C) 2005 Kevin Rogers
5   //
6   // This file is part of MillScript.
7   //
8   // MillScript is free software; you can redistribute it and/or modify it under
9   // the terms of the GNU General Public License as published by the Free
10  // Software Foundation; either version 2 of the License, or (at your option)
11  // any later version.
12  //
13  // MillScript is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  // more details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // MillScript; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA  02111-1307  USA
21  ////////////////////////////////////////////////////////////////////////////////
22  package org.millscript.millscript.render;
23  
24  import org.millscript.commons.util.IList;
25  import org.millscript.commons.util.MapIterator;
26  import org.millscript.commons.vfs.VFile;
27  import org.millscript.millscript.alert.Alerts;
28  import org.millscript.millscript.conf.Configuration;
29  import org.millscript.millscript.datatypes.CDATA;
30  import org.millscript.millscript.datatypes.XmlComment;
31  import org.millscript.millscript.tools.CharacterEntity;
32  
33  import java.io.BufferedWriter;
34  import java.io.IOException;
35  import java.io.OutputStreamWriter;
36  import java.io.Writer;
37  import java.nio.charset.Charset;
38  import java.nio.charset.CharsetEncoder;
39  import java.nio.charset.CodingErrorAction;
40  import java.util.Iterator;
41  import java.util.List;
42  
43  /**
44   * This abstract renderer provides the core functionality required by most
45   * renderer implementations.
46   */
47  public abstract class AbstractRenderer implements Renderer {
48  
49      /**
50       * The set of character entities that are available to this renderer.
51       */
52      protected final CharacterEntity availableEntities;
53  
54      /**
55       * The configuration for this renderer.
56       */
57      protected final Configuration config;
58  
59      /**
60       * The character set this renderer is using for output.
61       */
62      protected final Charset outputCharset;
63  
64      /**
65       * The character set encoder this renderer is using for output.
66       */
67      protected final CharsetEncoder outputEncoder;
68  
69      /**
70       * The writer we are rendering into.
71       */
72      protected final Writer outputWriter;
73  
74      /**
75       * The virtual file we are rendering to.
76       */
77      protected final VFile outputVFile;
78  
79      /**
80       * Constructs a new abstract renderer, to render to the specified file,
81       * with the given set of character entities and output character set.
82       *
83       * @param ce    the set of character entities to render with
84       * @param conf  the configuration for this renderer
85       * @param cs    the output character set
86       * @param file  the virtual output file
87       */
88      public AbstractRenderer( final CharacterEntity ce, final Configuration conf, final Charset cs, final VFile file ) {
89          this.availableEntities = ce;
90          this.config = conf;
91          this.outputCharset = cs;
92          this.outputEncoder = cs.newEncoder();
93          // Ensure that encoding errors are reported by our charset encoder
94          // Without this our canEncode method may fail
95          this.outputEncoder.onMalformedInput( CodingErrorAction.REPORT );
96          this.outputEncoder.onUnmappableCharacter( CodingErrorAction.REPORT );
97          this.outputVFile = file;
98          // We'll use a writer with a 16k buffer for output
99          this.outputWriter = new BufferedWriter(
100             new OutputStreamWriter(
101                 file.getOutputStream(),
102                 this.outputEncoder
103             ),
104             16384
105         );
106     }
107 
108     /**
109      * Constructs a new abstract renderer, to render to the specified file,
110      * with the no character entities and output character set.
111      *
112      * @param conf  the configuration for this renderer
113      * @param file  the virtual output file
114      */
115     public AbstractRenderer( final Configuration conf, final VFile file ) {
116         this( null, conf, conf.getOutputCharset(), file );
117     }
118 
119     /**
120      * Constructs a new abstract renderer, to render to the specified file,
121      * with the no character entities and output character set.
122      *
123      * @param cs    the output character set
124      * @param file  the virtual output file
125      */
126     public AbstractRenderer( final Configuration conf, final Charset cs, final VFile file ) {
127         this( null, conf, cs, file );
128     }
129 
130     /**
131      * @see org.millscript.millscript.render.Renderer#append(java.lang.CharSequence)
132      */
133     public void append( final CharSequence cs ) throws IOException {
134         int len = cs.length();
135         for ( int i = 0; i < len; i++ ) {
136             this.append( cs.charAt( i ) );
137         }
138     }
139 
140     /**
141      * @see org.millscript.millscript.render.Renderer#appendNoEscape(java.lang.CharSequence)
142      */
143     public void appendNoEscape( final CharSequence cs ) throws IOException {
144         int len = cs.length();
145         for ( int i = 0; i < len; i++ ) {
146             this.appendNoEscape( cs.charAt( i ) );
147         }
148     }
149 
150     /**
151      * Checks if the specified character can be encoded by this renderer.
152      * 
153      * @param ch    the character to check    
154      * @return  <code>true</code> if the character can be encoded by this
155      * renderer
156      */
157     public final boolean canEncode( final char ch ) {
158         return this.outputEncoder.canEncode( ch );
159     }
160 
161     /**
162      * @see org.millscript.millscript.render.Renderer#render(java.lang.Object)
163      */
164     public void render( final Object o ) throws IOException {
165         if ( o == null ) {
166             return;
167         } else if ( o instanceof Renderable ) {
168             ((Renderable) o).render( this );
169         } else if ( o instanceof IList ) {
170             MapIterator it = ((IList) o).iterator( true );
171             while ( it.hasNext() ) {
172                 this.render( it.nextValue() );
173             }
174         } else if ( o instanceof List ) {
175             Iterator it = ((List)o).iterator();
176             while ( it.hasNext() ) {
177                 this.render( it.next() );
178             }
179         } else {
180             // FIXME - This code should use the standard
181             // AbstractRenderer.append(CharSequence) but we need to provide a way to
182             // parse database content into an XmlElement before we can change
183             // it.
184             this.renderObject( o );
185         }
186     }
187 
188 
189     /**
190      * @see org.millscript.millscript.render.Renderer#renderAsDocument(java.lang.Object)
191      */
192     public final void renderAsDocument( final Object o ) {
193         try {
194             this.renderDocumentHeader();
195             this.render( o );
196             this.renderDocumentFooter();
197             this.outputWriter.flush();
198             this.outputWriter.close();
199         } catch ( IOException ex ) {
200             throw(
201                 Alerts.eval(
202                     "Could not write to file",
203                     null
204                 ).
205                 culprit( "file", this.outputVFile ).
206                 culprit( "message", ex.getMessage() ).
207                 mishap()
208             );
209         }
210     };
211 
212     /**
213      * @see org.millscript.millscript.render.Renderer#renderAsFragment(org.millscript.commons.util.IList)
214      */
215     public final void renderAsFragment( final IList< ? > l ) {
216         try {
217             this.render( l );
218             this.outputWriter.flush();
219             this.outputWriter.close();
220         } catch ( IOException ex ) {
221             throw(
222                 Alerts.eval(
223                     "Could not write to file",
224                     null
225                 ).
226                 culprit( "file", this.outputVFile ).
227                 culprit( "message", ex.getMessage() ).
228                 mishap()
229             );
230         }
231     }
232 
233     /**
234      * @see org.millscript.millscript.render.Renderer#renderCDATA(org.millscript.millscript.datatypes.CDATA)
235      */
236     public void renderCDATA( final CDATA c ) throws IOException {
237         // Rendering a CDATA object is nothing special for general files, we
238         // simply render each of the objects making up the CDATA block
239         final Object[] kids = c.getStore();
240         final int nkids = kids.length;
241         for ( int i = 0; i < nkids; i++ ) {
242             this.render( kids[ i ] );
243         }
244     }
245 
246     /**
247      * @see org.millscript.millscript.render.Renderer#renderXMLComment(org.millscript.millscript.datatypes.XmlComment)
248      */
249     public void renderXMLComment( final XmlComment c ) throws IOException {
250         this.appendNoEscape( c.toString() );
251     }
252 
253 }