KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > source > DefaultCharacterPairMatcher


1 /*******************************************************************************
2  * Copyright (c) 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * Christian Plesner Hansen (plesner@quenta.org) - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.jface.text.source;
12 import java.util.HashSet JavaDoc;
13 import java.util.Set JavaDoc;
14
15 import org.eclipse.core.runtime.Assert;
16
17 import org.eclipse.jface.text.BadLocationException;
18 import org.eclipse.jface.text.IDocument;
19 import org.eclipse.jface.text.IDocumentExtension3;
20 import org.eclipse.jface.text.IRegion;
21 import org.eclipse.jface.text.ITypedRegion;
22 import org.eclipse.jface.text.Region;
23 import org.eclipse.jface.text.TextUtilities;
24
25 /**
26  * A character pair matcher that matches a specified set of character
27  * pairs against each other. Only characters that occur in the same
28  * partitioning are matched.
29  *
30  * @since 3.3
31  */

32 public class DefaultCharacterPairMatcher implements ICharacterPairMatcher {
33
34     private int fAnchor= -1;
35     private final CharPairs fPairs;
36     private final String JavaDoc fPartitioning;
37
38     /**
39      * Creates a new character pair matcher that matches the specified
40      * characters within the specified partitioning. The specified
41      * list of characters must have the form
42      * <blockquote>{ <i>start</i>, <i>end</i>, <i>start</i>, <i>end</i>, ..., <i>start</i>, <i>end</i> }</blockquote>
43      * For instance:
44      * <pre>
45      * char[] chars = new char[] {'(', ')', '{', '}', '[', ']'};
46      * new SimpleCharacterPairMatcher(chars, ...);
47      * </pre>
48      *
49      * @param chars a list of characters
50      * @param partitioning the partitioning to match within
51      */

52     public DefaultCharacterPairMatcher(char[] chars, String JavaDoc partitioning) {
53         Assert.isLegal(chars.length % 2 == 0);
54         Assert.isNotNull(partitioning);
55         fPairs= new CharPairs(chars);
56         fPartitioning= partitioning;
57     }
58     
59     /**
60      * Creates a new character pair matcher that matches characters
61      * within the default partitioning. The specified list of
62      * characters must have the form
63      * <blockquote>{ <i>start</i>, <i>end</i>, <i>start</i>, <i>end</i>, ..., <i>start</i>, <i>end</i> }</blockquote>
64      * For instance:
65      * <pre>
66      * char[] chars = new char[] {'(', ')', '{', '}', '[', ']'};
67      * new SimpleCharacterPairMatcher(chars);
68      * </pre>
69      *
70      * @param chars a list of characters
71      */

72     public DefaultCharacterPairMatcher(char[] chars) {
73         this(chars, IDocumentExtension3.DEFAULT_PARTITIONING);
74     }
75     
76     /* @see ICharacterPairMatcher#match(IDocument, int) */
77     public IRegion match(IDocument doc, int offset) {
78         if (doc == null || offset < 0 || offset > doc.getLength()) return null;
79         try {
80             return performMatch(doc, offset);
81         } catch (BadLocationException ble) {
82             return null;
83         }
84     }
85         
86     /*
87      * Performs the actual work of matching for #match(IDocument, int).
88      */

89     private IRegion performMatch(IDocument doc, int offset) throws BadLocationException {
90         final char prevChar= doc.getChar(Math.max(offset - 1, 0));
91         if (!fPairs.contains(prevChar)) return null;
92         final boolean isForward= fPairs.isStartCharacter(prevChar);
93         fAnchor= isForward ? ICharacterPairMatcher.LEFT : ICharacterPairMatcher.RIGHT;
94         final int searchStartPosition= isForward ? offset : offset - 2;
95         final int adjustedOffset= isForward ? offset - 1 : offset;
96         final String JavaDoc partition= TextUtilities.getContentType(doc, fPartitioning, adjustedOffset, false);
97         final DocumentPartitionAccessor partDoc= new DocumentPartitionAccessor(doc, fPartitioning, partition);
98         int endOffset= findMatchingPeer(partDoc, prevChar, fPairs.getMatching(prevChar),
99                 isForward, isForward ? doc.getLength() : -1,
100                 searchStartPosition);
101         if (endOffset == -1) return null;
102         final int adjustedEndOffset= isForward ? endOffset + 1: endOffset;
103         if (adjustedEndOffset == adjustedOffset) return null;
104         return new Region(Math.min(adjustedOffset, adjustedEndOffset),
105                 Math.abs(adjustedEndOffset - adjustedOffset));
106     }
107
108     /**
109      * Searches <code>doc</code> for the specified end character, <code>end</code>.
110      *
111      * @param doc the document to search
112      * @param start the opening matching character
113      * @param end the end character to search for
114      * @param searchForward search forwards or backwards?
115      * @param boundary a boundary at which the search should stop
116      * @param startPos the start offset
117      * @return the index of the end character if it was found, otherwise -1
118      * @throws BadLocationException
119      */

120     private int findMatchingPeer(DocumentPartitionAccessor doc, char start, char end, boolean searchForward, int boundary, int startPos) throws BadLocationException {
121         int pos= startPos;
122         while (pos != boundary) {
123             final char c= doc.getChar(pos);
124             if (doc.isMatch(pos, end)) {
125                 return pos;
126             } else if (c == start && doc.inPartition(pos)) {
127                 pos= findMatchingPeer(doc, start, end, searchForward, boundary,
128                         doc.getNextPosition(pos, searchForward));
129                 if (pos == -1) return -1;
130             }
131             pos= doc.getNextPosition(pos, searchForward);
132         }
133         return -1;
134     }
135
136     /* @see ICharacterPairMatcher#getAnchor() */
137     public int getAnchor() {
138         return fAnchor;
139     }
140     
141     /* @see ICharacterPairMatcher#dispose() */
142     public void dispose() { }
143
144     /* @see ICharacterPairMatcher#clear() */
145     public void clear() {
146         fAnchor= -1;
147     }
148
149     /**
150      * Utility class that wraps a document and gives access to
151      * partitioning information. A document is tied to a particular
152      * partition and, when considering whether or not a position is a
153      * valid match, only considers position within its partition.
154      */

155     private static class DocumentPartitionAccessor {
156         
157         private final IDocument fDocument;
158         private final String JavaDoc fPartitioning, fPartition;
159         private ITypedRegion fCachedPartition;
160         
161         /**
162          * Creates a new partitioned document for the specified document.
163          *
164          * @param doc the document to wrap
165          * @param partitioning the partitioning used
166          * @param partition the partition managed by this document
167          */

168         public DocumentPartitionAccessor(IDocument doc, String JavaDoc partitioning,
169                 String JavaDoc partition) {
170             fDocument= doc;
171             fPartitioning= partitioning;
172             fPartition= partition;
173         }
174     
175         /**
176          * Returns the character at the specified position in this document.
177          *
178          * @param pos an offset within this document
179          * @return the character at the offset
180          * @throws BadLocationException
181          */

182         public char getChar(int pos) throws BadLocationException {
183             return fDocument.getChar(pos);
184         }
185         
186         /**
187          * Returns true if the character at the specified position is a
188          * valid match for the specified end character. To be a valid
189          * match, it must be in the appropriate partition and equal to the
190          * end character.
191          *
192          * @param pos an offset within this document
193          * @param end the end character to match against
194          * @return true exactly if the position represents a valid match
195          * @throws BadLocationException
196          */

197         public boolean isMatch(int pos, char end) throws BadLocationException {
198             return getChar(pos) == end && inPartition(pos);
199         }
200         
201         /**
202          * Returns true if the specified offset is within the partition
203          * managed by this document.
204          *
205          * @param pos an offset within this document
206          * @return true if the offset is within this document's partition
207          */

208         public boolean inPartition(int pos) {
209             final ITypedRegion partition= getPartition(pos);
210             return partition != null && partition.getType().equals(fPartition);
211         }
212         
213         /**
214          * Returns the next position to query in the search. The position
215          * is not guaranteed to be in this document's partition.
216          *
217          * @param pos an offset within the document
218          * @param searchForward the direction of the search
219          * @return the next position to query
220          */

221         public int getNextPosition(int pos, boolean searchForward) {
222             final ITypedRegion partition= getPartition(pos);
223             if (partition == null) return simpleIncrement(pos, searchForward);
224             if (fPartition.equals(partition.getType()))
225                 return simpleIncrement(pos, searchForward);
226             if (searchForward) {
227                 int end= partition.getOffset() + partition.getLength();
228                 if (pos < end)
229                     return end;
230             } else {
231                 int offset= partition.getOffset();
232                 if (pos > offset)
233                     return offset - 1;
234             }
235             return simpleIncrement(pos, searchForward);
236         }
237     
238         private int simpleIncrement(int pos, boolean searchForward) {
239             return pos + (searchForward ? 1 : -1);
240         }
241         
242         /**
243          * Returns partition information about the region containing the
244          * specified position.
245          *
246          * @param pos a position within this document.
247          * @return positioning information about the region containing the
248          * position
249          */

250         private ITypedRegion getPartition(int pos) {
251             if (fCachedPartition == null || !contains(fCachedPartition, pos)) {
252                 Assert.isTrue(pos >= 0 && pos <= fDocument.getLength());
253                 try {
254                     fCachedPartition= TextUtilities.getPartition(fDocument, fPartitioning, pos, false);
255                 } catch (BadLocationException e) {
256                     fCachedPartition= null;
257                 }
258             }
259             return fCachedPartition;
260         }
261         
262         private static boolean contains(IRegion region, int pos) {
263             int offset= region.getOffset();
264             return offset <= pos && pos < offset + region.getLength();
265         }
266         
267     }
268
269     /**
270      * Utility class that encapsulates access to matching character pairs.
271      */

272     private static class CharPairs {
273
274         private final char[] fPairs;
275
276         public CharPairs(char[] pairs) {
277             fPairs= pairs;
278         }
279
280         /**
281          * Returns true if the specified character pair occurs in one
282          * of the character pairs.
283          *
284          * @param c a character
285          * @return true exactly if the character occurs in one of the pairs
286          */

287         public boolean contains(char c) {
288             return getAllCharacters().contains(new Character JavaDoc(c));
289         }
290
291         private Set JavaDoc/*<Character>*/ fCharsCache= null;
292         /**
293          * @return A set containing all characters occurring in character pairs.
294          */

295         private Set JavaDoc/*<Character>*/ getAllCharacters() {
296             if (fCharsCache == null) {
297                 Set JavaDoc/*<Character>*/ set= new HashSet JavaDoc/*<Character>*/();
298                 for (int i= 0; i < fPairs.length; i++)
299                     set.add(new Character JavaDoc(fPairs[i]));
300                 fCharsCache= set;
301             }
302             return fCharsCache;
303         }
304
305         /**
306          * Returns true if the specified character opens a character pair
307          * when scanning in the specified direction.
308          *
309          * @param c a character
310          * @param searchForward the direction of the search
311          * @return whether or not the character opens a character pair
312          */

313         public boolean isOpeningCharacter(char c, boolean searchForward) {
314             for (int i= 0; i < fPairs.length; i += 2) {
315                 if (searchForward && getStartChar(i) == c) return true;
316                 else if (!searchForward && getEndChar(i) == c) return true;
317             }
318             return false;
319         }
320
321         /**
322          * Returns true of the specified character is a start character.
323          *
324          * @param c a character
325          * @return true exactly if the character is a start character
326          */

327         public boolean isStartCharacter(char c) {
328             return this.isOpeningCharacter(c, true);
329         }
330     
331         /**
332          * Returns the matching character for the specified character.
333          *
334          * @param c a character occurring in a character pair
335          * @return the matching character
336          */

337         public char getMatching(char c) {
338             for (int i= 0; i < fPairs.length; i += 2) {
339                 if (getStartChar(i) == c) return getEndChar(i);
340                 else if (getEndChar(i) == c) return getStartChar(i);
341             }
342             Assert.isTrue(false);
343             return '\0';
344         }
345     
346         private char getStartChar(int i) {
347             return fPairs[i];
348         }
349     
350         private char getEndChar(int i) {
351             return fPairs[i + 1];
352         }
353     
354     }
355
356 }
357
Popular Tags