KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > editor > lib2 > highlighting > SyntaxHighlighting


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.editor.lib2.highlighting;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.ConcurrentModificationException JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.NoSuchElementException JavaDoc;
27 import java.util.WeakHashMap JavaDoc;
28 import java.util.logging.Level JavaDoc;
29 import java.util.logging.Logger JavaDoc;
30 import javax.swing.text.AttributeSet JavaDoc;
31 import javax.swing.text.Document JavaDoc;
32 import javax.swing.text.SimpleAttributeSet JavaDoc;
33 import org.netbeans.api.editor.mimelookup.MimeLookup;
34 import org.netbeans.api.editor.mimelookup.MimePath;
35 import org.netbeans.api.editor.settings.AttributesUtilities;
36 import org.netbeans.api.editor.settings.FontColorSettings;
37 import org.netbeans.api.lexer.Language;
38 import org.netbeans.api.lexer.LanguagePath;
39 import org.netbeans.api.lexer.TokenHierarchy;
40 import org.netbeans.api.lexer.TokenHierarchyEvent;
41 import org.netbeans.api.lexer.TokenHierarchyListener;
42 import org.netbeans.api.lexer.TokenId;
43 import org.netbeans.api.lexer.TokenSequence;
44 import org.netbeans.spi.editor.highlighting.HighlightsSequence;
45 import org.netbeans.spi.editor.highlighting.support.AbstractHighlightsContainer;
46 import org.openide.util.Lookup;
47 import org.openide.util.WeakListeners;
48
49 /**
50  * The syntax coloring layer.
51  *
52  * @author Vita Stejskal
53  */

