KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > freemarker > template > utility > StandardCompress


1 /*
2  * Copyright (c) 2003 The Visigoth Software Society. All rights
3  * reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in
14  * the documentation and/or other materials provided with the
15  * distribution.
16  *
17  * 3. The end-user documentation included with the redistribution, if
18  * any, must include the following acknowledgement:
19  * "This product includes software developed by the
20  * Visigoth Software Society (http://www.visigoths.org/)."
21  * Alternately, this acknowledgement may appear in the software itself,
22  * if and wherever such third-party acknowledgements normally appear.
23  *
24  * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
25  * project contributors may be used to endorse or promote products derived
26  * from this software without prior written permission. For written
27  * permission, please contact visigoths@visigoths.org.
28  *
29  * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
30  * nor may "FreeMarker" or "Visigoth" appear in their names
31  * without prior written permission of the Visigoth Software Society.
32  *
33  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
34  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36  * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
37  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
40  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
43  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44  * SUCH DAMAGE.
45  * ====================================================================
46  *
47  * This software consists of voluntary contributions made by many
48  * individuals on behalf of the Visigoth Software Society. For more
49  * information on the Visigoth Software Society, please see
50  * http://www.visigoths.org/
51  */

52
53 package freemarker.template.utility;
54
55 import java.io.IOException JavaDoc;
56 import java.io.Writer JavaDoc;
57 import java.util.Map JavaDoc;
58
59 import freemarker.template.TemplateBooleanModel;
60 import freemarker.template.TemplateModelException;
61 import freemarker.template.TemplateNumberModel;
62 import freemarker.template.TemplateTransformModel;
63
64 /**
65  * <p>A filter that compresses each sequence of consecutive whitespace
66  * to a single line break (if the sequence contains a line break) or a
67  * single space. In addition, leading and trailing whitespace is
68  * completely removed.</p>
69  *
70  * <p>Specify the transform parameter <code>single_line = true</code>
71  * to always compress to a single space instead of a line break.</p>
72  *
73  * <p>The default buffer size can be overridden by specifying a
74  * <code>buffer_size</code> transform parameter (in bytes).</p>
75  *
76  * <p><b>Note:</b> The compress tag is implemented using this filter</p>
77  *
78  * <p>Usage:<br />
79  * From java:</p>
80  * <pre>
81  * SimpleHash root = new SimpleHash();
82  *
83  * root.put( "standardCompress", new freemarker.template.utility.StandardCompress() );
84  *
85  * ...
86  * </pre>
87  *
88  * <p>From your FreeMarker template:</p>
89  * <pre>
90  * &lt;transform standardCompress&gt;
91  * &lt;p&gt;This paragraph will have
92  * extraneous
93  *
94  * whitespace removed.&lt;/p&gt;
95  * &lt;/transform&gt;
96  * </pre>
97  *
98  * <p>Output:</p>
99  * <pre>
100  * &lt;p&gt;This paragraph will have
101  * extraneous
102  * whitespace removed.&lt;/p&gt;
103  * </pre>
104  *
105  * @version $Id: StandardCompress.java,v 1.14 2004/01/06 17:06:43 szegedia Exp $
106  */

