~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to editor/libsrc/org/netbeans/editor/ext/KeywordMatchGenerator.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
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]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
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.
 
29
 *
 
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.
 
40
 */
 
41
 
 
42
package org.netbeans.editor.ext;
 
43
 
 
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;
 
50
import java.io.File;
 
51
import java.io.Reader;
 
52
import java.io.FileReader;
 
53
 
 
54
/**
 
55
* Generator of code used for matching the keywords or more generally some
 
56
* group of words.
 
57
*
 
58
* @author Miloslav Metelka
 
59
* @version 1.00
 
60
*/
 
61
 
 
62
public class KeywordMatchGenerator {
 
63
 
 
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
 
74
 
 
75
    private static final String UNKNOWN_OPTION = " is unknown option.\n"; // NOI18N
 
76
 
 
77
    public static final String IGNORE_CASE = "-i"; // NOI18N
 
78
 
 
79
    public static final String USE_STRING = "-s"; // NOI18N
 
80
 
 
81
    private static final String DEFAULT_METHOD_NAME = "match"; // NOI18N
 
82
 
 
83
    private static final String[] OPTION_LIST = { IGNORE_CASE, USE_STRING };
 
84
 
 
85
    /** The list of keywords */
 
86
    private String kwds[];
 
87
 
 
88
    /** Maximum length of keyword */
 
89
    private int maxKwdLen;
 
90
 
 
91
    /** Options */
 
92
    private HashMap options = new HashMap();
 
93
 
 
94
    private HashMap kwdConstants = new HashMap();
 
95
 
 
96
    /** Provide indentation (default 2 spaces) */
 
97
    private String indent(int cnt) {
 
98
        StringBuffer sb = new StringBuffer();
 
99
 
 
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 " + kwdConstants.get(kwds[i]) // NOI18N
 
116
                         + " = " + i + ";\n"); // NOI18N
 
117
        }
 
118
        appendString("\n"); // NOI18N
 
119
 
 
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
 
125
        } else {
 
126
            appendString("(char[] buffer, int offset, int len) {\n"); // NOI18N
 
127
        }
 
128
        appendString(indent(2) + "if (len > " + maxKwdLen + ")\n"); // NOI18N
 
129
        appendString(indent(3) + "return -1;\n"); // NOI18N
 
130
    }
 
131
 
 
132
    public void scan() {
 
133
        scan(0, kwds.length, 0, 2, 0);
 
134
    }
 
135
 
 
136
    protected void finishScan() {
 
137
        appendString(indent(1) + "}\n\n"); // NOI18N
 
138
    }
 
139
 
 
140
    public void addOption(String option) {
 
141
        options.put(option, option);
 
142
    }
 
143
 
 
144
    protected String getKwdConstantPrefix() {
 
145
        return ""; // "KWD_"; // NOI18N
 
146
    }
 
147
 
 
148
    protected String getKwdConstant(String kwd) {
 
149
        return (String)kwdConstants.get(kwd);
 
150
    }
 
151
 
 
152
    protected boolean upperCaseKeyConstants() {
 
153
        return true;
 
154
    }
 
155
 
 
156
    /** Parse the keywords from a string */
 
157
    private void parseKeywords(String s) {
 
158
        ArrayList keyList = new ArrayList();
 
159
        StringTokenizer strTok = new StringTokenizer(s);
 
160
 
 
161
        try {
 
162
            while(true) {
 
163
                String key = strTok.nextToken();
 
164
                int keyLen = key.length();
 
165
                maxKwdLen = Math.max(maxKwdLen, keyLen);
 
166
                keyList.add(key);
 
167
                kwdConstants.put(key, getKwdConstantPrefix()
 
168
                                 + (upperCaseKeyConstants() ? key.toUpperCase() : key));
 
169
            }
 
170
        } catch(NoSuchElementException e) {
 
171
            // no more elements
 
172
        }
 
173
 
 
174
        kwds = new String[keyList.size()];
 
175
        keyList.toArray(kwds);
 
176
        Arrays.sort(kwds);
 
177
    }
 
178
 
 
179
    protected String getCurrentChar() {
 
180
        boolean useString = (options.get(USE_STRING) != null);
 
181
        boolean ignoreCase = (options.get(IGNORE_CASE) != null);
 
182
 
 
183
        if(useString) {
 
184
            return ignoreCase ? "Character.toLowerCase(buffer.charAt(offset++))" // NOI18N
 
185
                   : "buffer.charAt(offset++)"; // NOI18N
 
186
        } else {
 
187
            return ignoreCase ? "Character.toLowerCase(buffer[offset++])" // NOI18N
 
188
                   : "buffer[offset++]"; // NOI18N
 
189
        }
 
190
    }
 
191
 
 
192
    private void appendCheckedReturn(String kwd, int offset, int indent) {
 
193
        appendString(indent(indent) + "return (len == " // NOI18N
 
194
                     + kwd.length());
 
195
 
 
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
 
200
        }
 
201
 
 
202
        appendString(")\n" + indent(indent + 2) + "? " + getKwdConstant(kwd) + " : -1;\n"); // NOI18N
 
203
    }
 
204
 
 
205
    protected void appendString(String s) {
 
206
        System.out.print(s);
 
207
    }
 
208
 
 
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.
 
217
    */
 
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
 