54 public final class SyntaxHighlighting extends AbstractHighlightsContainer implements TokenHierarchyListener {
55     
56     private static final Logger JavaDoc LOG = Logger.getLogger(SyntaxHighlighting.class.getName());
57     
58     public static final String JavaDoc LAYER_TYPE_ID = "org.netbeans.modules.editor.lib2.highlighting.SyntaxHighlighting"; //NOI18N
59

60     private final HashMap JavaDoc<String JavaDoc, WeakHashMap JavaDoc<TokenId, AttributeSet JavaDoc>> attribsCache = new HashMap JavaDoc<String JavaDoc, WeakHashMap JavaDoc<TokenId, AttributeSet JavaDoc>>();
61     private final HashMap JavaDoc<String JavaDoc, FontColorSettings> fcsCache = new HashMap JavaDoc<String JavaDoc, FontColorSettings>();
62     
63     private final Document JavaDoc document;
64     private final String JavaDoc mimeTypeForHack;
65     private TokenHierarchy<? extends Document JavaDoc> hierarchy = null;
66     private long version = 0;
67     
68     /** Creates a new instance of SyntaxHighlighting */
69     public SyntaxHighlighting(Document JavaDoc document) {
70         this.document = document;
71         
72         String JavaDoc mimeType = (String JavaDoc) document.getProperty("mimeType"); //NOI18N
73
if (mimeType != null && mimeType.startsWith("test")) { //NOI18N
74
this.mimeTypeForHack = mimeType;
75         } else {
76             this.mimeTypeForHack = null;
77         }
78     }
79
80     public HighlightsSequence getHighlights(int startOffset, int endOffset) {
81         synchronized (this) {
82             if (hierarchy == null) {
83                 hierarchy = TokenHierarchy.get(document);
84                 if (hierarchy != null) {
85                     hierarchy.addTokenHierarchyListener(WeakListeners.create(TokenHierarchyListener.class, this, hierarchy));
86                 }
87             }
88
89             if (hierarchy != null) {
90                 return new HSImpl(version, hierarchy, startOffset, endOffset);
91             } else {
92                 return HighlightsSequence.EMPTY;
93             }
94         }
95     }
96
97     // ----------------------------------------------------------------------
98
// TokenHierarchyListener implementation
99
// ----------------------------------------------------------------------
100

101     public void tokenHierarchyChanged(TokenHierarchyEvent evt) {
102         synchronized (this) {
103             version++;
104         }
105
106         fireHighlightsChange(evt.affectedStartOffset(), evt.affectedEndOffset());
107     }
108
109     // ----------------------------------------------------------------------
110
// Private implementation
111
// ----------------------------------------------------------------------
112

113     private final class HSImpl implements HighlightsSequence {
114         
115         private static final int S_NORMAL = 1;
116         private static final int S_EMBEDDED_HEAD = 2;
117         private static final int S_EMBEDDED_TAIL = 3;
118         private static final int S_DONE = 4;
119
120         private long version;
121         private TokenHierarchy<? extends Document JavaDoc> scanner;
122         private List JavaDoc<TokenSequence<? extends TokenId>> sequences;
123         private int startOffset;
124         private int endOffset;
125         private int state;
126         
127         public HSImpl(long version, TokenHierarchy<? extends Document JavaDoc> scanner, int startOffset, int endOffset) {
128             this.version = version;
129             this.scanner = scanner;
130             this.startOffset = startOffset;
131             this.endOffset = endOffset;
132             this.sequences = null;
133         }
134         
135         public boolean moveNext() {
136             synchronized (SyntaxHighlighting.this) {
137                 checkVersion();
138
139                 if (sequences == null) {
140                     // initialize
141
TokenSequence<? extends TokenId> seq = scanner.tokenSequence();
142                     seq.move(startOffset);
143                     sequences = new ArrayList JavaDoc<TokenSequence<? extends TokenId>>();
144                     sequences.add(seq);
145                     state = S_NORMAL;
146                 }
147
148                 switch (state) {
149                     case S_NORMAL:
150                         // The current token is a normal one
151
state = moveTheSequence();
152                         break;
153
154                     case S_EMBEDDED_HEAD:
155                         // The current token contains embedded language and we have processed it's head
156
TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1);
157                         seq.moveStart();
158                         if (seq.moveNext()) {
159                             state = S_NORMAL;
160                         } else {
161                             throw new IllegalStateException JavaDoc("Invalid state");
162                         }
163                         break;
164
165                     case S_EMBEDDED_TAIL:
166                         // The current token contains embedded language and we have processed it's tail
167
sequences.remove(sequences.size() - 1);
168                         state = moveTheSequence();
169                         break;
170
171                     case S_DONE:
172                         // We have gone through all the tokens in all sequences
173
break;
174
175                     default:
176                         throw new IllegalStateException JavaDoc("Invalid state: " + state);
177                 }
178
179                 if (state == S_NORMAL) {
180                     // We have moved to the next normal token, so look what it is
181
TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1);
182                     TokenSequence<? extends TokenId> embeddedSeq = seq.embedded();
183                     while (embeddedSeq != null && embeddedSeq.moveNext()) {
184                         sequences.add(sequences.size(), embeddedSeq);
185                         if (embeddedSeq.offset() > seq.offset()) {
186                             state = S_EMBEDDED_HEAD;
187                             break;
188                         } else {
189                             seq = embeddedSeq;
190                             embeddedSeq = seq.embedded();
191                         }
192                     }
193                 } else if (state == S_DONE) {
194                     attribsCache.clear();
195                 }
196
197                 return state != S_DONE;
198             }
199         }
200
201         public int getStartOffset() {
202             synchronized (SyntaxHighlighting.this) {
203                 checkVersion();
204
205                 if (sequences == null) {
206                     throw new NoSuchElementException JavaDoc("Call moveNext() first.");
207                 }
208
209                 switch (state) {
210                     case S_NORMAL: {
211                         TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1);
212                         return seq.offset();
213                     }
214                     case S_EMBEDDED_HEAD: {
215                         TokenSequence<? extends TokenId> embeddingSeq = sequences.get(sequences.size() - 2);
216                         return embeddingSeq.offset();
217                     }
218                     case S_EMBEDDED_TAIL: {
219                         TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1);
220                         seq.moveEnd();
221                         if (seq.movePrevious()) {
222                             return seq.offset() + seq.token().length();
223                         } else {
224                             throw new IllegalStateException JavaDoc("Invalid state");
225                         }
226                     }
227                     case S_DONE:
228                         throw new NoSuchElementException JavaDoc();
229
230                     default:
231                         throw new IllegalStateException JavaDoc("Invalid state: " + state);
232                 }
233             }
234         }
235
236         public int getEndOffset() {
237             synchronized (SyntaxHighlighting.this) {
238                 checkVersion();
239
240                 if (sequences == null) {
241                     throw new NoSuchElementException JavaDoc("Call moveNext() first.");
242                 }
243
244                 switch (state) {
245                     case S_NORMAL: {
246                         TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1);
247                         return seq.offset() + seq.token().length();
248                     }
249                     case S_EMBEDDED_HEAD: {
250                         TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1);
251                         seq.moveStart();
252                         if (seq.moveNext()) {
253                             return seq.offset();
254                         } else {
255                             TokenSequence<? extends TokenId> embeddingSeq = sequences.get(sequences.size() - 2);
256                             return embeddingSeq.offset() + embeddingSeq.token().length();
257                         }
258                     }
259                     case S_EMBEDDED_TAIL:
260                         TokenSequence<? extends TokenId> embeddingSeq = sequences.get(sequences.size() - 2);
261                         return embeddingSeq.offset() + embeddingSeq.token().length();
262
263                     case S_DONE:
264                         throw new NoSuchElementException JavaDoc();
265
266                     default:
267                         throw new IllegalStateException JavaDoc("Invalid state: " + state);
268                 }
269             }
270         }
271
272         public AttributeSet JavaDoc getAttributes() {
273             synchronized (SyntaxHighlighting.this) {
274                 checkVersion();
275
276                 if (sequences == null) {
277                     throw new NoSuchElementException JavaDoc("Call moveNext() first.");
278                 }
279
280                 switch (state) {
281                     case S_NORMAL:
282                         return findAttribs(sequences.size() - 1);
283
284                     case S_EMBEDDED_HEAD:
285                     case S_EMBEDDED_TAIL:
286                         return findAttribs(sequences.size() - 2);
287
288                     case S_DONE:
289                         throw new NoSuchElementException JavaDoc();
290
291                     default:
292                         throw new IllegalStateException JavaDoc("Invalid state: " + state);
293                 }
294             }
295         }
296         
297         private AttributeSet JavaDoc findAttribs(int seqIdx) {
298             TokenSequence<? extends TokenId> seq = sequences.get(seqIdx);
299             TokenId tokenId = seq.token().id();
300             String JavaDoc mimePath;
301             
302             if (mimeTypeForHack != null) {
303                 mimePath = languagePathToMimePathHack(seq.languagePath());
304             } else {
305                 mimePath = seq.languagePath().mimePath();
306             }
307
308             WeakHashMap JavaDoc<TokenId, AttributeSet JavaDoc> token2attribs = attribsCache.get(mimePath);
309             if (token2attribs == null) {
310                 token2attribs = new WeakHashMap JavaDoc<TokenId, AttributeSet JavaDoc>();
311                 attribsCache.put(mimePath, token2attribs);
312             }
313             
314             AttributeSet JavaDoc tokenAttribs = token2attribs.get(tokenId);
315             if (tokenAttribs == null) {
316                 tokenAttribs = findTokenAttribs(tokenId, mimePath, seq.languagePath().innerLanguage());
317
318                 if (seqIdx > 0) {
319                     AttributeSet JavaDoc embeddingTokenAttribs = findAttribs(seqIdx - 1);
320                     tokenAttribs = AttributesUtilities.createComposite(
321                         tokenAttribs, embeddingTokenAttribs);
322                 }
323                 
324                 token2attribs.put(tokenId, tokenAttribs);
325             }
326             
327             return tokenAttribs;
328         }
329
330         private AttributeSet JavaDoc findTokenAttribs(TokenId tokenId, String JavaDoc mimePath, Language<? extends TokenId> innerLanguage) {
331             FontColorSettings fcs = fcsCache.get(mimePath);
332             
333             if (fcs == null) {
334                 Lookup lookup = MimeLookup.getLookup(MimePath.parse(mimePath));
335                 fcs = lookup.lookup(FontColorSettings.class);
336                 fcsCache.put(mimePath, fcs);
337             }
338             
339             AttributeSet JavaDoc attribs = findFontAndColors(fcs, tokenId, innerLanguage);
340             
341             if (LOG.isLoggable(Level.FINE)) {
342                 LOG.fine(mimePath + ":" + tokenId.name() + " -> " + attribs); //NOI18N
343
}
344             
345             return attribs != null ? attribs : SimpleAttributeSet.EMPTY;
346         }
347
348         // XXX: This hack is here to make sure that preview panels in Tools-Options
349
// work. Currently there is no way how to force a particular JTextComponent
350
// to use a particular MimeLookup. They all use MimeLookup common for all components
351
// and for the mime path of things displayed in that component. The preview panels
352
// however need special MimeLookup that loads colorings from a special profile
353
// (i.e. not the currently active coloring profile, which is used normally by
354
// all the other components).
355
//
356
// The hack is that Tools-Options modifies mime type of the document loaded
357
// in the preview panel and prepend 'textXXXX_' at the beginning. The normal
358
// MimeLookup for this mime type and any mime path derived from this mime type
359
// is empty. The editor/settings/storage however provides a special handling
360
// for these 'test' mime paths and bridge them to the MimeLookup that you would
361
// normally get for the mime path without the 'testXXXX_' at the beginning, plus
362
// they supply special colorings from the profile called 'testXXXX'. This way
363
// the preview panels can have different colorings from the rest of the IDE.
364
//
365
// This is obviously very fragile and not fully transparent for clients as
366
// you can see here. We need a better solution for that. Generally it should
367
// be posible to ask somewhere for a component-specific MimeLookup. This would
368
// normally be a standard MimeLookup as you know it, but in special cases it
369
// could be modified by the client code that created the component - e.g. Tools-Options
370
// panel.
371
private String JavaDoc languagePathToMimePathHack(LanguagePath languagePath) {
372             if (languagePath.size() == 1) {
373                 return mimeTypeForHack;
374             } else if (languagePath.size() > 1) {
375                 return mimeTypeForHack + "/" + languagePath.subPath(1).mimePath(); //NOI18N
376
} else {
377                 throw new IllegalStateException JavaDoc("LanguagePath should not be empty."); //NOI18N
378
}
379         }
380         
381         private AttributeSet JavaDoc findFontAndColors(FontColorSettings fcs, TokenId tokenId, Language lang) {
382             // First try the token's name
383
String JavaDoc name = tokenId.name();
384             AttributeSet JavaDoc attribs = fcs.getTokenFontColors(name);
385             
386             // Then try the primary category
387
if (attribs == null) {
388                 String JavaDoc primary = tokenId.primaryCategory();
389                 if (primary != null) {
390                     attribs = fcs.getTokenFontColors(primary);
391                 }
392             }
393             
394             // Then try all the other categories
395
if (attribs == null) {
396                 @SuppressWarnings JavaDoc("unchecked")
397                 List JavaDoc<String JavaDoc> categories = ((Language<TokenId>)lang).nonPrimaryTokenCategories(tokenId);
398                 for(String JavaDoc c : categories) {
399                     attribs = fcs.getTokenFontColors(c);
400                     if (attribs != null) {
401                         break;
402                     }
403                 }
404             }
405             
406             return attribs;
407         }
408         
409         private int moveTheSequence() {
410             TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1);
411             
412             if (seq.moveNext() && seq.offset() < endOffset) {
413                 return S_NORMAL;
414             } else {
415                 if (sequences.size() > 1) {
416                     TokenSequence<? extends TokenId> embeddingSeq = sequences.get(sequences.size() - 2);
417                     seq.moveEnd();
418                     if (seq.movePrevious()) {
419                         if ((seq.offset() + seq.token().length()) < (embeddingSeq.offset() + embeddingSeq.token().length())) {
420                             return S_EMBEDDED_TAIL;
421                         } else {
422                             sequences.remove(sequences.size() - 1);
423                             return moveTheSequence();
424                         }
425                     } else {
426                         throw new IllegalStateException JavaDoc("Invalid state");
427                     }
428                 } else {
429                     sequences.clear();
430                     return S_DONE;
431                 }
432             }
433         }
434         
435         private void checkVersion() {
436             if (this.version != SyntaxHighlighting.this.version) {
437                 throw new ConcurrentModificationException JavaDoc();
438             }
439         }
440     } // End of HSImpl class
441
}
442
Popular Tags