2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.editor.ext;
44
import java.util.Arrays;
45
import java.util.ArrayList;
46
import java.util.HashMap;
47
import java.util.StringTokenizer;
48
import java.util.NoSuchElementException;
49
import java.io.IOException;
51
import java.io.Reader;
52
import java.io.FileReader;
55
* Generator of code used for matching the keywords or more generally some
58
* @author Miloslav Metelka
62
public class KeywordMatchGenerator {
64
private static final String USAGE
65
= "Usage: java org.netbeans.editor.ext.KeywordMatchGenerator [options]" // NOI18N
66
+ " keyword-file [match-function-name]\n\n" // NOI18N
67
+ "Options:\n" // NOI18N
68
+ " -i Ignore case in matching\n" // NOI18N
69
+ " -s Input is in 'input' String or StringBuffer instead of char buffer\n" // NOI18N
70
+ "\nGenerator of method that matches" // NOI18N
71
+ " the keywords provided in the file.\n" // NOI18N
72
+ "Keywords in the file must be separated by spaces or new-lines" // NOI18N
73
+ " and they don't need to be sorted.\n"; // NOI18N
75
private static final String UNKNOWN_OPTION = " is unknown option.\n"; // NOI18N
77
public static final String IGNORE_CASE = "-i"; // NOI18N
79
public static final String USE_STRING = "-s"; // NOI18N
81
private static final String DEFAULT_METHOD_NAME = "match"; // NOI18N
83
private static final String[] OPTION_LIST = { IGNORE_CASE, USE_STRING };
85
/** The list of keywords */
86
private String kwds[];
88
/** Maximum length of keyword */
89
private int maxKwdLen;
92
private HashMap options = new HashMap();
94
private HashMap kwdConstants = new HashMap();
96
/** Provide indentation (default 2 spaces) */
97
private String indent(int cnt) {
98
StringBuffer sb = new StringBuffer();
101
sb.append(" "); // NOI18N
103
return sb.toString();
106
protected void initScan(String methodName) {
108
if (methodName == null) {
109
methodName = DEFAULT_METHOD_NAME;
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 " + kwdConstants.get(kwds[i]) // NOI18N
116
+ " = " + i + ";\n"); // NOI18N
118
appendString("\n"); // NOI18N
120
// write method header
121
appendString(indent(1) + "public static int "); // NOI18N
122
appendString(methodName);
123
if (options.get(USE_STRING) != null) {
124
appendString("(String buffer, int offset, int len) {\n"); // NOI18N
126
appendString("(char[] buffer, int offset, int len) {\n"); // NOI18N
128
appendString(indent(2) + "if (len > " + maxKwdLen + ")\n"); // NOI18N
129
appendString(indent(3) + "return -1;\n"); // NOI18N
133
scan(0, kwds.length, 0, 2, 0);
136
protected void finishScan() {
137
appendString(indent(1) + "}\n\n"); // NOI18N
140
public void addOption(String option) {
141
options.put(option, option);
144
protected String getKwdConstantPrefix() {
145
return ""; // "KWD_"; // NOI18N
148
protected String getKwdConstant(String kwd) {
149
return (String)kwdConstants.get(kwd);
152
protected boolean upperCaseKeyConstants() {
156
/** Parse the keywords from a string */
157
private void parseKeywords(String s) {
158
ArrayList keyList = new ArrayList();
159
StringTokenizer strTok = new StringTokenizer(s);
163
String key = strTok.nextToken();
164
int keyLen = key.length();
165
maxKwdLen = Math.max(maxKwdLen, keyLen);
167
kwdConstants.put(key, getKwdConstantPrefix()
168
+ (upperCaseKeyConstants() ? key.toUpperCase() : key));
170
} catch(NoSuchElementException e) {
174
kwds = new String[keyList.size()];
175
keyList.toArray(kwds);
179
protected String getCurrentChar() {
180
boolean useString = (options.get(USE_STRING) != null);
181
boolean ignoreCase = (options.get(IGNORE_CASE) != null);
184
return ignoreCase ? "Character.toLowerCase(buffer.charAt(offset++))" // NOI18N
185
: "buffer.charAt(offset++)"; // NOI18N
187
return ignoreCase ? "Character.toLowerCase(buffer[offset++])" // NOI18N
188
: "buffer[offset++]"; // NOI18N
192
private void appendCheckedReturn(String kwd, int offset, int indent) {
193
appendString(indent(indent) + "return (len == " // NOI18N
196
int kwdLenM1 = kwd.length() - 1;
197
for(int k = offset; k <= kwdLenM1; k++) {
198
appendString("\n" + indent(indent + 1) + "&& "); // NOI18N
199
appendString(getCurrentChar() + " == '" + kwd.charAt(k) + "'"); // NOI18N
202
appendString(")\n" + indent(indent + 2) + "? " + getKwdConstant(kwd) + " : -1;\n"); // NOI18N
205
protected void appendString(String s) {
209
/** Scan the keywords and generate the output. This method is initially
210
* called with the full range of keywords and offset equal to zero.
211
* It recursively calls itself to scan the subgroups.
212
* @param indFrom index in kwds[] where the subgroup of keywords starts
213
* @pararm indTo index in kwds[] where the subgroup of keywords ends
214
* @param offset current horizontal offset. It's incremented as the subgroups
215
* are recognized. All the characters prior to offset index are the same
216
* in all keywords in the group.
218
private void scan(int indFrom, int indTo, int offset, int indent, int minKwdLen) {
219
// System.out.println(">>>DEBUG<<< indFrom=" + indFrom + ", indTo=" + indTo + ", offset=" + offset + ", indent=" + indent + ", minKwdLen="+ minKwdLen); // NOI18N
221
for (int i = indFrom; i < indTo; i++) {
222
maxLen = Math.max(maxLen, kwds[i].length());
228
minLen = Integer.MAX_VALUE;
229
// Compute minimum and maximum keyword length in the current group
230
for (int i = indFrom; i < indTo; i++) {
231
minLen = Math.min(minLen, kwds[i].length());
234
// System.out.println(">>>DEBUG<<< while(): minLen=" + minLen + ", minKwdLen=" + minKwdLen); // NOI18N
235
if (minLen > minKwdLen) {
236
appendString(indent(indent) + "if (len <= " + (minLen - 1) + ")\n"); // NOI18N
237
appendString(indent(indent + 1) + "return -1;\n"); // NOI18N
240
// Compute how many chars from current offset on are the same
241
// in all keywords in the current group
243
boolean stop = false;
244
for (int i = offset; i < minLen; i++) {
245
char c = kwds[indFrom].charAt(i);
246
for (int j = indFrom + 1; j < indTo; j++) {
247
if (kwds[j].charAt(i) != c) {
258
// System.out.println(">>>DEBUG<<< minLen=" + minLen + ", maxLen=" + maxLen + ", same=" + same); // NOI18N
260
// Add check for all the same chars
262
appendString(indent(indent) + "if ("); // NOI18N
263
for (int i = 0; i < same; i++) {
265
appendString(indent(indent + 1) + "|| "); // NOI18N
267
appendString(getCurrentChar() + " != '" + kwds[indFrom].charAt(offset + i) + "'"); // NOI18N
269
appendString("\n"); // NOI18N
272
appendString(")\n" + indent(indent + 2) + "return -1;\n"); // NOI18N
276
// Increase the offset to the first 'non-same' char
279
// If there's a keyword with the length equal to the current offset
280
// it will be first in the (sorted) group and it will be matched now
281
if (offset == kwds[indFrom].length()) {
282
appendString(indent(indent) + "if (len == " + offset + ")\n"); // NOI18N
283
appendString(indent(indent + 1) + "return " // NOI18N
284
+ getKwdConstant(kwds[indFrom]) + ";\n"); // NOI18N
285
indFrom++; // increase starting index as first keyword already matched
286
if (offset >= minLen) {
291
minKwdLen = minLen; // minLen already tested, so assign new minimum
293
} while (same > 0 && indFrom < indTo);
295
// If there are other chars at the end of any keyword,
296
// add the switch statement
297
if (offset < maxLen) {
298
appendString(indent(indent) + "switch (" + getCurrentChar() + ") {\n"); // NOI18N
303
// Add the case statement
304
char actChar = kwds[i].charAt(offset);
305
appendString(indent(indent + 1) + "case '" + actChar + "':\n"); // NOI18N
307
// Check whether the subgroup will have more than one keyword
308
int subGroupEndInd = i + 1;
309
while(subGroupEndInd < indTo
310
&& kwds[subGroupEndInd].length() > offset
311
&& kwds[subGroupEndInd].charAt(offset) == actChar
316
if(subGroupEndInd > i + 1) { // more than one keyword in subgroup
317
scan(i, subGroupEndInd, offset + 1, indent + 2, minLen);
318
} else { // just one keyword in the subgroup
319
appendCheckedReturn(kwds[i], offset + 1, indent + 2);
322
// advance current index to the end of current subgroup
326
appendString(indent(indent + 1) + "default:\n"); // NOI18N
327
appendString(indent(indent + 2) + "return -1;\n"); // NOI18N
328
appendString(indent(indent) + "}\n"); // NOI18N
329
} else { // no add-on chars, keyword not found in this case
330
appendString(indent(indent) + "return -1;\n"); // NOI18N
336
public static void main(String args[]) {
337
KeywordMatchGenerator km = new KeywordMatchGenerator();
341
for (argShift = 0; argShift < args.length; argShift++) {
343
if (args[argShift].charAt(0) != '-') {
344
break; // no more options
346
for (j = 0; j < OPTION_LIST.length; j++) {
347
if (args[argShift].equals(OPTION_LIST[j])) {
348
km.addOption(OPTION_LIST[j]);
352
if (j == OPTION_LIST.length) {
353
System.err.println("'" + args[argShift] + "'" + UNKNOWN_OPTION); // NOI18N
354
System.err.println(USAGE);
359
// check count of mandatory args
360
if (args.length - argShift < 1) {
361
System.err.println(USAGE);
368
File f = new File(args[argShift]);
370
System.err.println("Non-existent file '" + args[argShift] + "'"); // NOI18N
373
char arr[] = new char[(int)f.length()];
374
Reader isr = new FileReader(f);
377
while (n < f.length()) {
378
int count = isr.read(arr, n, (int)f.length() - n);
384
kwds = new String(arr);
385
} catch(IOException e) {
387
System.err.println("Cannot read from keyword file '" + args[argShift] + "'"); // NOI18N
391
// Check for optional method name
392
String methodName = null;
393
if (args.length - argShift >= 2) {
394
methodName = args[argShift + 1];
398
km.parseKeywords(kwds);
399
km.initScan(methodName);