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