| /******************************************************************************* |
| * Copyright (c) 2000, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.ui.text; |
| |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.jface.text.Assert; |
| import org.eclipse.jface.text.rules.ICharacterScanner; |
| import org.eclipse.jface.text.rules.IRule; |
| import org.eclipse.jface.text.rules.IToken; |
| import org.eclipse.jface.text.rules.IWordDetector; |
| import org.eclipse.jface.text.rules.Token; |
| |
| |
| /** |
| * An implementation of <code>IRule</code> capable of detecting words. |
| * <p> |
| * Word rules also allow for the association of tokens with specific words. |
| * That is, not only can the rule be used to provide tokens for exact matches, |
| * but also for the generalized notion of a word in the context in which it is used. |
| * A word rules uses a word detector to determine what a word is.</p> |
| * <p> |
| * This word rule allows a word detector to be shared among different word matchers. |
| * Its up to the word matchers to decide if a word matches and, in this a case, which |
| * token is associated with that word. |
| * </p> |
| * |
| * @see IWordDetector |
| * @since 3.0 |
| */ |
| public class CombinedWordRule implements IRule { |
| |
| /** |
| * Word matcher, that associates matched words with tokens. |
| */ |
| public static class WordMatcher { |
| |
| /** The table of predefined words and token for this matcher */ |
| private Map fWords= new HashMap(); |
| |
| /** |
| * Adds a word and the token to be returned if it is detected. |
| * |
| * @param word the word this rule will search for, may not be <code>null</code> |
| * @param token the token to be returned if the word has been found, may not be <code>null</code> |
| */ |
| public void addWord(String word, IToken token) { |
| Assert.isNotNull(word); |
| Assert.isNotNull(token); |
| |
| fWords.put(new CharacterBuffer(word), token); |
| } |
| |
| /** |
| * Returns the token associated to the given word and the scanner state. |
| * |
| * @param scanner the scanner |
| * @param word the word |
| * @return the token or <code>null</code> if none is associated by this matcher |
| */ |
| public IToken evaluate(ICharacterScanner scanner, CharacterBuffer word) { |
| IToken token= (IToken) fWords.get(word); |
| if (token != null) |
| return token; |
| return Token.UNDEFINED; |
| } |
| |
| /** |
| * Removes all words. |
| */ |
| public void clearWords() { |
| fWords.clear(); |
| } |
| } |
| |
| /** |
| * Character buffer, mutable <b>or</b> suitable for use as key in hash maps. |
| */ |
| public static class CharacterBuffer { |
| |
| /** Buffer content */ |
| private char[] fContent; |
| /** Buffer content size */ |
| private int fLength= 0; |
| |
| /** Is hash code cached? */ |
| private boolean fIsHashCached= false; |
| /** The hash code */ |
| private int fHashCode; |
| |
| /** |
| * Initialize with the given capacity. |
| * |
| * @param capacity the initial capacity |
| */ |
| public CharacterBuffer(int capacity) { |
| fContent= new char[capacity]; |
| } |
| |
| /** |
| * Initialize with the given content. |
| * |
| * @param string the initial content |
| */ |
| public CharacterBuffer(String content) { |
| fContent= content.toCharArray(); |
| fLength= content.length(); |
| } |
| |
| /** |
| * Empties this buffer. |
| */ |
| public void clear() { |
| fIsHashCached= false; |
| fLength= 0; |
| } |
| |
| /** |
| * Appends the given character to the buffer. |
| * |
| * @param c the character |
| */ |
| public void append(char c) { |
| fIsHashCached= false; |
| if (fLength == fContent.length) { |
| char[] old= fContent; |
| fContent= new char[old.length << 1]; |
| System.arraycopy(old, 0, fContent, 0, old.length); |
| } |
| fContent[fLength++]= c; |
| } |
| |
| /** |
| * Returns the length of the content. |
| * |
| * @return the length |
| */ |
| public int length() { |
| return fLength; |
| } |
| |
| /** |
| * Returns the content as string. |
| * |
| * @return the content |
| */ |
| public String toString() { |
| return new String(fContent, 0, fLength); |
| } |
| |
| /** |
| * Returns the character at the given position. |
| * |
| * @param i the position |
| * @return the character at position <code>i</code> |
| */ |
| public char charAt(int i) { |
| return fContent[i]; |
| } |
| |
| /* |
| * @see java.lang.Object#hashCode() |
| */ |
| public int hashCode() { |
| if (fIsHashCached) |
| return fHashCode; |
| |
| int hash= 0; |
| for (int i= 0, n= fLength; i < n; i++) |
| hash= 29*hash + fContent[i]; |
| fHashCode= hash; |
| fIsHashCached= true; |
| return hash; |
| } |
| |
| |
| /* |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| public boolean equals(Object obj) { |
| if (obj == this) |
| return true; |
| if (!(obj instanceof CharacterBuffer)) |
| return false; |
| CharacterBuffer buffer= (CharacterBuffer) obj; |
| int length= buffer.length(); |
| if (length != fLength) |
| return false; |
| for (int i= 0; i < length; i++) |
| if (buffer.charAt(i) != fContent[i]) |
| return false; |
| return true; |
| } |
| |
| /** |
| * Is the content equal to the given string? |
| * |
| * @param string the string |
| * @return <code>true</code> iff the content is the same character sequence as in the string |
| */ |
| public boolean equals(String string) { |
| int length= string.length(); |
| if (length != fLength) |
| return false; |
| for (int i= 0; i < length; i++) |
| if (string.charAt(i) != fContent[i]) |
| return false; |
| return true; |
| } |
| } |
| |
| /** Internal setting for the uninitialized column constraint */ |
| private static final int UNDEFINED= -1; |
| |
| /** The word detector used by this rule */ |
| private IWordDetector fDetector; |
| /** The default token to be returned on success and if nothing else has been specified. */ |
| private IToken fDefaultToken; |
| /** The column constraint */ |
| private int fColumn= UNDEFINED; |
| /** Buffer used for pattern detection */ |
| private CharacterBuffer fBuffer= new CharacterBuffer(16); |
| |
| /** List of word matchers */ |
| private List fMatchers= new ArrayList(); |
| |
| /** |
| * Creates a rule which, with the help of an word detector, will return the token |
| * associated with the detected word. If no token has been associated, the scanner |
| * will be rolled back and an undefined token will be returned in order to allow |
| * any subsequent rules to analyze the characters. |
| * |
| * @param detector the word detector to be used by this rule, may not be <code>null</code> |
| * |
| * @see #addWord(String, IToken) |
| */ |
| public CombinedWordRule(IWordDetector detector) { |
| this(detector, null, Token.UNDEFINED); |
| } |
| |
| /** |
| * Creates a rule which, with the help of an word detector, will return the token |
| * associated with the detected word. If no token has been associated, the |
| * specified default token will be returned. |
| * |
| * @param detector the word detector to be used by this rule, may not be <code>null</code> |
| * @param defaultToken the default token to be returned on success |
| * if nothing else is specified, may not be <code>null</code> |
| * |
| * @see #addWord(String, IToken) |
| */ |
| public CombinedWordRule(IWordDetector detector, IToken defaultToken) { |
| this(detector, null, defaultToken); |
| } |
| |
| /** |
| * Creates a rule which, with the help of an word detector, will return the token |
| * associated with the detected word. If no token has been associated, the scanner |
| * will be rolled back and an undefined token will be returned in order to allow |
| * any subsequent rules to analyze the characters. |
| * |
| * @param detector the word detector to be used by this rule, may not be <code>null</code> |
| * @param matcher the initial word matcher |
| * |
| * @see #addWord(String, IToken) |
| */ |
| public CombinedWordRule(IWordDetector detector, WordMatcher matcher) { |
| this(detector, matcher, Token.UNDEFINED); |
| } |
| |
| /** |
| * Creates a rule which, with the help of an word detector, will return the token |
| * associated with the detected word. If no token has been associated, the |
| * specified default token will be returned. |
| * |
| * @param detector the word detector to be used by this rule, may not be <code>null</code> |
| * @param matcher the initial word matcher |
| * @param defaultToken the default token to be returned on success |
| * if nothing else is specified, may not be <code>null</code> |
| * |
| * @see #addWord(String, IToken) |
| */ |
| public CombinedWordRule(IWordDetector detector, WordMatcher matcher, IToken defaultToken) { |
| |
| Assert.isNotNull(detector); |
| Assert.isNotNull(defaultToken); |
| |
| fDetector= detector; |
| fDefaultToken= defaultToken; |
| if (matcher != null) |
| addWordMatcher(matcher); |
| } |
| |
| |
| /** |
| * Adds the given matcher. |
| * |
| * @param matcher the matcher |
| */ |
| public void addWordMatcher(WordMatcher matcher) { |
| fMatchers.add(matcher); |
| } |
| |
| /** |
| * Sets a column constraint for this rule. If set, the rule's token |
| * will only be returned if the pattern is detected starting at the |
| * specified column. If the column is smaller then 0, the column |
| * constraint is considered removed. |
| * |
| * @param column the column in which the pattern starts |
| */ |
| public void setColumnConstraint(int column) { |
| if (column < 0) |
| column= UNDEFINED; |
| fColumn= column; |
| } |
| |
| /* |
| * @see IRule#evaluate(ICharacterScanner) |
| */ |
| public IToken evaluate(ICharacterScanner scanner) { |
| int c= scanner.read(); |
| if (fDetector.isWordStart((char) c)) { |
| if (fColumn == UNDEFINED || (fColumn == scanner.getColumn() - 1)) { |
| |
| fBuffer.clear(); |
| do { |
| fBuffer.append((char) c); |
| c= scanner.read(); |
| } while (c != ICharacterScanner.EOF && fDetector.isWordPart((char) c)); |
| scanner.unread(); |
| |
| for (int i= 0, n= fMatchers.size(); i < n; i++) { |
| IToken token= ((WordMatcher) fMatchers.get(i)).evaluate(scanner, fBuffer); |
| if (!token.isUndefined()) |
| return token; |
| } |
| |
| if (fDefaultToken.isUndefined()) |
| unreadBuffer(scanner); |
| |
| return fDefaultToken; |
| } |
| } |
| |
| scanner.unread(); |
| return Token.UNDEFINED; |
| } |
| |
| /** |
| * Returns the characters in the buffer to the scanner. |
| * |
| * @param scanner the scanner to be used |
| */ |
| private void unreadBuffer(ICharacterScanner scanner) { |
| for (int i= fBuffer.length() - 1; i >= 0; i--) |
| scanner.unread(); |
| } |
| } |