001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.lib.lexer;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.util.Collections;
047: import java.util.HashSet;
048: import java.util.Set;
049: import org.netbeans.api.lexer.Language;
050: import org.netbeans.api.lexer.LanguagePath;
051: import org.netbeans.api.lexer.Token;
052: import org.netbeans.api.lexer.TokenId;
053: import org.netbeans.lib.editor.util.CharSequenceUtilities;
054: import org.netbeans.lib.lexer.token.DefaultToken;
055: import org.netbeans.spi.lexer.EmbeddingPresence;
056: import org.netbeans.spi.lexer.LanguageHierarchy;
057: import org.netbeans.lib.lexer.token.TextToken;
058: import org.netbeans.spi.lexer.LanguageEmbedding;
059: import org.netbeans.spi.lexer.TokenFactory;
060: import org.netbeans.spi.lexer.TokenValidator;
061: import org.openide.util.WeakListeners;
062:
063: /**
064: * The operation behind the language hierarchy.
065: *
066: * @author Miloslav Metelka
067: * @version 1.00
068: */
069:
070: public final class LanguageOperation<T extends TokenId> implements
071: PropertyChangeListener {
072:
073: private static final int MAX_START_SKIP_LENGTH_CACHED = 10;
074:
075: private static final int MAX_END_SKIP_LENGTH_CACHED = 10;
076:
077: private static final TokenValidator<TokenId> NULL_VALIDATOR = new TokenValidator<TokenId>() {
078: public Token<TokenId> validateToken(Token<TokenId> token,
079: TokenFactory<TokenId> factory, CharSequence tokenText,
080: int modRelOffset, int removedLength,
081: CharSequence removedText, int insertedLength,
082: CharSequence insertedText) {
083: return null;
084: }
085: };
086:
087: /**
088: * Find the language paths either for this language only
089: * or from TokenHierarchyOperation when adding a new default or custom embedding
090: * to the token hierarchy.
091: * <br/>
092: * As a language may finally be embedded in itself (e.g. someone might
093: * want to syntax color java code snippet embedded in javadoc)
094: * this method must prevent creation of infinite language paths
095: * by using exploredLanguages parameter.
096: *
097: * @param existingLanguagePaths set of language paths that are already known.
098: * This set is not modified by the method.
099: * @param newLanguagePaths newly discovered language paths will be added to this set.
100: * @param exploredLanguages used for checking whether the subpaths containing
101: * this language were already discovered.
102: * @param lp language path that will be checked. Its innermost language
103: * will be checked for embeddings automatically.
104: */
105: public static <T extends TokenId> void findLanguagePaths(
106: Set<LanguagePath> existingLanguagePaths,
107: Set<LanguagePath> newLanguagePaths,
108: Set<Language<?>> exploredLanguages, LanguagePath lp) {
109: // Get the complete language path
110: if (!existingLanguagePaths.contains(lp)) {
111: newLanguagePaths.add(lp);
112: }
113: @SuppressWarnings("unchecked")
114: Language<T> language = (Language<T>) lp.innerLanguage();
115: if (!exploredLanguages.contains(language)) {
116: exploredLanguages.add(language);
117: Set<T> ids = language.tokenIds();
118: for (T id : ids) {
119: // Create a fake empty token
120: DefaultToken<T> emptyToken = new DefaultToken<T>(id);
121: // Find embedding for non-flyweight token
122: LanguageHierarchy<T> languageHierarchy = LexerUtilsConstants
123: .innerLanguageHierarchy(lp);
124: LanguageEmbedding<?> embedding = LexerUtilsConstants
125: .findEmbedding(languageHierarchy, emptyToken,
126: lp, null);
127: if (embedding != null) {
128: LanguagePath elp = LanguagePath.get(lp, embedding
129: .language());
130: findLanguagePaths(existingLanguagePaths,
131: newLanguagePaths, exploredLanguages, elp);
132: }
133: }
134: }
135: }
136:
137: private final LanguageHierarchy<T> languageHierarchy;
138:
139: private final Language<T> language;
140:
141: /** Embeddings cached by start skip length and end skip length. */
142: private LanguageEmbedding<T>[][] cachedEmbeddings;
143:
144: /**
145: * Possibility of embedding presence for token ids.
146: */
147: private EmbeddingPresence[] embeddingPresences;
148:
149: private LanguageEmbedding<T>[][] cachedJoinSectionsEmbeddings;
150:
151: private TokenValidator<T>[] tokenValidators;
152:
153: private Set<LanguagePath> languagePaths;
154:
155: private Set<Language<?>> exploredLanguages;
156:
157: private FlyItem<T>[] flyItems;
158:
159: public LanguageOperation(LanguageHierarchy<T> languageHierarchy,
160: Language<T> language) {
161: this .languageHierarchy = languageHierarchy;
162: this .language = language; // Should not be operated during constructor (not inited yet)
163:
164: // Listen on changes in language manager
165: LanguageManager.getInstance().addPropertyChangeListener(
166: WeakListeners.create(PropertyChangeListener.class,
167: this , LanguageManager.getInstance()));
168: }
169:
170: public synchronized TokenValidator<T> tokenValidator(T id) {
171: if (tokenValidators == null) {
172: tokenValidators = allocateTokenValidatorArray(language
173: .maxOrdinal() + 1);
174: }
175: // Not synced intentionally (no problem to create dup instances)
176: TokenValidator<T> validator = tokenValidators[id.ordinal()];
177: if (validator == null) {
178: validator = LexerSpiPackageAccessor.get()
179: .createTokenValidator(languageHierarchy, id);
180: if (validator == null) {
181: validator = nullValidator();
182: }
183: tokenValidators[id.ordinal()] = validator;
184: }
185: return (validator == nullValidator()) ? null : validator;
186: }
187:
188: public synchronized TextToken<T> getFlyweightToken(T id, String text) {
189: TextToken<T> token;
190: if (flyItems == null) {
191: // Create flyItems array
192: @SuppressWarnings("unchecked")
193: FlyItem<T>[] arr = (FlyItem<T>[]) new FlyItem[language
194: .maxOrdinal() + 1];
195: flyItems = arr;
196: }
197: FlyItem<T> item = flyItems[id.ordinal()];
198: if (item == null) {
199: token = new TextToken<T>(id, text); // create flyweight token
200: token.makeFlyweight();
201: flyItems[id.ordinal()] = new FlyItem<T>(token);
202: } else { // already a valid item
203: token = item.token();
204: if (token.text() != text) {
205: token = item.token2();
206: if (token == null || token.text() != text) {
207: token = item.token();
208: if (!CharSequenceUtilities.textEquals(token.text(),
209: text)) {
210: token = item.token2();
211: if (token == null
212: || !CharSequenceUtilities.textEquals(
213: token.text(), text)) {
214: // Create new token
215: token = new TextToken<T>(id, text);
216: token.makeFlyweight();
217: }
218: item.pushToken(token);
219: }
220: } else { // found token2
221: item.pushToken(token);
222: }
223: }
224: }
225: assert (token != null); // Should return non-null token
226: return token;
227: }
228:
229: public synchronized EmbeddingPresence embeddingPresence(T id) {
230: if (embeddingPresences == null) {
231: embeddingPresences = new EmbeddingPresence[language
232: .maxOrdinal() + 1];
233: }
234: EmbeddingPresence ep = embeddingPresences[id.ordinal()];
235: if (ep == null) { // Not initialized yet
236: ep = LexerSpiPackageAccessor.get().embeddingPresence(
237: languageHierarchy, id);
238: embeddingPresences[id.ordinal()] = ep;
239: }
240: return ep;
241: }
242:
243: public synchronized void setEmbeddingPresence(T id,
244: EmbeddingPresence ep) {
245: // No check embeddingPresences==null since always called after embeddingPresence(T id)
246: embeddingPresences[id.ordinal()] = ep;
247: }
248:
249: /**
250: * Get cached or create a new embedding with the language of this operation
251: * and the given start and end skip lengths.
252: * @return non-null embedding.
253: */
254: public synchronized LanguageEmbedding<T> getEmbedding(
255: int startSkipLength, int endSkipLength, boolean joinSections) {
256: LanguageEmbedding<T>[][] ce = joinSections ? cachedJoinSectionsEmbeddings
257: : cachedEmbeddings;
258: if (ce == null || startSkipLength >= ce.length) {
259: if (startSkipLength > MAX_START_SKIP_LENGTH_CACHED)
260: return createEmbedding(startSkipLength, endSkipLength,
261: joinSections);
262: @SuppressWarnings("unchecked")
263: LanguageEmbedding<T>[][] tmp = (LanguageEmbedding<T>[][]) new LanguageEmbedding[startSkipLength + 1][];
264: if (ce != null)
265: System.arraycopy(ce, 0, tmp, 0, ce.length);
266: ce = tmp;
267: if (joinSections)
268: cachedJoinSectionsEmbeddings = ce;
269: else
270: cachedEmbeddings = ce;
271: }
272: LanguageEmbedding<T>[] byESL = ce[startSkipLength];
273: if (byESL == null || endSkipLength >= byESL.length) { // given endSkipLength not cached
274: if (endSkipLength > MAX_END_SKIP_LENGTH_CACHED)
275: return createEmbedding(startSkipLength, endSkipLength,
276: joinSections);
277: @SuppressWarnings("unchecked")
278: LanguageEmbedding<T>[] tmp = (LanguageEmbedding<T>[]) new LanguageEmbedding[endSkipLength + 1];
279: if (byESL != null)
280: System.arraycopy(byESL, 0, tmp, 0, byESL.length);
281: byESL = tmp;
282: ce[startSkipLength] = byESL;
283: }
284: LanguageEmbedding<T> e = byESL[endSkipLength];
285: if (e == null) {
286: e = createEmbedding(startSkipLength, endSkipLength,
287: joinSections);
288: byESL[endSkipLength] = e;
289: }
290: return e;
291: }
292:
293: private LanguageEmbedding<T> createEmbedding(int startSkipLength,
294: int endSkipLength, boolean joinSections) {
295: return LexerSpiPackageAccessor.get().createLanguageEmbedding(
296: language, startSkipLength, endSkipLength, joinSections);
297: }
298:
299: /**
300: * Get static language paths for this language.
301: */
302: public Set<LanguagePath> languagePaths() {
303: Set<LanguagePath> lps;
304: synchronized (this ) {
305: lps = languagePaths;
306: }
307: if (lps == null) {
308: lps = new HashSet<LanguagePath>();
309: Set<LanguagePath> existingLps = Collections.emptySet();
310: Set<Language<?>> exploredLangs = new HashSet<Language<?>>();
311: findLanguagePaths(existingLps, lps, exploredLangs,
312: LanguagePath.get(language));
313: synchronized (this ) {
314: languagePaths = lps;
315: exploredLanguages = exploredLangs;
316: }
317: }
318: return lps;
319: }
320:
321: public Set<Language<?>> exploredLanguages() {
322: languagePaths(); // Init exploredLanguages
323: return exploredLanguages;
324: }
325:
326: public void propertyChange(PropertyChangeEvent evt) {
327: synchronized (this ) {
328: languagePaths = null;
329: exploredLanguages = null;
330: }
331: }
332:
333: @SuppressWarnings("unchecked")
334: private final TokenValidator<T> nullValidator() {
335: return (TokenValidator<T>) NULL_VALIDATOR;
336: }
337:
338: @SuppressWarnings("unchecked")
339: private final TokenValidator<T>[] allocateTokenValidatorArray(
340: int length) {
341: return (TokenValidator<T>[]) new TokenValidator[length];
342: }
343:
344: private static final class FlyItem<T extends TokenId> {
345:
346: private TextToken<T> token;
347:
348: private TextToken<T> token2;
349:
350: public FlyItem(TextToken<T> token) {
351: this .token = token;
352: }
353:
354: public TextToken<T> token() {
355: return token;
356: }
357:
358: public TextToken<T> token2() {
359: return token2;
360: }
361:
362: public void pushToken(TextToken<T> token) {
363: this.token2 = this.token;
364: this.token = token;
365: }
366:
367: }
368:
369: }
|