001: /*
002: * KeywordMap.java - Fast keyword->id map
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1998, 2002 Slava Pestov
007: * Copyright (C) 1999 Mike Dillon
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: */
023: package org.gjt.sp.jedit.syntax;
024:
025: import javax.swing.text.Segment;
026: import java.util.Vector;
027:
028: /**
029: * A <code>KeywordMap</code> is similar to a hashtable in that it maps keys
030: * to values. However, the `keys' are Swing segments. This allows lookups of
031: * text substrings without the overhead of creating a new string object.
032: *
033: * @author Slava Pestov, Mike Dillon
034: * @version $Id: KeywordMap.java 5053 2004-05-29 01:55:26Z spestov $
035: */
036: public class KeywordMap {
037: //{{{ KeywordMap constructor
038: /**
039: * Creates a new <code>KeywordMap</code>.
040: * @param ignoreCase True if keys are case insensitive
041: */
042: public KeywordMap(boolean ignoreCase) {
043: this (ignoreCase, 52);
044: this .ignoreCase = ignoreCase;
045: noWordSep = new StringBuffer();
046: } //}}}
047:
048: //{{{ KeywordMap constructor
049: /**
050: * Creates a new <code>KeywordMap</code>.
051: * @param ignoreCase True if the keys are case insensitive
052: * @param mapLength The number of `buckets' to create.
053: * A value of 52 will give good performance for most maps.
054: */
055: public KeywordMap(boolean ignoreCase, int mapLength) {
056: this .mapLength = mapLength;
057: this .ignoreCase = ignoreCase;
058: map = new Keyword[mapLength];
059: } //}}}
060:
061: //{{{ lookup() method
062: /**
063: * Looks up a key.
064: * @param text The text segment
065: * @param offset The offset of the substring within the text segment
066: * @param length The length of the substring
067: */
068: public byte lookup(Segment text, int offset, int length) {
069: if (length == 0)
070: return Token.NULL;
071: Keyword k = map[getSegmentMapKey(text, offset, length)];
072: while (k != null) {
073: if (length != k.keyword.length) {
074: k = k.next;
075: continue;
076: }
077: if (SyntaxUtilities.regionMatches(ignoreCase, text, offset,
078: k.keyword))
079: return k.id;
080: k = k.next;
081: }
082: return Token.NULL;
083: } //}}}
084:
085: //{{{ add() method
086: /**
087: * Adds a key-value mapping.
088: * @param keyword The key
089: * @param id The value
090: */
091: public void add(String keyword, byte id) {
092: add(keyword.toCharArray(), id);
093: } //}}}
094:
095: //{{{ add() method
096: /**
097: * Adds a key-value mapping.
098: * @param keyword The key
099: * @param id The value
100: * @since jEdit 4.2pre3
101: */
102: public void add(char[] keyword, byte id) {
103: int key = getStringMapKey(keyword);
104:
105: // complete-word command needs a list of all non-alphanumeric
106: // characters used in a keyword map.
107: loop: for (int i = 0; i < keyword.length; i++) {
108: char ch = keyword[i];
109: if (!Character.isLetterOrDigit(ch)) {
110: for (int j = 0; j < noWordSep.length(); j++) {
111: if (noWordSep.charAt(j) == ch)
112: continue loop;
113: }
114:
115: noWordSep.append(ch);
116: }
117: }
118:
119: map[key] = new Keyword(keyword, id, map[key]);
120: } //}}}
121:
122: //{{{ getNonAlphaNumericChars() method
123: /**
124: * Returns all non-alphanumeric characters that appear in the
125: * keywords of this keyword map.
126: * @since jEdit 4.0pre3
127: */
128: public String getNonAlphaNumericChars() {
129: return noWordSep.toString();
130: } //}}}
131:
132: //{{{ getKeywords() method
133: /**
134: * Returns an array containing all keywords in this keyword map.
135: * @since jEdit 4.0pre3
136: */
137: public String[] getKeywords() {
138: Vector vector = new Vector(100);
139: for (int i = 0; i < map.length; i++) {
140: Keyword keyword = map[i];
141: while (keyword != null) {
142: vector.addElement(new String(keyword.keyword));
143: keyword = keyword.next;
144: }
145: }
146: String[] retVal = new String[vector.size()];
147: vector.copyInto(retVal);
148: return retVal;
149: } //}}}
150:
151: //{{{ getIgnoreCase() method
152: /**
153: * Returns true if the keyword map is set to be case insensitive,
154: * false otherwise.
155: */
156: public boolean getIgnoreCase() {
157: return ignoreCase;
158: } //}}}
159:
160: //{{{ setIgnoreCase() method
161: /**
162: * Sets if the keyword map should be case insensitive.
163: * @param ignoreCase True if the keyword map should be case
164: * insensitive, false otherwise
165: */
166: public void setIgnoreCase(boolean ignoreCase) {
167: this .ignoreCase = ignoreCase;
168: } //}}}
169:
170: //{{{ add() method
171: /**
172: * Adds the content of another keyword map to this one.
173: * @since jEdit 4.2pre3
174: */
175: public void add(KeywordMap map) {
176: for (int i = 0; i < map.map.length; i++) {
177: Keyword k = map.map[i];
178: while (k != null) {
179: add(k.keyword, k.id);
180: k = k.next;
181: }
182: }
183: } //}}}
184:
185: //{{{ Private members
186:
187: //{{{ Instance variables
188: private int mapLength;
189: private Keyword[] map;
190: private boolean ignoreCase;
191: private StringBuffer noWordSep;
192:
193: //}}}
194:
195: //{{{ getStringMapKey() method
196: private int getStringMapKey(char[] s) {
197: return (Character.toUpperCase(s[0]) + Character
198: .toUpperCase(s[s.length - 1]))
199: % mapLength;
200: } //}}}
201:
202: //{{{ getSegmentMapKey() method
203: protected int getSegmentMapKey(Segment s, int off, int len) {
204: return (Character.toUpperCase(s.array[off]) + Character
205: .toUpperCase(s.array[off + len - 1]))
206: % mapLength;
207: } //}}}
208:
209: //}}}
210:
211: //{{{ Keyword class
212: class Keyword {
213: public Keyword(char[] keyword, byte id, Keyword next) {
214: this .keyword = keyword;
215: this .id = id;
216: this .next = next;
217: }
218:
219: public char[] keyword;
220: public byte id;
221: public Keyword next;
222: } //}}}
223: }
|