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.api.lexer;
043:
044: import java.lang.ref.Reference;
045: import java.lang.ref.SoftReference;
046: import java.util.Map;
047: import java.util.WeakHashMap;
048:
049: /**
050: * Language path describes a complete embedding
051: * of the languages starting from the root (top-level) language
052: * till the most embedded language.
053: * <br/>
054: * Language path consists of one root language
055: * and zero or more embedded languages.
056: * <br/>
057: * E.g. for javadoc embedded in java that is embedded in jsp
058: * then the language path <code>lp</code> would return the following:<pre>
059: * lp.size() == 3
060: * lp.language(0) == JspTokenId.language()
061: * lp.language(1) == JavaTokenId.language()
062: * lp.language(2) == JavadocTokenId.language()
063: * </pre>
064: *
065: * <p>
066: * The two language paths for the same languages in the same order
067: * represent a single object. Therefore language paths can be compared
068: * by using == operator.
069: * </p>
070: *
071: * <p>
072: * <b>Lifetime:</b>
073: * Once a particular language path is created
074: * it is held by a soft reference from its "parent" language path.
075: * </p>
076: *
077: * <p>
078: * This class may safely be used by multiple threads.
079: * </p>
080: *
081: * @author Miloslav Metelka
082: * @version 1.0
083: */
084: public final class LanguagePath {
085:
086: /**
087: * Empty language path for internal use and referencing the top-level language paths.
088: */
089: private static final LanguagePath EMPTY = new LanguagePath();
090:
091: /**
092: * Get language path that contains a single language.
093: *
094: * @param language non-null language.
095: * @return non-null language path.
096: */
097: public static LanguagePath get(Language<?> language) {
098: return get(null, language);
099: }
100:
101: /**
102: * Get language path corresponding to the language embedded in the given context
103: * language path.
104: * <br/>
105: * This method has the same effect like using {@link #embedded(Language)}.
106: * <br/>
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.
109: * <br/>
110: * By using this method language paths with arbitrary depth can be created.
111: *
112: *
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.
117: */
118: public static LanguagePath get(LanguagePath prefix,
119: Language<?> language) {
120: if (prefix == null)
121: prefix = EMPTY;
122: return prefix.embedded(language);
123: }
124:
125: /**
126: * Array of component language paths for this language path.
127: * <br>
128: * The last member of the array is <code>this</code>.
129: */
130: private final Language<?>[] languages;
131:
132: /**
133: * Mapping of embedded language (or suffix language path) to a weak reference to LanguagePath.
134: */
135: private Map<Object, Reference<LanguagePath>> language2path;
136:
137: /**
138: * Cached and interned mime-path string.
139: */
140: private String mimePath;
141:
142: /**
143: * Language path with inner language removed. Null for single-language paths.
144: */
145: private LanguagePath parent;
146:
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,
151: prefixSize);
152: this .languages[prefixSize] = language;
153: this .parent = (prefix == EMPTY) ? null : prefix;
154: }
155:
156: /** Build EMPTY LanguagePath */
157: private LanguagePath() {
158: this .languages = allocateLanguageArray(0);
159: }
160:
161: /**
162: * Get total number of languages in this language path.
163: *
164: * @return >=1 number of languages contained in this language path.
165: */
166: public int size() {
167: return languages.length;
168: }
169:
170: /**
171: * Get language of this language path at the given index.
172: * <br>
173: * Index zero corresponds to the root language.
174: *
175: * @param index >=0 && < {@link #size()}.
176: * @return non-null language at the given index.
177: * @throws IndexOutOfBoundsException in case the index is not within
178: * required bounds.
179: */
180: public Language<?> language(int index) {
181: return languages[index];
182: }
183:
184: /**
185: * Get embedded path of this language path.
186: * <br/>
187: * This method has the same effect like using {@link #get(LanguagePath,Language)}
188: * but this one is usually preferred as it supports more readable code.
189: * <br/>
190: * For example for java scriplet embedded in jsp the prefix would
191: * be a language-path for jsp language and language would be java language.
192: * <br/>
193: * By using this method language paths with arbitrary depth can be created.
194: *
195: * @param language non-null language.
196: * @return non-null language path.
197: */
198: public LanguagePath embedded(Language<?> language) {
199: if (language == null) {
200: throw new IllegalArgumentException(
201: "language cannot be null");
202: }
203: // Attempt to retrieve from the cache first
204: synchronized (languages) {
205: initLanguage2path();
206: Reference<LanguagePath> lpRef = language2path.get(language);
207: LanguagePath lp;
208: if (lpRef == null || (lp = lpRef.get()) == null) {
209: // Construct the LanguagePath
210: lp = new LanguagePath(this , language);
211: language2path.put(language,
212: new SoftReference<LanguagePath>(lp));
213: }
214:
215: return lp;
216: }
217: }
218:
219: /**
220: * Get language path corresponding to the suffix language path embedded
221: * in this path.
222: *
223: * @param suffix non-null suffix to be added to this path.
224: * @return non-null language path consisting of this path with the
225: * suffix added to the end.
226: */
227: public LanguagePath embedded(LanguagePath suffix) {
228: if (suffix == null) {
229: throw new IllegalArgumentException("suffix cannot be null");
230: }
231: // Attempt to retrieve from the cache first
232: synchronized (languages) {
233: initLanguage2path();
234: Reference<LanguagePath> lpRef = language2path.get(suffix);
235: LanguagePath lp;
236: if (lpRef == null || (lp = lpRef.get()) == null) {
237: // Construct the LanguagePath
238: lp = this ;
239: for (int i = 0; i < suffix.size(); i++) {
240: lp = lp.embedded(suffix.language(i));
241: }
242: language2path.put(suffix,
243: new SoftReference<LanguagePath>(lp));
244: }
245:
246: return lp;
247: }
248: }
249:
250: /**
251: * Returns language path consisting of <code><0, size() - 1></code>
252: * languages (i.e. the inner language is cut out).
253: * <code>
254: * If {@link #size()} == 1 then <code>null</code> is returned.
255: */
256: public LanguagePath parent() {
257: return parent;
258: }
259:
260: /**
261: * Return the top-level language of this language path.
262: * <br/>
263: * It's equivalent to <code>language(0)</code>.
264: *
265: * @see #language(int)
266: */
267: public Language<?> topLanguage() {
268: return language(0);
269: }
270:
271: /**
272: * Return the most inner language of this path.
273: * <br/>
274: * It's equivalent to <code>language(size() - 1)</code>.
275: *
276: * @see #language(int)
277: */
278: public Language<?> innerLanguage() {
279: return language(size() - 1);
280: }
281:
282: /**
283: * Check whether this language path ends with the given language path.
284: * <br/>
285: * This may be useful for checking whether a given input contains certain language
286: * (or language path) that may possibly be embedded somewhere in the input.
287: *
288: * @param languagePath non-null language path to be checked.
289: * @return true if this language path contains the given language path
290: * at its end (applies for <code>this</code> as well).
291: */
292: public boolean endsWith(LanguagePath languagePath) {
293: if (languagePath == this || languagePath == EMPTY)
294: return true;
295: int lpSize = languagePath.size();
296: if (lpSize <= size()) {
297: for (int i = 1; i <= lpSize; i++) {
298: if (language(size() - i) != languagePath
299: .language(lpSize - i))
300: return false;
301: }
302: return true;
303: }
304: return false;
305: }
306:
307: /**
308: * Gets the path starting at the given index and ending after
309: * the last language contained in this path.
310: *
311: * @see #subPath(int, int)
312: */
313: public LanguagePath subPath(int startIndex) {
314: return subPath(startIndex, size());
315: }
316:
317: /**
318: * Gets the path starting at the given index and ending after
319: * the last language contained in this path.
320: *
321: * @param startIndex >=0 starting index of the requested path in this path.
322: * @param endIndex >startIndex index after the last item
323: * of the requested path.
324: * @return non-null language path containing items between startIndex and endIndex.
325: */
326: public LanguagePath subPath(int startIndex, int endIndex) {
327: if (startIndex < 0) {
328: throw new IndexOutOfBoundsException("startIndex="
329: + startIndex + " < 0"); // NOI18N
330: }
331: if (endIndex > size()) {
332: throw new IndexOutOfBoundsException("endIndex=" + endIndex
333: + " > size()=" + size());
334: }
335: if (startIndex >= endIndex) {
336: throw new IndexOutOfBoundsException("startIndex="
337: + startIndex + " >= endIndex=" + endIndex);
338: }
339: if (startIndex == 0 && endIndex == size()) {
340: return this ;
341: }
342: LanguagePath lp = LanguagePath.get(language(startIndex++));
343: while (startIndex < endIndex) {
344: lp = LanguagePath.get(lp, language(startIndex++));
345: }
346: return lp;
347: }
348:
349: /**
350: * Gets the mime path equivalent of this language path. The mime path is
351: * a concatenation of mime types of all the languages in this language path.
352: * The mime types are separated by the '/' character.
353: *
354: * <p>
355: * For example the language path of the java language embedded in the
356: * JSP language will return 'text/x-jsp/text/x-java' when this method is called.
357: * </p>
358: *
359: * <p>
360: * The returned string path can be used in MimeLookup's operation
361: * to obtain a corresponding MimePath object by using
362: * <code>MimePath.parse(returned-mime-path-string)</code>.
363: * </p>
364: *
365: * @return The mime path string.
366: * @see org.netbeans.spi.lexer.LanguageHierarchy#mimeType()
367: */
368: public String mimePath() {
369: synchronized (languages) {
370: if (mimePath == null) {
371: StringBuilder sb = new StringBuilder(
372: 15 * languages.length);
373: for (Language<?> language : languages) {
374: if (sb.length() > 0) {
375: sb.append('/');
376: }
377: sb.append(language.mimeType());
378: }
379: // Intern the mimePath for faster operation of MimePath.parse()
380: mimePath = sb.toString().intern();
381: }
382: return mimePath;
383: }
384: }
385:
386: private void initLanguage2path() {
387: if (language2path == null) {
388: language2path = new WeakHashMap<Object, Reference<LanguagePath>>();
389: }
390: }
391:
392: private Language<?>[] allocateLanguageArray(int length) {
393: return (Language<?>[]) (new Language[length]);
394: }
395:
396: public String toString() {
397: StringBuilder sb = new StringBuilder();
398: sb.append("LanguagePath: size=");
399: sb.append(size());
400: sb.append('\n');
401: for (int i = 0; i < size(); i++) {
402: sb.append('[').append(i).append("]: "); // NOI18N
403: sb.append(language(i)).append('\n');
404: }
405: return sb.toString();
406: }
407:
408: }
|