2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.api.lexer;
44
import java.lang.ref.Reference;
45
import java.lang.ref.SoftReference;
47
import java.util.WeakHashMap;
50
* Language path describes a complete embedding
51
* of the languages starting from the root (top-level) language
52
* till the most embedded language.
54
* Language path consists of one root language
55
* and zero or more embedded languages.
57
* E.g. for javadoc embedded in java that is embedded in jsp
58
* then the language path <code>lp</code> would return the following:<pre>
60
* lp.language(0) == JspTokenId.language()
61
* lp.language(1) == JavaTokenId.language()
62
* lp.language(2) == JavadocTokenId.language()
66
* The two language paths for the same languages in the same order
67
* represent a single object. Therefore language paths can be compared
68
* by using == operator.
73
* Once a particular language path is created
74
* it is held by a soft reference from its "parent" language path.
78
* This class may safely be used by multiple threads.
81
* @author Miloslav Metelka
84
public final class LanguagePath {
87
* Empty language path for internal use and referencing the top-level language paths.
89
private static final LanguagePath EMPTY = new LanguagePath();
92
* Get language path that contains a single language.
94
* @param language non-null language.
95
* @return non-null language path.
97
public static LanguagePath get(Language<?> language) {
98
return get(null, language);
102
* Get language path corresponding to the language embedded in the given context
105
* This method has the same effect like using {@link #embedded(Language)}.
107
* For example for java scriplet embedded in jsp the prefix would
108
* be a language-path for jsp language and language would be java language.
110
* By using this method language paths with arbitrary depth can be created.
113
* @param prefix prefix language path determining the context in which
114
* the language is embedded or null if there is no prefix.
115
* @param language non-null language.
116
* @return non-null language path.
118
public static LanguagePath get(LanguagePath prefix, Language<?> language) {
121
return prefix.embedded(language);
125
* Array of component language paths for this language path.
127
* The last member of the array is <code>this</code>.
129
private final Language<?>[] languages;
132
* Mapping of embedded language (or suffix language path) to a weak reference to LanguagePath.
134
private Map<Object, Reference<LanguagePath>> language2path;
137
* Cached and interned mime-path string.
139
private String mimePath;
142
* Language path with inner language removed. Null for single-language paths.
144
private LanguagePath parent;
147
private LanguagePath(LanguagePath prefix, Language<?> language) {
148
int prefixSize = prefix.size();
149
this.languages = allocateLanguageArray(prefixSize + 1);
150
System.arraycopy(prefix.languages, 0, this.languages, 0, prefixSize);
151
this.languages[prefixSize] = language;
152
this.parent = (prefix == EMPTY) ? null : prefix;
155
/** Build EMPTY LanguagePath */
156
private LanguagePath() {
157
this.languages = allocateLanguageArray(0);
161
* Get total number of languages in this language path.
163
* @return >=1 number of languages contained in this language path.
166
return languages.length;
170
* Get language of this language path at the given index.
172
* Index zero corresponds to the root language.
174
* @param index >=0 && < {@link #size()}.
175
* @return non-null language at the given index.
176
* @throws IndexOutOfBoundsException in case the index is not within
179
public Language<?> language(int index) {
180
return languages[index];
184
* Get embedded path of this language path.
186
* This method has the same effect like using {@link #get(LanguagePath,Language)}
187
* but this one is usually preferred as it supports more readable code.
189
* For example for java scriplet embedded in jsp the prefix would
190
* be a language-path for jsp language and language would be java language.
192
* By using this method language paths with arbitrary depth can be created.
194
* @param language non-null language.
195
* @return non-null language path.
197
public LanguagePath embedded(Language<?> language) {
198
if (language == null) {
199
throw new IllegalArgumentException("language cannot be null");
201
// Attempt to retrieve from the cache first
202
synchronized (languages) {
204
Reference<LanguagePath> lpRef = language2path.get(language);
206
if (lpRef == null || (lp = lpRef.get()) == null) {
207
// Construct the LanguagePath
208
lp = new LanguagePath(this, language);
209
language2path.put(language, new SoftReference<LanguagePath>(lp));
217
* Get language path corresponding to the suffix language path embedded
220
* @param suffix non-null suffix to be added to this path.
221
* @return non-null language path consisting of this path with the
222
* suffix added to the end.
224
public LanguagePath embedded(LanguagePath suffix) {
225
if (suffix == null) {
226
throw new IllegalArgumentException("suffix cannot be null");
228
// Attempt to retrieve from the cache first
229
synchronized (languages) {
231
Reference<LanguagePath> lpRef = language2path.get(suffix);
233
if (lpRef == null || (lp = lpRef.get()) == null) {
234
// Construct the LanguagePath
236
for (int i = 0; i < suffix.size(); i++) {
237
lp = lp.embedded(suffix.language(i));
239
language2path.put(suffix, new SoftReference<LanguagePath>(lp));
247
* Returns language path consisting of <code><0, size() - 1></code>
248
* languages (i.e. the inner language is cut out).
250
* If {@link #size()} == 1 then <code>null</code> is returned.
252
public LanguagePath parent() {
257
* Return the top-level language of this language path.
259
* It's equivalent to <code>language(0)</code>.
261
* @see #language(int)
263
public Language<?> topLanguage() {
268
* Return the most inner language of this path.
270
* It's equivalent to <code>language(size() - 1)</code>.
272
* @see #language(int)
274
public Language<?> innerLanguage() {
275
return language(size() - 1);
279
* Check whether this language path ends with the given language path.
281
* This may be useful for checking whether a given input contains certain language
282
* (or language path) that may possibly be embedded somewhere in the input.
284
* @param languagePath non-null language path to be checked.
285
* @return true if this language path contains the given language path
286
* at its end (applies for <code>this</code> as well).
288
public boolean endsWith(LanguagePath languagePath) {
289
if (languagePath == this || languagePath == EMPTY)
291
int lpSize = languagePath.size();
292
if (lpSize <= size()) {
293
for (int i = 1; i <= lpSize; i++) {
294
if (language(size() - i) != languagePath.language(lpSize - i))
303
* Gets the path starting at the given index and ending after
304
* the last language contained in this path.
306
* @see #subPath(int, int)
308
public LanguagePath subPath(int startIndex) {
309
return subPath(startIndex, size());
313
* Gets the path starting at the given index and ending after
314
* the last language contained in this path.
316
* @param startIndex >=0 starting index of the requested path in this path.
317
* @param endIndex >startIndex index after the last item
318
* of the requested path.
319
* @return non-null language path containing items between startIndex and endIndex.
321
public LanguagePath subPath(int startIndex, int endIndex) {
322
if (startIndex < 0) {
323
throw new IndexOutOfBoundsException("startIndex=" + startIndex + " < 0"); // NOI18N
325
if (endIndex > size()) {
326
throw new IndexOutOfBoundsException("endIndex=" + endIndex + " > size()=" + size());
328
if (startIndex >= endIndex) {
329
throw new IndexOutOfBoundsException("startIndex=" + startIndex + " >= endIndex=" + endIndex);
331
if (startIndex == 0 && endIndex == size()) {
334
LanguagePath lp = LanguagePath.get(language(startIndex++));
335
while (startIndex < endIndex) {
336
lp = LanguagePath.get(lp, language(startIndex++));
342
* Gets the mime path equivalent of this language path. The mime path is
343
* a concatenation of mime types of all the languages in this language path.
344
* The mime types are separated by the '/' character.
347
* For example the language path of the java language embedded in the
348
* JSP language will return 'text/x-jsp/text/x-java' when this method is called.
352
* The returned string path can be used in MimeLookup's operation
353
* to obtain a corresponding MimePath object by using
354
* <code>MimePath.parse(returned-mime-path-string)</code>.
357
* @return The mime path string.
358
* @see org.netbeans.spi.lexer.LanguageHierarchy#mimeType()
360
public String mimePath() {
361
synchronized (languages) {
362
if (mimePath == null) {
363
StringBuilder sb = new StringBuilder(15 * languages.length);
364
for (Language<?> language : languages) {
365
if (sb.length() > 0) {
368
sb.append(language.mimeType());
370
// Intern the mimePath for faster operation of MimePath.parse()
371
mimePath = sb.toString().intern();
377
private void initLanguage2path() {
378
if (language2path == null) {
379
language2path = new WeakHashMap<Object,Reference<LanguagePath>>();
384
private Language<?>[] allocateLanguageArray(int length) {
385
return (Language<?>[])(new Language[length]);
388
public String toString() {
389
StringBuilder sb = new StringBuilder();
390
sb.append("LanguagePath: size=");
393
for (int i = 0; i < size(); i++) {
394
sb.append('[').append(i).append("]: "); // NOI18N
395
sb.append(language(i)).append('\n');
397
return sb.toString();
b'\\ No newline at end of file'