107 public class StandardCompress implements TemplateTransformModel {
108     private static final String JavaDoc BUFFER_SIZE_KEY = "buffer_size";
109     private static final String JavaDoc SINGLE_LINE_KEY = "single_line";
110     private int defaultBufferSize;
111
112     public static final StandardCompress INSTANCE = new StandardCompress();
113     
114     public StandardCompress()
115     {
116         this(2048);
117     }
118
119     /**
120      * @param defaultBufferSize the default amount of characters to buffer
121      */

122     public StandardCompress(int defaultBufferSize)
123     {
124         this.defaultBufferSize = defaultBufferSize;
125     }
126
127     public Writer JavaDoc getWriter(final Writer JavaDoc out, Map JavaDoc args)
128     throws TemplateModelException
129     {
130         int bufferSize = defaultBufferSize;
131         boolean singleLine = false;
132         if (args != null) {
133             try {
134                 TemplateNumberModel num = (TemplateNumberModel)args.get(BUFFER_SIZE_KEY);
135                 if (num != null)
136                     bufferSize = num.getAsNumber().intValue();
137             } catch (ClassCastException JavaDoc e) {
138                 throw new TemplateModelException("Expecting numerical argument to " + BUFFER_SIZE_KEY);
139             }
140             try {
141                 TemplateBooleanModel flag = (TemplateBooleanModel)args.get(SINGLE_LINE_KEY);
142                 if (flag != null)
143                     singleLine = flag.getAsBoolean();
144             } catch (ClassCastException JavaDoc e) {
145                 throw new TemplateModelException("Expecting boolean argument to " + SINGLE_LINE_KEY);
146             }
147         }
148         return new StandardCompressWriter(out, bufferSize, singleLine);
149     }
150
151     private static class StandardCompressWriter extends Writer JavaDoc
152     {
153         private static final int MAX_EOL_LENGTH = 2; // CRLF is two bytes
154

155         private static final int AT_BEGINNING = 0;
156         private static final int SINGLE_LINE = 1;
157         private static final int INIT = 2;
158         private static final int SAW_CR = 3;
159         private static final int LINEBREAK_CR = 4;
160         private static final int LINEBREAK_CRLF = 5;
161         private static final int LINEBREAK_LF = 6;
162
163         private final Writer JavaDoc out;
164         private final char[] buf;
165         private final boolean singleLine;
166     
167         private int pos = 0;
168         private boolean inWhitespace = true;
169         private int lineBreakState = AT_BEGINNING;
170
171         public StandardCompressWriter(Writer JavaDoc out, int bufSize, boolean singleLine) {
172             this.out = out;
173             this.singleLine = singleLine;
174             buf = new char[bufSize];
175         }
176
177         public void write(char[] cbuf, int off, int len) throws IOException JavaDoc {
178             for (;;) {
179                 // Need to reserve space for the EOL potentially left in the state machine
180
int room = buf.length - pos - MAX_EOL_LENGTH;
181                 if (room >= len) {
182                     writeHelper(cbuf, off, len);
183                     break;
184                 } else if (room <= 0) {
185                     flushInternal();
186                 } else {
187                     writeHelper(cbuf, off, room);
188                     flushInternal();
189                     off += room;
190                     len -= room;
191                 }
192             }
193         }
194
195         private void writeHelper(char[] cbuf, int off, int len) {
196             for (int i = off, end = off + len; i < end; i++) {
197                 char c = cbuf[i];
198                 if (Character.isWhitespace(c)) {
199                     inWhitespace = true;
200                     updateLineBreakState(c);
201                 } else if (inWhitespace) {
202                     inWhitespace = false;
203                     writeLineBreakOrSpace();
204                     buf[pos++] = c;
205                 } else {
206                     buf[pos++] = c;
207                 }
208             }
209         }
210
211         /*
212           \r\n => CRLF
213           \r[^\n] => CR
214           \r$ => CR
215           [^\r]\n => LF
216           ^\n => LF
217         */

218         private void updateLineBreakState(char c)
219         {
220             switch (lineBreakState) {
221             case INIT:
222                 if (c == '\r') {
223                     lineBreakState = SAW_CR;
224                 } else if (c == '\n') {
225                     lineBreakState = LINEBREAK_LF;
226                 }
227                 break;
228             case SAW_CR:
229                 if (c == '\n') {
230                     lineBreakState = LINEBREAK_CRLF;
231                 } else {
232                     lineBreakState = LINEBREAK_CR;
233                 }
234             }
235         }
236
237         private void writeLineBreakOrSpace()
238         {
239             switch (lineBreakState) {
240             case SAW_CR:
241                 // whitespace ended with CR, fall through
242
case LINEBREAK_CR:
243                 buf[pos++] = '\r';
244                 break;
245             case LINEBREAK_CRLF:
246                 buf[pos++] = '\r';
247                 // fall through
248
case LINEBREAK_LF:
249                 buf[pos++] = '\n';
250                 break;
251             case AT_BEGINNING:
252                 // ignore leading whitespace
253
break;
254             case INIT:
255             case SINGLE_LINE:
256                 buf[pos++] = ' ';
257             }
258             lineBreakState = (singleLine) ? SINGLE_LINE : INIT;
259         }
260
261         private void flushInternal() throws IOException JavaDoc {
262             out.write(buf, 0, pos);
263             pos = 0;
264         }
265
266         public void flush() throws IOException JavaDoc {
267             flushInternal();
268             out.flush();
269         }
270
271         public void close() throws IOException JavaDoc {
272             flushInternal();
273         }
274     }
275 }
276
Popular Tags