/* * 08/26/2004 * * TokenMap.java - Similar to a Map in Java, only designed specifically for * org.fife.ui.rsyntaxtextarea.Tokens. * Copyright (C) 2004 Robert Futrell * robert_futrell at users.sourceforge.net * http://fifesoft.com/rsyntaxtextarea * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ package org.fife.ui.rsyntaxtextarea; import javax.swing.text.Segment; /** * A hash table for reserved words, etc. defined by a {@link TokenMaker}. This class is designed for the quick lookup of * tokens, as it can compare <code>Segment</code>s without the need to allocate a new string. * <p> * * The <code>org.fife.ui.rsyntaxtextarea</code> package uses this class to help identify reserved words in programming * languages. An instance of {@link TokenMaker} will create and initialize an instance of this class containing all * reserved words, data types, and all other words that need to be syntax-highlighted for that particular language. When * the token maker parses a line and identifies an individual token, it is looked up in the <code>TokenMap</code> to see * if it should be syntax-highlighted. * * @author Robert Futrell * @version 0.6 */ public class TokenMap { private int size; private TokenMapToken[] tokenMap; private boolean ignoreCase; private static final int DEFAULT_TOKEN_MAP_SIZE = 52; /** * Constructs a new token map that is case-sensitive. */ public TokenMap() { this(DEFAULT_TOKEN_MAP_SIZE); } /** * Constructs a new token map that is case-sensitive. * * @param size * The size of the token map. */ public TokenMap(int size) { this(size, false); } /** * Constructs a new token map. * * @param ignoreCase * Whether or not this token map should ignore case when comparing tokens. */ public TokenMap(boolean ignoreCase) { this(DEFAULT_TOKEN_MAP_SIZE, ignoreCase); } /** * Constructs a new token map. * * @param size * The size of the token map. * @param ignoreCase * Whether or not this token map should ignore case when comparing tokens. */ public TokenMap(int size, boolean ignoreCase) { this.size = size; tokenMap = new TokenMapToken[size]; this.ignoreCase = ignoreCase; } /** * Adds a token to a specified bucket in the token map. * * @param bucket * The bucket in which to add the token. * @param token * The token to add. */ private void addTokenToBucket(int bucket, TokenMapToken token) { TokenMapToken old = tokenMap[bucket]; token.nextToken = old; tokenMap[bucket] = token; } /** * Returns the token type associated with the given text, if the given text is in this token map. If it isn't, * <code>-1</code> is returned. * * @param text * The segment from which to get the text to compare. * @param start * The starting index in the segment of the text. * @param end * The ending index in the segment of the text. * @return The token type associated with the given text, or <code>-1</code> if this token was not specified in this * map. */ public int get(Segment text, int start, int end) { return get(text.array, start, end); } /** * Returns the token type associated with the given text, if the given text is in this token map. If it isn't, * <code>-1</code> is returned. * * @param array1 * An array of characters containing the text. * @param start * The starting index in the array of the text. * @param end * The ending index in the array of the text. * @return The token type associated with the given text, or <code>-1</code> if this token was not specified in this * map. */ public int get(char[] array1, int start, int end) { int length1 = end - start + 1; int hash = getHashCode(array1, start, length1); TokenMapToken token = tokenMap[hash]; char[] array2; int offset2; int offset1; int length; /* * We check whether or not to ignore case before doing any looping to minimize the number of extraneous * comparisons we do. This makes for slightly redundant code, but it'll be a little more efficient. */ // If matches are case-sensitive (C, C++, Java, etc.)... if (ignoreCase == false) { mainLoop: while (token != null) { if (token.length == length1) { array2 = token.text; offset2 = token.offset; offset1 = start; length = length1; while (length-- > 0) { if (array1[offset1++] != array2[offset2++]) { token = token.nextToken; continue mainLoop; } } return token.tokenType; } token = token.nextToken; } } // If matches are NOT case-sensitive (HTML)... // Note that all tokens saved in this map were converted to // lower-case already. else { mainLoop2: while (token != null) { if (token.length == length1) { array2 = token.text; offset2 = token.offset; offset1 = start; length = length1; while (length-- > 0) { if (RSyntaxUtilities.toLowerCase( array1[offset1++]) != array2[offset2++]) { token = token.nextToken; continue mainLoop2; } } return token.tokenType; } token = token.nextToken; } } // Didn't match any of the tokens in the bucket. return -1; } /** * Returns the hash code for a given string. * * @param text * The text to hash. * @param offset * The offset into the text at which to start hashing. * @param length * The last character in the text to hash. * @return The hash code. */ private final int getHashCode(char[] text, int offset, int length) { return (RSyntaxUtilities.toLowerCase(text[offset]) + RSyntaxUtilities.toLowerCase(text[offset + length - 1])) % size; } /** * Returns whether this token map ignores case when checking for tokens. This property is set in the constructor and * cannot be changed, as this is an intrinsic property of a particular programming language. * * @return Whether or not this token maker is ignoring case. */ protected boolean isIgnoringCase() { return ignoreCase; } /** * Adds a string to this token map. * * @param string * The string to add. * @param tokenType * The type of token the string is. */ public void put(final String string, final int tokenType) { if (isIgnoringCase()) put(string.toLowerCase().toCharArray(), tokenType); else put(string.toCharArray(), tokenType); } /** * Adds a string to this token map. The char array passed-in will be used as the actual data for the token, so it * may well be modified (such as lower-casing it if <code>ignoreCase</code> is <code>true</code>). This shouldn't be * an issue though as this method is only called from the public <code>put</code> method, which allocates a new char * array. * * @param string * The string to add. * @param tokenType * The type of token the string is. */ private void put(char[] string, int tokenType) { int hashCode = getHashCode(string, 0, string.length); addTokenToBucket(hashCode, new TokenMapToken(string, tokenType)); } /** * The "token" used by a token map. Note that this isn't the same thing as the {@link Token} class, but it's * basically a 1-1 correspondence for reserved words, etc. */ private static class TokenMapToken { char[] text; int offset; int length; int tokenType; TokenMapToken nextToken; TokenMapToken(char[] text, int tokenType) { this.text = text; this.offset = 0; this.length = text.length; this.tokenType = tokenType; } public String toString() { return "[TokenMapToken: " + new String(text, offset, length) + "]"; } } }