001: /*
002: * JavaFormatter.java
003: *
004: * Copyright (C) 1998-2004 Peter Graves
005: * $Id: JavaFormatter.java,v 1.3 2004/02/28 17:59:08 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j;
023:
024: public final class JavaFormatter extends Formatter implements Constants {
025: private static final int JAVA_FORMAT_TEXT = 0;
026: private static final int JAVA_FORMAT_COMMENT = 1;
027: private static final int JAVA_FORMAT_STRING = 2;
028: private static final int JAVA_FORMAT_IDENTIFIER = 3;
029: private static final int JAVA_FORMAT_KEYWORD = 4;
030: private static final int JAVA_FORMAT_FUNCTION = 5;
031: private static final int JAVA_FORMAT_OPERATOR = 6;
032: private static final int JAVA_FORMAT_BRACE = 7;
033: private static final int JAVA_FORMAT_NUMBER = 8;
034:
035: public static final int JAVA_FORMAT_LAST = 8;
036:
037: private final int language;
038:
039: public JavaFormatter(Buffer buffer) {
040: this (buffer, LANGUAGE_JAVA);
041: }
042:
043: public JavaFormatter(Buffer buffer, int language) {
044: this .buffer = buffer;
045: this .language = language;
046: }
047:
048: private int tokenBegin = 0;
049:
050: private void endToken(String text, int tokenEnd, int state) {
051: if (tokenEnd - tokenBegin > 0) {
052: int format = JAVA_FORMAT_TEXT;
053: switch (state) {
054: case STATE_NEUTRAL:
055: format = JAVA_FORMAT_TEXT;
056: break;
057: case STATE_QUOTE:
058: format = JAVA_FORMAT_STRING;
059: break;
060: case STATE_IDENTIFIER:
061: format = JAVA_FORMAT_IDENTIFIER;
062: break;
063: case STATE_COMMENT:
064: format = JAVA_FORMAT_COMMENT;
065: break;
066: case STATE_OPERATOR:
067: format = JAVA_FORMAT_OPERATOR;
068: break;
069: case STATE_BRACE:
070: format = JAVA_FORMAT_BRACE;
071: break;
072: case STATE_NUMBER:
073: case STATE_HEXNUMBER:
074: format = JAVA_FORMAT_NUMBER;
075: break;
076: }
077: addSegment(text, tokenBegin, tokenEnd, format);
078: tokenBegin = tokenEnd;
079: }
080: }
081:
082: private void parseLine(Line line) {
083: final String text = getDetabbedText(line);
084: tokenBegin = 0;
085: boolean isPreprocessorLine = false;
086: char quoteChar = '\0';
087: int state = line.flags();
088: if (state == STATE_QUOTE)
089: quoteChar = '"';
090: int i = 0;
091: final int limit = text.length();
092: // Skip whitespace at start of line.
093: while (i < limit) {
094: if (Character.isWhitespace(text.charAt(i)))
095: ++i;
096: else {
097: endToken(text, i, state);
098: break;
099: }
100: }
101: char c;
102: if (state == STATE_SCRIPT)
103: state = STATE_NEUTRAL;
104: while (i < limit) {
105: c = text.charAt(i);
106: if (state == STATE_COMMENT) {
107: if (i < limit - 1 && c == '*'
108: && text.charAt(i + 1) == '/') {
109: endToken(text, i + 2, state);
110: state = STATE_NEUTRAL;
111: i += 2;
112: } else
113: ++i;
114: continue;
115: }
116: if (state == STATE_QUOTE) {
117: if (c == quoteChar) {
118: endToken(text, i + 1, state);
119: state = STATE_NEUTRAL;
120: } else if (c == '\\' && i < limit - 1) {
121: // Escape char.
122: ++i;
123: }
124: ++i;
125: continue;
126: }
127: // Reaching here, we're not in a comment or a quoted string.
128: if (c == '"' || c == '\'') {
129: endToken(text, i, state);
130: state = STATE_QUOTE;
131: quoteChar = c;
132: ++i;
133: continue;
134: }
135: if (c == '/') {
136: if (i < limit - 1) {
137: if (text.charAt(i + 1) == '*') {
138: endToken(text, i, state);
139: state = STATE_COMMENT;
140: i += 2;
141: } else if (text.charAt(i + 1) == '/') {
142: endToken(text, i, state);
143: endToken(text, limit, STATE_COMMENT);
144: return;
145: } else
146: ++i;
147: } else
148: ++i;
149: continue;
150: }
151: if (isOperatorChar(c)) {
152: if (state != STATE_OPERATOR) {
153: endToken(text, i, state);
154: // Check for keyword (as in e.g. "char*").
155: LineSegment segment = getLastSegment();
156: if (segment != null && isKeyword(segment.getText()))
157: segment.setFormat(JAVA_FORMAT_KEYWORD);
158: state = STATE_OPERATOR;
159: }
160: ++i;
161: continue;
162: }
163: if (c == '{' || c == '}') {
164: if (state != STATE_BRACE) {
165: endToken(text, i, state);
166: // Check for keyword (e.g. "try").
167: LineSegment segment = getLastSegment();
168: if (segment != null && isKeyword(segment.getText()))
169: segment.setFormat(JAVA_FORMAT_KEYWORD);
170: state = STATE_BRACE;
171: }
172: ++i;
173: continue;
174: }
175: if (state == STATE_OPERATOR || state == STATE_BRACE) {
176: if (Character.isJavaIdentifierStart(c)) {
177: endToken(text, i, state);
178: state = STATE_IDENTIFIER;
179: } else if (Character.isDigit(c)) {
180: endToken(text, i, state);
181: state = STATE_NUMBER;
182: } else {
183: endToken(text, i, state);
184: state = STATE_NEUTRAL;
185: }
186: ++i;
187: continue;
188: }
189: if (state == STATE_IDENTIFIER) {
190: if (!Character.isJavaIdentifierPart(c)) {
191: endToken(text, i, state);
192: // Check for keyword or function.
193: LineSegment segment = getLastSegment();
194: if (segment != null) {
195: String segmentText = segment.getText();
196: if (!isPreprocessorLine
197: && isKeyword(segmentText))
198: segment.setFormat(JAVA_FORMAT_KEYWORD);
199: else if (c == '(')
200: segment.setFormat(JAVA_FORMAT_FUNCTION);
201: else if (Character.isWhitespace(c)) {
202: // Look ahead to see if next non-whitespace char is '('.
203: int j = i + 1;
204: while (j < limit
205: && Character.isWhitespace(c = text
206: .charAt(j)))
207: ++j;
208: if (c == '(')
209: segment.setFormat(JAVA_FORMAT_FUNCTION);
210: }
211: }
212: state = STATE_NEUTRAL;
213: }
214: ++i;
215: continue;
216: }
217: if (state == STATE_NUMBER) {
218: if (Character.isDigit(c))
219: ;
220: else if (c == 'u' || c == 'U' || c == 'l' || c == 'L')
221: ;
222: else if (i - tokenBegin == 1 && c == 'x' || c == 'X')
223: state = STATE_HEXNUMBER;
224: else {
225: endToken(text, i, state);
226: if (Character.isJavaIdentifierStart(c))
227: state = STATE_IDENTIFIER;
228: else
229: state = STATE_NEUTRAL;
230: }
231: ++i;
232: continue;
233: }
234: if (state == STATE_HEXNUMBER) {
235: if (Character.isDigit(c))
236: ;
237: else if ((c >= 'a' && c <= 'f')
238: || (c >= 'A' && c <= 'F'))
239: ;
240: else if (c == 'u' || c == 'U' || c == 'l' || c == 'L')
241: ;
242: else {
243: endToken(text, i, state);
244: if (Character.isJavaIdentifierStart(c))
245: state = STATE_IDENTIFIER;
246: else
247: state = STATE_NEUTRAL;
248: }
249: ++i;
250: continue;
251: }
252: if (state == STATE_NEUTRAL) {
253: if (Character.isJavaIdentifierStart(c)) {
254: endToken(text, i, state);
255: state = STATE_IDENTIFIER;
256: } else if (Character.isDigit(c)) {
257: endToken(text, i, state);
258: state = STATE_NUMBER;
259: }
260: }
261: ++i;
262: }
263: // Reached end of line.
264: endToken(text, i, state);
265: if (state == STATE_IDENTIFIER) {
266: // Last token might be a keyword.
267: LineSegment segment = getLastSegment();
268: if (segment != null && isKeyword(segment.getText()))
269: segment.setFormat(JAVA_FORMAT_KEYWORD);
270: }
271: }
272:
273: public LineSegmentList formatLine(Line line) {
274: clearSegmentList();
275: if (line == null) {
276: addSegment("", JAVA_FORMAT_TEXT);
277: return segmentList;
278: }
279: parseLine(line);
280: return segmentList;
281: }
282:
283: public boolean parseBuffer() {
284: int state = STATE_NEUTRAL;
285: Line line = buffer.getFirstLine();
286: boolean changed = false;
287: while (line != null) {
288: int oldflags = line.flags();
289: // Quoted strings can't span lines in Java.
290: if (state == STATE_QUOTE && language == LANGUAGE_JAVA)
291: state = STATE_NEUTRAL;
292:
293: if (state != oldflags) {
294: line.setFlags(state);
295: changed = true;
296: }
297: char quoteChar = state == STATE_QUOTE ? '"' : '\0';
298: final int limit = line.length();
299: for (int i = 0; i < limit; i++) {
300: char c = line.charAt(i);
301: if (c == '\\' && i < limit - 1) {
302: // Escape.
303: ++i;
304: continue;
305: }
306: if (state == STATE_COMMENT) {
307: if (c == '*' && i < limit - 1) {
308: c = line.charAt(i + 1);
309: if (c == '/') {
310: ++i;
311: state = STATE_NEUTRAL;
312: }
313: }
314: continue;
315: }
316: if (state == STATE_QUOTE) {
317: if (c == quoteChar) {
318: state = STATE_NEUTRAL;
319: quoteChar = '\0';
320: }
321: continue;
322: }
323:
324: // Not in comment or quoted string.
325: if (c == '/' && i < limit - 1) {
326: c = line.charAt(++i);
327: if (c == '/') {
328: // Single-line comment beginning.
329: // Ignore rest of line.
330: break;
331: } else if (c == '*')
332: state = STATE_COMMENT;
333: } else if (c == '"' || c == '\'') {
334: state = STATE_QUOTE;
335: quoteChar = c;
336: }
337: }
338: line = line.next();
339: }
340: buffer.setNeedsParsing(false);
341: return changed;
342: }
343:
344: private static final boolean isOperatorChar(char c) {
345: return "!&|<>=+/*-^".indexOf(c) >= 0;
346: }
347:
348: public FormatTable getFormatTable() {
349: if (formatTable == null) {
350: formatTable = new FormatTable("JavaMode");
351: formatTable.addEntryFromPrefs(JAVA_FORMAT_TEXT, "text");
352: formatTable.addEntryFromPrefs(JAVA_FORMAT_COMMENT,
353: "comment");
354: formatTable.addEntryFromPrefs(JAVA_FORMAT_STRING, "string");
355: formatTable.addEntryFromPrefs(JAVA_FORMAT_IDENTIFIER,
356: "identifier", "text");
357: formatTable.addEntryFromPrefs(JAVA_FORMAT_KEYWORD,
358: "keyword");
359: formatTable.addEntryFromPrefs(JAVA_FORMAT_FUNCTION,
360: "function");
361: formatTable.addEntryFromPrefs(JAVA_FORMAT_OPERATOR,
362: "operator");
363: formatTable.addEntryFromPrefs(JAVA_FORMAT_BRACE, "brace");
364: formatTable.addEntryFromPrefs(JAVA_FORMAT_NUMBER, "number");
365: }
366: return formatTable;
367: }
368: }
|