001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor.ext;
015:
016: import java.io.File;
017: import java.io.FileReader;
018: import java.io.IOException;
019: import java.io.Reader;
020: import java.util.ArrayList;
021: import java.util.Arrays;
022: import java.util.HashMap;
023: import java.util.NoSuchElementException;
024: import java.util.StringTokenizer;
025:
026: /**
027: * Generator of code used for matching the keywords or more generally some group
028: * of words.
029: *
030: * @author Miloslav Metelka
031: * @version 1.00
032: */
033:
034: public class KeywordMatchGenerator {
035:
036: private static final String USAGE = "Usage: java org.netbeans.editor.ext.KeywordMatchGenerator [options]" // NOI18N
037: + " keyword-file [match-function-name]\n\n" // NOI18N
038: + "Options:\n" // NOI18N
039: + " -i Ignore case in matching\n" // NOI18N
040: + " -s Input is in 'input' String or StringBuffer instead of char buffer\n" // NOI18N
041: + "\nGenerator of method that matches" // NOI18N
042: + " the keywords provided in the file.\n" // NOI18N
043: + "Keywords in the file must be separated by spaces or new-lines" // NOI18N
044: + " and they don't need to be sorted.\n"; // NOI18N
045:
046: private static final String UNKNOWN_OPTION = " is unknown option.\n"; // NOI18N
047:
048: public static final String IGNORE_CASE = "-i"; // NOI18N
049:
050: public static final String USE_STRING = "-s"; // NOI18N
051:
052: private static final String DEFAULT_METHOD_NAME = "match"; // NOI18N
053:
054: private static final String[] OPTION_LIST = { IGNORE_CASE,
055: USE_STRING };
056:
057: /** The list of keywords */
058: private String kwds[];
059:
060: /** Maximum length of keyword */
061: private int maxKwdLen;
062:
063: /** Options */
064: private HashMap options = new HashMap();
065:
066: private HashMap kwdConstants = new HashMap();
067:
068: /** Provide indentation (default 2 spaces) */
069: private String indent(int cnt) {
070: StringBuffer sb = new StringBuffer();
071:
072: while (cnt-- > 0) {
073: sb.append(" "); // NOI18N
074: }
075: return sb.toString();
076: }
077:
078: protected void initScan(String methodName) {
079:
080: if (methodName == null) {
081: methodName = DEFAULT_METHOD_NAME;
082: }
083:
084: // write keyword constants table
085: appendString("\n"); // NOI18N
086: for (int i = 0; i < kwds.length; i++) {
087: appendString(indent(1) + "public static final int "
088: + kwdConstants.get(kwds[i]) // NOI18N
089: + " = " + i + ";\n"); // NOI18N
090: }
091: appendString("\n"); // NOI18N
092:
093: // write method header
094: appendString(indent(1) + "public static int "); // NOI18N
095: appendString(methodName);
096: if (options.get(USE_STRING) != null) {
097: appendString("(String buffer, int offset, int len) {\n"); // NOI18N
098: } else {
099: appendString("(char[] buffer, int offset, int len) {\n"); // NOI18N
100: }
101: appendString(indent(2) + "if (len > " + maxKwdLen + ")\n"); // NOI18N
102: appendString(indent(3) + "return -1;\n"); // NOI18N
103: }
104:
105: public void scan() {
106: scan(0, kwds.length, 0, 2, 0);
107: }
108:
109: protected void finishScan() {
110: appendString(indent(1) + "}\n\n"); // NOI18N
111: }
112:
113: public void addOption(String option) {
114: options.put(option, option);
115: }
116:
117: protected String getKwdConstantPrefix() {
118: return ""; // "KWD_"; // NOI18N
119: }
120:
121: protected String getKwdConstant(String kwd) {
122: return (String) kwdConstants.get(kwd);
123: }
124:
125: protected boolean upperCaseKeyConstants() {
126: return true;
127: }
128:
129: /** Parse the keywords from a string */
130: private void parseKeywords(String s) {
131: ArrayList keyList = new ArrayList();
132: StringTokenizer strTok = new StringTokenizer(s);
133:
134: try {
135: while (true) {
136: String key = strTok.nextToken();
137: int keyLen = key.length();
138: maxKwdLen = Math.max(maxKwdLen, keyLen);
139: keyList.add(key);
140: kwdConstants.put(key, getKwdConstantPrefix()
141: + (upperCaseKeyConstants() ? key.toUpperCase()
142: : key));
143: }
144: } catch (NoSuchElementException e) {
145: // no more elements
146: }
147:
148: kwds = new String[keyList.size()];
149: keyList.toArray(kwds);
150: Arrays.sort(kwds);
151: }
152:
153: protected String getCurrentChar() {
154: boolean useString = (options.get(USE_STRING) != null);
155: boolean ignoreCase = (options.get(IGNORE_CASE) != null);
156:
157: if (useString) {
158: return ignoreCase ? "Character.toLowerCase(buffer.charAt(offset++))" // NOI18N
159: : "buffer.charAt(offset++)"; // NOI18N
160: } else {
161: return ignoreCase ? "Character.toLowerCase(buffer[offset++])" // NOI18N
162: : "buffer[offset++]"; // NOI18N
163: }
164: }
165:
166: private void appendCheckedReturn(String kwd, int offset, int indent) {
167: appendString(indent(indent) + "return (len == " // NOI18N
168: + kwd.length());
169:
170: int kwdLenM1 = kwd.length() - 1;
171: for (int k = offset; k <= kwdLenM1; k++) {
172: appendString("\n" + indent(indent + 1) + "&& "); // NOI18N
173: appendString(getCurrentChar() + " == '" + kwd.charAt(k)
174: + "'"); // NOI18N
175: }
176:
177: appendString(")\n" + indent(indent + 2) + "? "
178: + getKwdConstant(kwd) + " : -1;\n"); // NOI18N
179: }
180:
181: protected void appendString(String s) {
182: System.out.print(s);
183: }
184:
185: /**
186: * Scan the keywords and generate the output. This method is initially
187: * called with the full range of keywords and offset equal to zero. It
188: * recursively calls itself to scan the subgroups.
189: *
190: * @param indFrom
191: * index in kwds[] where the subgroup of keywords starts
192: * @pararm indTo index in kwds[] where the subgroup of keywords ends
193: * @param offset
194: * current horizontal offset. It's incremented as the subgroups
195: * are recognized. All the characters prior to offset index are
196: * the same in all keywords in the group.
197: */
198: private void scan(int indFrom, int indTo, int offset, int indent,
199: int minKwdLen) {
200: // System.out.println(">>>DEBUG<<< indFrom=" + indFrom + ", indTo=" +
201: // indTo + ", offset=" + offset + ", indent=" + indent + ", minKwdLen="+
202: // minKwdLen); // NOI18N
203: int maxLen = 0;
204: for (int i = indFrom; i < indTo; i++) {
205: maxLen = Math.max(maxLen, kwds[i].length());
206: }
207:
208: int same;
209: int minLen;
210: do {
211: minLen = Integer.MAX_VALUE;
212: // Compute minimum and maximum keyword length in the current group
213: for (int i = indFrom; i < indTo; i++) {
214: minLen = Math.min(minLen, kwds[i].length());
215: }
216:
217: // System.out.println(">>>DEBUG<<< while(): minLen=" + minLen + ",
218: // minKwdLen=" + minKwdLen); // NOI18N
219: if (minLen > minKwdLen) {
220: appendString(indent(indent) + "if (len <= "
221: + (minLen - 1) + ")\n"); // NOI18N
222: appendString(indent(indent + 1) + "return -1;\n"); // NOI18N
223: }
224:
225: // Compute how many chars from current offset on are the same
226: // in all keywords in the current group
227: same = 0;
228: boolean stop = false;
229: for (int i = offset; i < minLen; i++) {
230: char c = kwds[indFrom].charAt(i);
231: for (int j = indFrom + 1; j < indTo; j++) {
232: if (kwds[j].charAt(i) != c) {
233: stop = true;
234: break;
235: }
236: }
237: if (stop) {
238: break;
239: }
240: same++;
241: }
242:
243: // System.out.println(">>>DEBUG<<< minLen=" + minLen + ", maxLen=" +
244: // maxLen + ", same=" + same); // NOI18N
245:
246: // Add check for all the same chars
247: if (same > 0) {
248: appendString(indent(indent) + "if ("); // NOI18N
249: for (int i = 0; i < same; i++) {
250: if (i > 0) {
251: appendString(indent(indent + 1) + "|| "); // NOI18N
252: }
253: appendString(getCurrentChar() + " != '"
254: + kwds[indFrom].charAt(offset + i) + "'"); // NOI18N
255: if (i < same - 1) {
256: appendString("\n"); // NOI18N
257: }
258: }
259: appendString(")\n" + indent(indent + 2)
260: + "return -1;\n"); // NOI18N
261:
262: }
263:
264: // Increase the offset to the first 'non-same' char
265: offset += same;
266:
267: // If there's a keyword with the length equal to the current offset
268: // it will be first in the (sorted) group and it will be matched now
269: if (offset == kwds[indFrom].length()) {
270: appendString(indent(indent) + "if (len == " + offset
271: + ")\n"); // NOI18N
272: appendString(indent(indent + 1) + "return " // NOI18N
273: + getKwdConstant(kwds[indFrom]) + ";\n"); // NOI18N
274: indFrom++; // increase starting index as first keyword already
275: // matched
276: if (offset >= minLen) {
277: minLen = offset + 1;
278: }
279: }
280:
281: minKwdLen = minLen; // minLen already tested, so assign new minimum
282:
283: } while (same > 0 && indFrom < indTo);
284:
285: // If there are other chars at the end of any keyword,
286: // add the switch statement
287: if (offset < maxLen) {
288: appendString(indent(indent) + "switch (" + getCurrentChar()
289: + ") {\n"); // NOI18N
290:
291: // Compute subgroups
292: int i = indFrom;
293: while (i < indTo) {
294: // Add the case statement
295: char actChar = kwds[i].charAt(offset);
296: appendString(indent(indent + 1) + "case '" + actChar
297: + "':\n"); // NOI18N
298:
299: // Check whether the subgroup will have more than one keyword
300: int subGroupEndInd = i + 1;
301: while (subGroupEndInd < indTo
302: && kwds[subGroupEndInd].length() > offset
303: && kwds[subGroupEndInd].charAt(offset) == actChar) {
304: subGroupEndInd++;
305: }
306:
307: if (subGroupEndInd > i + 1) { // more than one keyword in
308: // subgroup
309: scan(i, subGroupEndInd, offset + 1, indent + 2,
310: minLen);
311: } else { // just one keyword in the subgroup
312: appendCheckedReturn(kwds[i], offset + 1, indent + 2);
313: }
314:
315: // advance current index to the end of current subgroup
316: i = subGroupEndInd;
317: }
318:
319: appendString(indent(indent + 1) + "default:\n"); // NOI18N
320: appendString(indent(indent + 2) + "return -1;\n"); // NOI18N
321: appendString(indent(indent) + "}\n"); // NOI18N
322: } else { // no add-on chars, keyword not found in this case
323: appendString(indent(indent) + "return -1;\n"); // NOI18N
324: }
325:
326: }
327:
328: /** Main method */
329: public static void main(String args[]) {
330: KeywordMatchGenerator km = new KeywordMatchGenerator();
331:
332: // parse options
333: int argShift;
334: for (argShift = 0; argShift < args.length; argShift++) {
335: int j;
336: if (args[argShift].charAt(0) != '-') {
337: break; // no more options
338: }
339: for (j = 0; j < OPTION_LIST.length; j++) {
340: if (args[argShift].equals(OPTION_LIST[j])) {
341: km.addOption(OPTION_LIST[j]);
342: break;
343: }
344: }
345: if (j == OPTION_LIST.length) {
346: System.err.println("'" + args[argShift] + "'"
347: + UNKNOWN_OPTION); // NOI18N
348: System.err.println(USAGE);
349: return;
350: }
351: }
352:
353: // check count of mandatory args
354: if (args.length - argShift < 1) {
355: System.err.println(USAGE);
356: return;
357: }
358:
359: // read keyword file
360: String kwds = null;
361: try {
362: File f = new File(args[argShift]);
363: if (!f.exists()) {
364: System.err.println("Non-existent file '"
365: + args[argShift] + "'"); // NOI18N
366: return;
367: }
368: char arr[] = new char[(int) f.length()];
369: Reader isr = new FileReader(f);
370:
371: int n = 0;
372: while (n < f.length()) {
373: int count = isr.read(arr, n, (int) f.length() - n);
374: if (count < 0)
375: break;
376: n += count;
377: }
378:
379: kwds = new String(arr);
380: } catch (IOException e) {
381: // IO exception
382: System.err.println("Cannot read from keyword file '"
383: + args[argShift] + "'"); // NOI18N
384: return;
385: }
386:
387: // Check for optional method name
388: String methodName = null;
389: if (args.length - argShift >= 2) {
390: methodName = args[argShift + 1];
391: }
392:
393: // generate
394: km.parseKeywords(kwds);
395: km.initScan(methodName);
396: km.scan();
397: km.finishScan();
398:
399: }
400:
401: }
|