001: /*
002: * Copyright 2004,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.bsf.util.cf;
018:
019: import java.io.BufferedReader;
020: import java.io.BufferedWriter;
021: import java.io.IOException;
022: import java.io.Reader;
023: import java.io.Writer;
024:
025: import org.apache.bsf.util.IndentWriter;
026: import org.apache.bsf.util.StringUtils;
027:
028: /**
029: * A <code>CodeFormatter</code> bean is used to format raw Java code. It
030: * indents, word-wraps, and replaces tab characters with an amount of space
031: * characters equal to the size of the <code>indentationStep</code> property.
032: * To create and use a <code>CodeFormatter</code>, you simply instantiate a
033: * new <code>CodeFormatter</code> bean, and invoke
034: * <code>formatCode(Reader source, Writer target)</code> with appropriate
035: * arguments.
036: *
037: * @version 1.0
038: * @author Matthew J. Duftler
039: */
040: public class CodeFormatter {
041: /**
042: * The default maximum line length.
043: */
044: public static final int DEFAULT_MAX = 74;
045: /**
046: * The default size of the indentation step.
047: */
048: public static final int DEFAULT_STEP = 2;
049: /**
050: * The default set of delimiters.
051: */
052: public static final String DEFAULT_DELIM = "(+";
053: /**
054: * The default set of sticky delimiters.
055: */
056: public static final String DEFAULT_S_DELIM = ",";
057:
058: // Configurable Parameters
059: private int maxLineLength = DEFAULT_MAX;
060: private int indentationStep = DEFAULT_STEP;
061: private String delimiters = DEFAULT_DELIM;
062: private String stickyDelimiters = DEFAULT_S_DELIM;
063:
064: // Global Variables
065: private int indent;
066: private int hangingIndent;
067: private int origIndent;
068: private boolean inCPP_Comment;
069:
070: private void addTok(StringBuffer targetBuf, StringBuffer tokBuf,
071: IndentWriter out) {
072: int tokLength = tokBuf.length(), targetLength = targetBuf
073: .length();
074:
075: if (indent + targetLength + tokLength > maxLineLength) {
076: if (targetLength == 0) {
077: out.println(indent, tokBuf.toString());
078: indent = hangingIndent;
079: targetBuf.setLength(0);
080:
081: return;
082: } else {
083: out.println(indent, targetBuf.toString().trim());
084: indent = hangingIndent;
085: targetBuf.setLength(0);
086: }
087: }
088:
089: targetBuf.append(tokBuf.toString());
090:
091: return;
092: }
093:
094: /**
095: * Formats the code read from <code>source</code>, and writes the formatted
096: * code to <code>target</code>.
097: *
098: * @param source where to read the unformatted code from.
099: * @param target where to write the formatted code to.
100: */
101: public void formatCode(Reader source, Writer target) {
102: String line;
103: BufferedReader in = new BufferedReader(source);
104: IndentWriter out = new IndentWriter(new BufferedWriter(target),
105: true);
106:
107: try {
108: origIndent = 0;
109: inCPP_Comment = false;
110:
111: while ((line = in.readLine()) != null) {
112: line = line.trim();
113:
114: if (line.length() > 0) {
115: indent = origIndent;
116: hangingIndent = indent + indentationStep;
117: printLine(line, out);
118: } else
119: out.println();
120: }
121: } catch (IOException e) {
122: e.printStackTrace();
123: }
124: }
125:
126: /**
127: * Gets the set of delimiters.
128: *
129: * @return the set of delimiters.
130: * @see #setDelimiters
131: */
132: public String getDelimiters() {
133: return delimiters;
134: }
135:
136: /**
137: * Gets the size of the indentation step.
138: *
139: * @return the size of the indentation step.
140: * @see #setIndentationStep
141: */
142: public int getIndentationStep() {
143: return indentationStep;
144: }
145:
146: /**
147: * Gets the maximum line length.
148: *
149: * @return the maximum line length.
150: * @see #setMaxLineLength
151: */
152: public int getMaxLineLength() {
153: return maxLineLength;
154: }
155:
156: /**
157: * Gets the set of sticky delimiters.
158: *
159: * @return the set of sticky delimiters.
160: * @see #setStickyDelimiters
161: */
162: public String getStickyDelimiters() {
163: return stickyDelimiters;
164: }
165:
166: private void printLine(String line, IndentWriter out) {
167: char[] source = line.toCharArray();
168: char ch;
169: char quoteChar = ' ';
170: boolean inEscapeSequence = false;
171: boolean inString = false;
172: StringBuffer tokBuf = new StringBuffer(), targetBuf = new StringBuffer(
173: hangingIndent + line.length());
174:
175: for (int i = 0; i < source.length; i++) {
176: ch = source[i];
177:
178: if (inEscapeSequence) {
179: tokBuf.append(ch);
180: inEscapeSequence = false;
181: } else {
182: if (inString) {
183: switch (ch) {
184: case '\\':
185: tokBuf.append('\\');
186: inEscapeSequence = true;
187: break;
188: case '\'':
189: case '\"':
190: tokBuf.append(ch);
191:
192: if (ch == quoteChar) {
193: addTok(targetBuf, tokBuf, out);
194: tokBuf.setLength(0);
195: inString = false;
196: }
197: break;
198: case 9: // pass thru tab characters...
199: tokBuf.append(ch);
200: break;
201: default:
202: if (ch > 31)
203: tokBuf.append(ch);
204: break;
205: }
206: } else // !inString
207: {
208: if (inCPP_Comment) {
209: tokBuf.append(ch);
210:
211: if (ch == '/' && i > 0 && source[i - 1] == '*')
212: inCPP_Comment = false;
213: } else {
214: switch (ch) {
215: case '/':
216: tokBuf.append(ch);
217:
218: if (i > 0 && source[i - 1] == '/') {
219: String tokStr = tokBuf.append(source,
220: i + 1, source.length - (i + 1))
221: .toString();
222:
223: out.println(indent, targetBuf.append(
224: tokStr).toString());
225:
226: return;
227: }
228: break;
229: case '*':
230: tokBuf.append(ch);
231:
232: if (i > 0 && source[i - 1] == '/')
233: inCPP_Comment = true;
234: break;
235: case '\'':
236: case '\"':
237: addTok(targetBuf, tokBuf, out);
238: tokBuf.setLength(0);
239: tokBuf.append(ch);
240: quoteChar = ch;
241: inString = true;
242: break;
243: case 9: // replace tab characters...
244: tokBuf.append(StringUtils.getChars(
245: indentationStep, ' '));
246: break;
247: case '{':
248: tokBuf.append(ch);
249: origIndent += indentationStep;
250: break;
251: case '}':
252: tokBuf.append(ch);
253: origIndent -= indentationStep;
254:
255: if (i == 0)
256: indent = origIndent;
257: break;
258: default:
259: if (ch > 31) {
260: if (delimiters.indexOf(ch) != -1) {
261: addTok(targetBuf, tokBuf, out);
262: tokBuf.setLength(0);
263: tokBuf.append(ch);
264: } else if (stickyDelimiters.indexOf(ch) != -1) {
265: tokBuf.append(ch);
266: addTok(targetBuf, tokBuf, out);
267: tokBuf.setLength(0);
268: } else
269: tokBuf.append(ch);
270: }
271: break;
272: }
273: }
274: }
275: }
276: }
277:
278: if (tokBuf.length() > 0)
279: addTok(targetBuf, tokBuf, out);
280:
281: String lastLine = targetBuf.toString().trim();
282:
283: if (lastLine.length() > 0)
284: out.println(indent, lastLine);
285: }
286:
287: /**
288: * Sets the set of delimiters; default set is <code>"(+"</code>.
289: * <p>
290: * Each character represents one delimiter. If a line is ready to be
291: * word-wrapped and a delimiter is encountered, the delimiter will
292: * appear as the <em>first character on the following line</em>.
293: * A quotation mark, <code>"</code> or <code>'</code>, opening a string
294: * is always a delimiter, whether you specify it or not.
295: *
296: * @param newDelimiters the new set of delimiters.
297: * @see #getDelimiters
298: */
299: public void setDelimiters(String newDelimiters) {
300: delimiters = newDelimiters;
301: }
302:
303: /**
304: * Sets the size of the indentation step; default size is <code>2</code>.
305: * <p>
306: * This is the number of spaces that lines will be indented (when appropriate).
307: *
308: * @param newIndentationStep the new size of the indentation step.
309: * @see #getIndentationStep
310: */
311: public void setIndentationStep(int newIndentationStep) {
312: indentationStep = (newIndentationStep < 0 ? 0
313: : newIndentationStep);
314: }
315:
316: /**
317: * Sets the (desired) maximum line length; default length is
318: * <code>74</code>.
319: * <p>
320: * If a token is longer than the requested maximum line length,
321: * then the line containing that token will obviously be longer
322: * than the desired maximum.
323: *
324: * @param newMaxLineLength the new maximum line length.
325: * @see #getMaxLineLength
326: */
327: public void setMaxLineLength(int newMaxLineLength) {
328: maxLineLength = (newMaxLineLength < 0 ? 0 : newMaxLineLength);
329: }
330:
331: /**
332: * Sets the set of sticky delimiters; default set is <code>","</code>.
333: * <p>
334: * Each character represents one sticky delimiter. If a line is ready
335: * to be word-wrapped and a sticky delimiter is encountered, the sticky
336: * delimiter will appear as the <em>last character on the current line</em>.
337: * A quotation mark, <code>"</code> or <code>'</code>, closing a string
338: * is always a sticky delimiter, whether you specify it or not.
339: *
340: * @param newStickyDelimiters the new set of sticky delimiters.
341: * @see #getStickyDelimiters
342: */
343: public void setStickyDelimiters(String newStickyDelimiters) {
344: stickyDelimiters = newStickyDelimiters;
345: }
346: }
|