1 /*
2 * Copyright 2004,2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.apache.bsf.util.cf;
18
19 import java.io.BufferedReader;
20 import java.io.BufferedWriter;
21 import java.io.IOException;
22 import java.io.Reader;
23 import java.io.Writer;
24
25 import org.apache.bsf.util.IndentWriter;
26 import org.apache.bsf.util.StringUtils;
27
28 /**
29 * A <code>CodeFormatter</code> bean is used to format raw Java code. It
30 * indents, word-wraps, and replaces tab characters with an amount of space
31 * characters equal to the size of the <code>indentationStep</code> property.
32 * To create and use a <code>CodeFormatter</code>, you simply instantiate a
33 * new <code>CodeFormatter</code> bean, and invoke
34 * <code>formatCode(Reader source, Writer target)</code> with appropriate
35 * arguments.
36 *
37 * @version 1.0
38 * @author Matthew J. Duftler
39 */
40 public class CodeFormatter
41 {
42 /**
43 * The default maximum line length.
44 */
45 public static final int DEFAULT_MAX = 74;
46 /**
47 * The default size of the indentation step.
48 */
49 public static final int DEFAULT_STEP = 2;
50 /**
51 * The default set of delimiters.
52 */
53 public static final String DEFAULT_DELIM = "(+";
54 /**
55 * The default set of sticky delimiters.
56 */
57 public static final String DEFAULT_S_DELIM = ",";
58
59 // Configurable Parameters
60 private int maxLineLength = DEFAULT_MAX;
61 private int indentationStep = DEFAULT_STEP;
62 private String delimiters = DEFAULT_DELIM;
63 private String stickyDelimiters = DEFAULT_S_DELIM;
64
65 // Global Variables
66 private int indent;
67 private int hangingIndent;
68 private int origIndent;
69 private boolean inCPP_Comment;
70
71 private void addTok(StringBuffer targetBuf, StringBuffer tokBuf,
72 IndentWriter out)
73 {
74 int tokLength = tokBuf.length(),
75 targetLength = targetBuf.length();
76
77 if (indent + targetLength + tokLength > maxLineLength)
78 {
79 if (targetLength == 0)
80 {
81 out.println(indent, tokBuf.toString());
82 indent = hangingIndent;
83 targetBuf.setLength(0);
84
85 return;
86 }
87 else
88 {
89 out.println(indent, targetBuf.toString().trim());
90 indent = hangingIndent;
91 targetBuf.setLength(0);
92 }
93 }
94
95 targetBuf.append(tokBuf.toString());
96
97 return;
98 }
99 /**
100 * Formats the code read from <code>source</code>, and writes the formatted
101 * code to <code>target</code>.
102 *
103 * @param source where to read the unformatted code from.
104 * @param target where to write the formatted code to.
105 */
106 public void formatCode(Reader source, Writer target)
107 {
108 String line;
109 BufferedReader in = new BufferedReader(source);
110 IndentWriter out = new IndentWriter(new BufferedWriter(target), true);
111
112 try
113 {
114 origIndent = 0;
115 inCPP_Comment = false;
116
117 while ((line = in.readLine()) != null)
118 {
119 line = line.trim();
120
121 if (line.length() > 0)
122 {
123 indent = origIndent;
124 hangingIndent = indent + indentationStep;
125 printLine(line, out);
126 }
127 else
128 out.println();
129 }
130 }
131 catch (IOException e)
132 {
133 e.printStackTrace();
134 }
135 }
136 /**
137 * Gets the set of delimiters.
138 *
139 * @return the set of delimiters.
140 * @see #setDelimiters
141 */
142 public String getDelimiters()
143 {
144 return delimiters;
145 }
146 /**
147 * Gets the size of the indentation step.
148 *
149 * @return the size of the indentation step.
150 * @see #setIndentationStep
151 */
152 public int getIndentationStep()
153 {
154 return indentationStep;
155 }
156 /**
157 * Gets the maximum line length.
158 *
159 * @return the maximum line length.
160 * @see #setMaxLineLength
161 */
162 public int getMaxLineLength()
163 {
164 return maxLineLength;
165 }
166 /**
167 * Gets the set of sticky delimiters.
168 *
169 * @return the set of sticky delimiters.
170 * @see #setStickyDelimiters
171 */
172 public String getStickyDelimiters()
173 {
174 return stickyDelimiters;
175 }
176 private void printLine(String line, IndentWriter out)
177 {
178 char[] source = line.toCharArray();
179 char ch;
180 char quoteChar = ' ';
181 boolean inEscapeSequence = false;
182 boolean inString = false;
183 StringBuffer tokBuf = new StringBuffer(),
184 targetBuf = new StringBuffer(hangingIndent + line.length());
185
186 for (int i = 0; i < source.length; i++)
187 {
188 ch = source[i];
189
190 if (inEscapeSequence)
191 {
192 tokBuf.append(ch);
193 inEscapeSequence = false;
194 }
195 else
196 {
197 if (inString)
198 {
199 switch (ch)
200 {
201 case '\\' :
202 tokBuf.append('\\');
203 inEscapeSequence = true;
204 break;
205 case '\'' :
206 case '\"' :
207 tokBuf.append(ch);
208
209 if (ch == quoteChar)
210 {
211 addTok(targetBuf, tokBuf, out);
212 tokBuf.setLength(0);
213 inString = false;
214 }
215 break;
216 case 9 : // pass thru tab characters...
217 tokBuf.append(ch);
218 break;
219 default :
220 if (ch > 31)
221 tokBuf.append(ch);
222 break;
223 }
224 }
225 else // !inString
226 {
227 if (inCPP_Comment)
228 {
229 tokBuf.append(ch);
230
231 if (ch == '/' && i > 0 && source[i - 1] == '*')
232 inCPP_Comment = false;
233 }
234 else
235 {
236 switch (ch)
237 {
238 case '/' :
239 tokBuf.append(ch);
240
241 if (i > 0 && source[i - 1] == '/')
242 {
243 String tokStr = tokBuf.append(source,
244 i + 1,
245 source.length - (i + 1)).toString();
246
247 out.println(indent, targetBuf.append(tokStr).toString());
248
249 return;
250 }
251 break;
252 case '*' :
253 tokBuf.append(ch);
254
255 if (i > 0 && source[i - 1] == '/')
256 inCPP_Comment = true;
257 break;
258 case '\'' :
259 case '\"' :
260 addTok(targetBuf, tokBuf, out);
261 tokBuf.setLength(0);
262 tokBuf.append(ch);
263 quoteChar = ch;
264 inString = true;
265 break;
266 case 9 : // replace tab characters...
267 tokBuf.append(StringUtils.getChars(indentationStep, ' '));
268 break;
269 case '{' :
270 tokBuf.append(ch);
271 origIndent += indentationStep;
272 break;
273 case '}' :
274 tokBuf.append(ch);
275 origIndent -= indentationStep;
276
277 if (i == 0)
278 indent = origIndent;
279 break;
280 default :
281 if (ch > 31)
282 {
283 if (delimiters.indexOf(ch) != -1)
284 {
285 addTok(targetBuf, tokBuf, out);
286 tokBuf.setLength(0);
287 tokBuf.append(ch);
288 }
289 else if (stickyDelimiters.indexOf(ch) != -1)
290 {
291 tokBuf.append(ch);
292 addTok(targetBuf, tokBuf, out);
293 tokBuf.setLength(0);
294 }
295 else
296 tokBuf.append(ch);
297 }
298 break;
299 }
300 }
301 }
302 }
303 }
304
305 if (tokBuf.length() > 0)
306 addTok(targetBuf, tokBuf, out);
307
308 String lastLine = targetBuf.toString().trim();
309
310 if (lastLine.length() > 0)
311 out.println(indent, lastLine);
312 }
313 /**
314 * Sets the set of delimiters; default set is <code>"(+"</code>.
315 * <p>
316 * Each character represents one delimiter. If a line is ready to be
317 * word-wrapped and a delimiter is encountered, the delimiter will
318 * appear as the <em>first character on the following line</em>.
319 * A quotation mark, <code>"</code> or <code>'</code>, opening a string
320 * is always a delimiter, whether you specify it or not.
321 *
322 * @param newDelimiters the new set of delimiters.
323 * @see #getDelimiters
324 */
325 public void setDelimiters(String newDelimiters)
326 {
327 delimiters = newDelimiters;
328 }
329 /**
330 * Sets the size of the indentation step; default size is <code>2</code>.
331 * <p>
332 * This is the number of spaces that lines will be indented (when appropriate).
333 *
334 * @param newIndentationStep the new size of the indentation step.
335 * @see #getIndentationStep
336 */
337 public void setIndentationStep(int newIndentationStep)
338 {
339 indentationStep = (newIndentationStep < 0 ? 0 : newIndentationStep);
340 }
341 /**
342 * Sets the (desired) maximum line length; default length is
343 * <code>74</code>.
344 * <p>
345 * If a token is longer than the requested maximum line length,
346 * then the line containing that token will obviously be longer
347 * than the desired maximum.
348 *
349 * @param newMaxLineLength the new maximum line length.
350 * @see #getMaxLineLength
351 */
352 public void setMaxLineLength(int newMaxLineLength)
353 {
354 maxLineLength = (newMaxLineLength < 0 ? 0 : newMaxLineLength);
355 }
356 /**
357 * Sets the set of sticky delimiters; default set is <code>","</code>.
358 * <p>
359 * Each character represents one sticky delimiter. If a line is ready
360 * to be word-wrapped and a sticky delimiter is encountered, the sticky
361 * delimiter will appear as the <em>last character on the current line</em>.
362 * A quotation mark, <code>"</code> or <code>'</code>, closing a string
363 * is always a sticky delimiter, whether you specify it or not.
364 *
365 * @param newStickyDelimiters the new set of sticky delimiters.
366 * @see #getStickyDelimiters
367 */
368 public void setStickyDelimiters(String newStickyDelimiters)
369 {
370 stickyDelimiters = newStickyDelimiters;
371 }
372 }