220
        int maxLen = 0;
 
221
        for (int i = indFrom; i < indTo; i++) {
 
222
            maxLen = Math.max(maxLen, kwds[i].length());
 
223
        }
 
224
 
 
225
        int same;
 
226
        int minLen;
 
227
        do {
 
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());
 
232
            }
 
233
 
 
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
 
238
            }
 
239
 
 
240
            // Compute how many chars from current offset on are the same
 
241
            // in all keywords in the current group
 
242
            same = 0;
 
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) {
 
248
                        stop = true;
 
249
                        break;
 
250
                    }
 
251
                }
 
252
                if (stop) {
 
253
                    break;
 
254
                }
 
255
                same++;
 
256
            }
 
257
 
 
258
            //      System.out.println(">>>DEBUG<<< minLen=" + minLen + ", maxLen=" + maxLen + ", same=" + same); // NOI18N
 
259
 
 
260
            // Add check for all the same chars
 
261
            if (same > 0) {
 
262
                appendString(indent(indent) + "if ("); // NOI18N
 
263
                for (int i = 0; i < same; i++) {
 
264
                    if (i > 0) {
 
265
                        appendString(indent(indent + 1) + "|| "); // NOI18N
 
266
                    }
 
267
                    appendString(getCurrentChar() + " != '" + kwds[indFrom].charAt(offset + i) + "'"); // NOI18N
 
268
                    if (i < same - 1) {
 
269
                        appendString("\n"); // NOI18N
 
270
                    }
 
271
                }
 
272
                appendString(")\n" + indent(indent + 2) + "return -1;\n"); // NOI18N
 
273
 
 
274
            }
 
275
 
 
276
            // Increase the offset to the first 'non-same' char
 
277
            offset += same;
 
278
 
 
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) {
 
287
                    minLen = offset + 1;
 
288
                }
 
289
            }
 
290
 
 
291
            minKwdLen = minLen; // minLen already tested, so assign new minimum
 
292
 
 
293
        } while (same > 0 && indFrom < indTo);
 
294
 
 
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
 
299
 
 
300
            // Compute subgroups
 
301
            int i = indFrom;
 
302
            while(i < indTo) {
 
303
                // Add the case statement
 
304
                char actChar = kwds[i].charAt(offset);
 
305
                appendString(indent(indent + 1) + "case '" + actChar + "':\n"); // NOI18N
 
306
 
 
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
 
312
                     ) {
 
313
                    subGroupEndInd++;
 
314
                }
 
315
 
 
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);
 
320
                }
 
321
 
 
322
                // advance current index to the end of current subgroup
 
323
                i = subGroupEndInd;
 
324
            }
 
325
 
 
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
 
331
        }
 
332
 
 
333
    }
 
334
 
 
335
    /** Main method */
 
336
    public static void main(String args[]) {
 
337
        KeywordMatchGenerator km = new KeywordMatchGenerator();
 
338
 
 
339
        // parse options
 
340
        int argShift;
 
341
        for (argShift = 0; argShift < args.length; argShift++) {
 
342
            int j;
 
343
            if (args[argShift].charAt(0) != '-') {
 
344
                break; // no more options
 
345
            }
 
346
            for (j = 0; j < OPTION_LIST.length; j++) {
 
347
                if (args[argShift].equals(OPTION_LIST[j])) {
 
348
                    km.addOption(OPTION_LIST[j]);
 
349
                    break;
 
350
                }
 
351
            }
 
352
            if (j == OPTION_LIST.length) {
 
353
                System.err.println("'" + args[argShift] + "'" + UNKNOWN_OPTION); // NOI18N
 
354
                System.err.println(USAGE);
 
355
                return;
 
356
            }
 
357
        }
 
358
 
 
359
        // check count of mandatory args
 
360
        if (args.length - argShift < 1) {
 
361
            System.err.println(USAGE);
 
362
            return;
 
363
        }
 
364
 
 
365
        // read keyword file
 
366
        String kwds = null;
 
367
        try {
 
368
            File f = new File(args[argShift]);
 
369
            if (!f.exists()) {
 
370
                System.err.println("Non-existent file '" + args[argShift] + "'"); // NOI18N
 
371
                return;
 
372
            }
 
373
            char arr[] = new char[(int)f.length()];
 
374
            Reader isr = new FileReader(f);
 
375
 
 
376
            int n = 0;
 
377
            while (n < f.length()) {
 
378
                int count = isr.read(arr, n, (int)f.length() - n);
 
379
                if (count < 0)
 
380
                    break;
 
381
                n += count;
 
382
            }
 
383
 
 
384
            kwds = new String(arr);
 
385
        } catch(IOException e) {
 
386
            // IO exception
 
387
            System.err.println("Cannot read from keyword file '" + args[argShift] + "'"); // NOI18N
 
388
            return;
 
389
        }
 
390
 
 
391
        // Check for optional method name
 
392
        String methodName = null;
 
393
        if (args.length - argShift >= 2) {
 
394
            methodName = args[argShift + 1];
 
395
        }
 
396
 
 
397
        // generate
 
398
        km.parseKeywords(kwds);
 
399
        km.initScan(methodName);
 
400
        km.scan();
 
401
        km.finishScan();
 
402
 
 
403
    }
 
404
 
 
405
 
 
406
}