5bf8747e4768f4b137fa2ad1d0f511593706d19a
[phpeclipse.git] / net.sourceforge.phpeclipse.ui / src / net / sourceforge / phpdt / internal / ui / text / CombinedWordRule.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.text;
12
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17
18 //incastrix
19 //import org.eclipse.jface.text.Assert;
20 import org.eclipse.core.runtime.Assert;
21 import org.eclipse.jface.text.rules.ICharacterScanner;
22 import org.eclipse.jface.text.rules.IRule;
23 import org.eclipse.jface.text.rules.IToken;
24 import org.eclipse.jface.text.rules.IWordDetector;
25 import org.eclipse.jface.text.rules.Token;
26
27 /**
28  * An implementation of <code>IRule</code> capable of detecting words.
29  * <p>
30  * Word rules also allow for the association of tokens with specific words. That
31  * is, not only can the rule be used to provide tokens for exact matches, but
32  * also for the generalized notion of a word in the context in which it is used.
33  * A word rules uses a word detector to determine what a word is.
34  * </p>
35  * <p>
36  * This word rule allows a word detector to be shared among different word
37  * matchers. Its up to the word matchers to decide if a word matches and, in
38  * this a case, which token is associated with that word.
39  * </p>
40  * 
41  * @see IWordDetector
42  * @since 3.0
43  */
44 public class CombinedWordRule implements IRule {
45
46         /**
47          * Word matcher, that associates matched words with tokens.
48          */
49         public static class WordMatcher {
50
51                 /** The table of predefined words and token for this matcher */
52                 private Map fWords = new HashMap();
53
54                 /**
55                  * Adds a word and the token to be returned if it is detected.
56                  * 
57                  * @param word
58                  *            the word this rule will search for, may not be
59                  *            <code>null</code>
60                  * @param token
61                  *            the token to be returned if the word has been found, may
62                  *            not be <code>null</code>
63                  */
64                 public void addWord(String word, IToken token) {
65                         Assert.isNotNull(word);
66                         Assert.isNotNull(token);
67
68                         fWords.put(new CharacterBuffer(word), token);
69                 }
70
71                 /**
72                  * Returns the token associated to the given word and the scanner state.
73                  * 
74                  * @param scanner
75                  *            the scanner
76                  * @param word
77                  *            the word
78                  * @return the token or <code>null</code> if none is associated by
79                  *         this matcher
80                  */
81                 public IToken evaluate(ICharacterScanner scanner, CharacterBuffer word) {
82                         IToken token = (IToken) fWords.get(word);
83                         if (token != null)
84                                 return token;
85                         return Token.UNDEFINED;
86                 }
87
88                 /**
89                  * Removes all words.
90                  */
91                 public void clearWords() {
92                         fWords.clear();
93                 }
94         }
95
96         /**
97          * Character buffer, mutable <b>or</b> suitable for use as key in hash
98          * maps.
99          */
100         public static class CharacterBuffer {
101
102                 /** Buffer content */
103                 private char[] fContent;
104
105                 /** Buffer content size */
106                 private int fLength = 0;
107
108                 /** Is hash code cached? */
109                 private boolean fIsHashCached = false;
110
111                 /** The hash code */
112                 private int fHashCode;
113
114                 /**
115                  * Initialize with the given capacity.
116                  * 
117                  * @param capacity
118                  *            the initial capacity
119                  */
120                 public CharacterBuffer(int capacity) {
121                         fContent = new char[capacity];
122                 }
123
124                 /**
125                  * Initialize with the given content.
126                  * 
127                  * @param string
128                  *            the initial content
129                  */
130                 public CharacterBuffer(String content) {
131                         fContent = content.toCharArray();
132                         fLength = content.length();
133                 }
134
135                 /**
136                  * Empties this buffer.
137                  */
138                 public void clear() {
139                         fIsHashCached = false;
140                         fLength = 0;
141                 }
142
143                 /**
144                  * Appends the given character to the buffer.
145                  * 
146                  * @param c
147                  *            the character
148                  */
149                 public void append(char c) {
150                         fIsHashCached = false;
151                         if (fLength == fContent.length) {
152                                 char[] old = fContent;
153                                 fContent = new char[old.length << 1];
154                                 System.arraycopy(old, 0, fContent, 0, old.length);
155                         }
156                         fContent[fLength++] = c;
157                 }
158
159                 /**
160                  * Returns the length of the content.
161                  * 
162                  * @return the length
163                  */
164                 public int length() {
165                         return fLength;
166                 }
167
168                 /**
169                  * Returns the content as string.
170                  * 
171                  * @return the content
172                  */
173                 public String toString() {
174                         return new String(fContent, 0, fLength);
175                 }
176
177                 /**
178                  * Returns the character at the given position.
179                  * 
180                  * @param i
181                  *            the position
182                  * @return the character at position <code>i</code>
183                  */
184                 public char charAt(int i) {
185                         return fContent[i];
186                 }
187
188                 /*
189                  * @see java.lang.Object#hashCode()
190                  */
191                 public int hashCode() {
192                         if (fIsHashCached)
193                                 return fHashCode;
194
195                         int hash = 0;
196                         for (int i = 0, n = fLength; i < n; i++)
197                                 hash = 29 * hash + fContent[i];
198                         fHashCode = hash;
199                         fIsHashCached = true;
200                         return hash;
201                 }
202
203                 /*
204                  * @see java.lang.Object#equals(java.lang.Object)
205                  */
206                 public boolean equals(Object obj) {
207                         if (obj == this)
208                                 return true;
209                         if (!(obj instanceof CharacterBuffer))
210                                 return false;
211                         CharacterBuffer buffer = (CharacterBuffer) obj;
212                         int length = buffer.length();
213                         if (length != fLength)
214                                 return false;
215                         for (int i = 0; i < length; i++)
216                                 if (buffer.charAt(i) != fContent[i])
217                                         return false;
218                         return true;
219                 }
220
221                 /**
222                  * Is the content equal to the given string?
223                  * 
224                  * @param string
225                  *            the string
226                  * @return <code>true</code> iff the content is the same character
227                  *         sequence as in the string
228                  */
229                 public boolean equals(String string) {
230                         int length = string.length();
231                         if (length != fLength)
232                                 return false;
233                         for (int i = 0; i < length; i++)
234                                 if (string.charAt(i) != fContent[i])
235                                         return false;
236                         return true;
237                 }
238         }
239
240         /** Internal setting for the uninitialized column constraint */
241         private static final int UNDEFINED = -1;
242
243         /** The word detector used by this rule */
244         private IWordDetector fDetector;
245
246         /**
247          * The default token to be returned on success and if nothing else has been
248          * specified.
249          */
250         private IToken fDefaultToken;
251
252         /** The column constraint */
253         private int fColumn = UNDEFINED;
254
255         /** Buffer used for pattern detection */
256         private CharacterBuffer fBuffer = new CharacterBuffer(16);
257
258         /** List of word matchers */
259         private List fMatchers = new ArrayList();
260
261         /**
262          * Creates a rule which, with the help of an word detector, will return the
263          * token associated with the detected word. If no token has been associated,
264          * the scanner will be rolled back and an undefined token will be returned
265          * in order to allow any subsequent rules to analyze the characters.
266          * 
267          * @param detector
268          *            the word detector to be used by this rule, may not be
269          *            <code>null</code>
270          * 
271          * @see #addWord(String, IToken)
272          */
273         public CombinedWordRule(IWordDetector detector) {
274                 this(detector, null, Token.UNDEFINED);
275         }
276
277         /**
278          * Creates a rule which, with the help of an word detector, will return the
279          * token associated with the detected word. If no token has been associated,
280          * the specified default token will be returned.
281          * 
282          * @param detector
283          *            the word detector to be used by this rule, may not be
284          *            <code>null</code>
285          * @param defaultToken
286          *            the default token to be returned on success if nothing else is
287          *            specified, may not be <code>null</code>
288          * 
289          * @see #addWord(String, IToken)
290          */
291         public CombinedWordRule(IWordDetector detector, IToken defaultToken) {
292                 this(detector, null, defaultToken);
293         }
294
295         /**
296          * Creates a rule which, with the help of an word detector, will return the
297          * token associated with the detected word. If no token has been associated,
298          * the scanner will be rolled back and an undefined token will be returned
299          * in order to allow any subsequent rules to analyze the characters.
300          * 
301          * @param detector
302          *            the word detector to be used by this rule, may not be
303          *            <code>null</code>
304          * @param matcher
305          *            the initial word matcher
306          * 
307          * @see #addWord(String, IToken)
308          */
309         public CombinedWordRule(IWordDetector detector, WordMatcher matcher) {
310                 this(detector, matcher, Token.UNDEFINED);
311         }
312
313         /**
314          * Creates a rule which, with the help of an word detector, will return the
315          * token associated with the detected word. If no token has been associated,
316          * the specified default token will be returned.
317          * 
318          * @param detector
319          *            the word detector to be used by this rule, may not be
320          *            <code>null</code>
321          * @param matcher
322          *            the initial word matcher
323          * @param defaultToken
324          *            the default token to be returned on success if nothing else is
325          *            specified, may not be <code>null</code>
326          * 
327          * @see #addWord(String, IToken)
328          */
329         public CombinedWordRule(IWordDetector detector, WordMatcher matcher,
330                         IToken defaultToken) {
331
332                 Assert.isNotNull(detector);
333                 Assert.isNotNull(defaultToken);
334
335                 fDetector = detector;
336                 fDefaultToken = defaultToken;
337                 if (matcher != null)
338                         addWordMatcher(matcher);
339         }
340
341         /**
342          * Adds the given matcher.
343          * 
344          * @param matcher
345          *            the matcher
346          */
347         public void addWordMatcher(WordMatcher matcher) {
348                 fMatchers.add(matcher);
349         }
350
351         /**
352          * Sets a column constraint for this rule. If set, the rule's token will
353          * only be returned if the pattern is detected starting at the specified
354          * column. If the column is smaller then 0, the column constraint is
355          * considered removed.
356          * 
357          * @param column
358          *            the column in which the pattern starts
359          */
360         public void setColumnConstraint(int column) {
361                 if (column < 0)
362                         column = UNDEFINED;
363                 fColumn = column;
364         }
365
366         /*
367          * @see IRule#evaluate(ICharacterScanner)
368          */
369         public IToken evaluate(ICharacterScanner scanner) {
370                 int c = scanner.read();
371                 if (fDetector.isWordStart((char) c)) {
372                         if (fColumn == UNDEFINED || (fColumn == scanner.getColumn() - 1)) {
373
374                                 fBuffer.clear();
375                                 do {
376                                         fBuffer.append((char) c);
377                                         c = scanner.read();
378                                 } while (c != ICharacterScanner.EOF
379                                                 && fDetector.isWordPart((char) c));
380                                 scanner.unread();
381
382                                 for (int i = 0, n = fMatchers.size(); i < n; i++) {
383                                         IToken token = ((WordMatcher) fMatchers.get(i)).evaluate(
384                                                         scanner, fBuffer);
385                                         if (!token.isUndefined())
386                                                 return token;
387                                 }
388
389                                 if (fDefaultToken.isUndefined())
390                                         unreadBuffer(scanner);
391
392                                 return fDefaultToken;
393                         }
394                 }
395
396                 scanner.unread();
397                 return Token.UNDEFINED;
398         }
399
400         /**
401          * Returns the characters in the buffer to the scanner.
402          * 
403          * @param scanner
404          *            the scanner to be used
405          */
406         private void unreadBuffer(ICharacterScanner scanner) {
407                 for (int i = fBuffer.length() - 1; i >= 0; i--)
408                         scanner.unread();
409         }
410 }