KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > api > editor > mimelookup > MimePath


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.api.editor.mimelookup;
21
22 import java.lang.ref.Reference JavaDoc;
23 import java.lang.ref.SoftReference JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.regex.Matcher JavaDoc;
28 import java.util.regex.Pattern JavaDoc;
29 import org.netbeans.modules.editor.mimelookup.MimePathLookup;
30
31 /**
32  * The mime path is a concatenation of one or more mime types. The purpose of
33  * a mime path is to describe the fact that a document of a certain mime type
34  * can contain fragments of another document with a different mime type. The fragment
35  * and its mime type is refered to as an embedded document and an embedded mime
36  * type respectively.
37  *
38  * <p>In order to fully understand the scale of the problem the mime path
39  * is trying to describe you should consider two things. First a document can
40  * contain several different embedded fragments each of a different
41  * mime type. Second, each embeded fragment itself can possibly contain one or
42  * more other embedded fragments and this nesting can in theory go indefinitely
43  * deep.
44  *
45  * <p>In reality the nesting probably will not be very deep. As an example of a
46  * document containing an embedded fragment of another document of the different
47  * mime type you could imagine a JSP page containing a Java scriplet. The main
48  * document is the JSP page of the 'text/x-jsp' mime type, which includes a fragment
49  * of Java source code of the 'text/x-java' mime type.
50  *
51  * <p>The mime path comes handy when we want to distinguish between the ordinary
52  * 'text/x-java' mime type and the 'text/x-java' mime type embedded in the JSP
53  * page, because both of those 'text/x-java' mime types will have a different
54  * mime path. The ordinary 'text/x-java' mime type has a mime path consisting
55  * of just one mime type - 'text/x-java'. The 'text/x-java' mime type embeded in
56  * the JSP page, however, has a mime path comprised from two mime types
57  * 'text/x-jsp' and 'text/x-java'. The order of mime types in a mime path is
58  * obviously very important, because it describes how the mime types are embedded.
59  *
60  * <p>The mime path can be represented as a <code>String</code> simply by
61  * concatenating all its mime types separated by the '/' character. Since
62  * mime types always contain one and only one '/' character it is clear which
63  * '/' character belongs to a mime type and which is the mime path separator.
64  *
65  * <p>In the above example the mime path of the 'text/x-java' mime type embedded
66  * in the 'text/x-jsp' mime type can be represented as 'text/x-jsp/text/x-java'.
67  *
68  * <p class="nonnormative">For some languages it is not uncommon to allow embedding of itself. For example
69  * in Ruby it is allowed to use Ruby code within strings and Ruby will
70  * evaluate this code when evaluating the value of the strings. Depending on the
71  * implementation of a lexer there can be tokens with <code>MimePath</code> that
72  * contains several consecutive same mime types.
73  *
74  * <p>The format of a valid mime type string is described in
75  * <a HREF="http://tools.ietf.org/html/rfc4288#section-4.2">RFC 4288</a>.
76  * <code>MimePath</code> performs internall checks according to this specification.
77  *
78  * <p><b>Identity:</b> By definition two <code>MimePath</code> instances are equal
79  * if they represent the same string mime path. The implementation guarantees
80  * that by caching and reusing instances of the <code>MimePath</code> that it
81  * creates. The <code>MimePath</code> instances can be used as keys in maps.
82  *
83  * <p><b>Lifecycle:</b> Although the instances of <code>MimePath</code> are
84  * internally cached and should survive for certain time without being referenced
85  * from outside of the MimePath API, clients are strongly encouraged to hold
86  * a reference to the <code>MimePath</code> they obtained throughout the whole
87  * lifecycle of their component. For example an opened java editor with a document
88  * should keep its instance of the 'text/x-java' <code>MimePath</code> for the
89  * whole time the editor is open.
90  *
91  * @author Miloslav Metelka, Vita Stejskal
92  * @see MimeLookup
93  * @see <a HREF="http://tools.ietf.org/html/rfc4288#section-4.2">RFC 4288</a>
94  */

95 public final class MimePath {
96     
97     /**
98      * The root of all mime paths. The empty mime path does not refer to any
99      * mime type.
100      */

101     public static final MimePath EMPTY = new MimePath();
102
103     /** Internal lock to manage the cache maps. */
104     private static final Object JavaDoc LOCK = new Object JavaDoc();
105
106     /** The List of Recently Used mime paths. */
107     private static final ArrayList JavaDoc<MimePath> LRU = new ArrayList JavaDoc<MimePath>();
108
109     /** The maximum size of the List of Recently Used mime paths.
110     /* package */
static final int MAX_LRU_SIZE = 3;
111
112     private static final String JavaDoc REG_NAME = "[[\\p{Alnum}][!#$&.+\\-^_]]{1,127}"; //NOI18N
113
private static final Pattern JavaDoc MIME_TYPE_PATTERN = Pattern.compile("^" + REG_NAME + "/{1}" + REG_NAME + "$"); //NOI18N
114

115     /**
116      * Gets the mime path for the given mime type. The returned <code>MimePath</code>
117      * will contain exactly one element and it will be the mime type passed in
118      * as the parameter.
119      *
120      * @param mimeType The mime type to get the mime path for. If <code>null</code>
121      * or empty string is passed in the <code>EMPTY</code> mime path will be
122      * returned.
123      *
124      * @return The <code>MimePath</code> for the given mime type or
125      * <code>MimePath.EMPTY</code> if the mime type is <code>null</code> or empty
126      * string.
127      */

128     public static MimePath get(String JavaDoc mimeType) {
129         if (mimeType == null || mimeType.length() == 0){
130             return EMPTY;
131         } else {
132             return get(EMPTY, mimeType);
133         }
134     }
135     
136     /**
137      * Gets the mime path corresponding to a mime type embedded in another
138      * mime type. The embedding mime type is described in form of a mime path
139      * passed in as the <code>prefix</code> parameter.
140      *
141      * <p>For example for a java scriplet embedded in a jsp page the <code>prefix</code> would
142      * be the mime path 'text/x-jsp' and <code>mimeType</code> would be 'text/x-java'.
143      * The method will return the 'text/x-jsp/text/x-java' mime path.
144      *
145      *
146      * @param prefix The mime path determining the mime type that embedds the mime
147      * type passed in in the second parameter. It can be {@link #EMPTY} in which
148      * case the call will be equivalent to calling <code>get(mimeType)</code> method.
149      * @param mimeType The mime type that is embedded in the mime type determined
150      * by the <code>prefix</code> mime path.
151      *
152      * @return The mime path representing the embedded mime type.
153      */

154     public static MimePath get(MimePath prefix, String JavaDoc mimeType) {
155         return prefix.getEmbedded(mimeType);
156     }
157     
158     /**
159      * Parses a mime path string and returns its <code>MimePath</code> representation.
160      *
161      * <p>The format of a mime path string representation is a string of mime
162      * type components comprising the mime path separated by the '/' character.
163      * For example a mime path representing the 'text/x-java' mime type embedded
164      * in the 'text/x-jsp' mime type can be represented as the following string -
165      * 'text/x-jsp/text/x-java'.
166      *
167      * <p>The mime path string can be an empty string, which represents the
168      * {@link #EMPTY} mime path. By definition all valid mime paths except of
169      * the empty one have to contain odd number of '/' characters.
170      *
171      * @param path The mime path string representation.
172      *
173      * @return non-null mime-path corresponding to the given string path.
174      */

175     public static MimePath parse(String JavaDoc path) {
176         return parseImpl(path);
177     }
178     
179     /**
180      * Array of component mime paths for this mime path.
181      * <br>
182      * The last member of the array is <code>this</code>.
183      */

184     private final MimePath[] mimePaths;
185     
186     /**
187      * Complete string path of this mimePath.
188      */

189     private final String JavaDoc path;
190
191     /**
192      * Mime type string represented by this mime path component.
193      */

194     private final String JavaDoc mimeType;
195     
196     /**
197      * Mapping of embedded mimeType to a weak reference to mimePath.
198      */

199     private Map JavaDoc<String JavaDoc, SoftReference JavaDoc<MimePath>> mimeType2mimePathRef;
200
201     /**
202      * The lookup with objects registered for this mime path.
203      */

204     private MimePathLookup lookup;
205     
206     /**
207      * Synchronization lock for creation of the mime path lookup.
208      */

209     private final String JavaDoc LOOKUP_LOCK = new String JavaDoc("MimePath.LOOKUP_LOCK"); //NOI18N
210

211     private MimePath(MimePath prefix, String JavaDoc mimeType) {
212         int prefixSize = prefix.size();
213         this.mimePaths = new MimePath[prefixSize + 1];
214         System.arraycopy(prefix.mimePaths, 0, this.mimePaths, 0, prefixSize);
215         this.mimePaths[prefixSize] = this;
216         String JavaDoc prefixPath = prefix.path;
217         this.path = (prefixPath != null && prefixPath.length() > 0 ) ?
218             (prefixPath + '/' + mimeType).intern() : //NOI18N
219
mimeType.intern();
220         this.mimeType = mimeType;
221     }
222     
223     /** Build EMPTY mimePath */
224     private MimePath() {
225         this.mimePaths = new MimePath[0];
226         this.path = ""; //NOI18N
227
this.mimeType = ""; //NOI18N
228
}
229     
230     /**
231      * Get string path represented by this mime-path.
232      * <br/>
233      * For example <code>"text/x-jsp/text/x-java"</code>.
234      *
235      * @return non-null string path.
236      */

237     public String JavaDoc getPath() {
238         return path;
239     }
240     
241     /**
242      * Get total number of mime-types in the mime-path.
243      * <br>
244      * {@link #EMPTY} mime-path has zero size.
245      * <br>
246      * <code>"text/x-jsp/text/x-java"</code> has size 2.
247      *
248      * @return >=0 number of mime-types contained in this mime-path.
249      */

250     public int size() {
251         return mimePaths.length;
252     }
253     
254     /**
255      * Get mime type of this mime-path at the given index.
256      * <br>
257      * Index zero corresponds to the root mime-type.
258      * <br>
259      * For <code>"text/x-jsp/text/x-java"</code>
260      * <code>getMimeType(0)</code> returns <code>"text/x-jsp"</code>
261      * and <code>getMimeType(1)</code> returns <code>"text/x-java"</code>.
262      *
263      * @param index >=0 && < {@link #size()}.
264      * @return non-null mime-type at the given index.
265      * @throws IndexOutOfBoundsException in case the index is not within
266      * required bounds.
267      */

268     public String JavaDoc getMimeType(int index) {
269         return mimePaths[index].mimeType;
270     }
271     
272     /**
273      * Return prefix mime-path with the given number of mime-type components
274      * ranging from zero till the size of this mime-path.
275      *
276      * @param size >=0 && <= {@link #size()}.
277      * <br>
278      * For zero size the {@link #EMPTY} will be returned.
279      * <br>
280      * For <code>size()</code> <code>this</code> will be returned.
281      * @return non-null mime-type of the given size.
282      * @throws IndexOutOfBoundsException in case the index is not within
283      * required bounds.
284      */

285     public MimePath getPrefix(int size) {
286         return (size == 0)
287             ? EMPTY
288             : mimePaths[size - 1];
289     }
290
291     private MimePath getEmbedded(String JavaDoc mimeType) {
292         // Attempt to retrieve from the cache first
293
// It has also an advantage that the mime-type does not need
294
// to be tested for correctness
295
synchronized (LOCK) {
296             if (mimeType2mimePathRef == null) {
297                 mimeType2mimePathRef = new HashMap JavaDoc<String JavaDoc, SoftReference JavaDoc<MimePath>>();
298             }
299             Reference JavaDoc mpRef = mimeType2mimePathRef.get(mimeType);
300             MimePath mimePath;
301             if (mpRef == null || (mimePath = (MimePath)mpRef.get()) == null) {
302                 // Check mimeType correctness
303
Matcher JavaDoc m = MIME_TYPE_PATTERN.matcher(mimeType);
304                 if (!m.matches()) {
305                     throw new IllegalArgumentException JavaDoc("Invalid mimeType=\"" + mimeType + "\""); //NOI18N
306
}
307                 
308                 // Construct the mimePath
309
mimePath = new MimePath(this, mimeType);
310                 mimeType2mimePathRef.put(mimeType, new SoftReference JavaDoc<MimePath>(mimePath));
311
312                 // Hard reference the last few MimePaths created.
313
LRU.add(0, mimePath);
314                 if (LRU.size() > MAX_LRU_SIZE) {
315                     LRU.remove(LRU.size() - 1);
316                 }
317             }
318         
319             return mimePath;
320         }
321     }
322     
323     private static MimePath parseImpl(String JavaDoc path) {
324         if (path == null) {
325             throw new IllegalArgumentException JavaDoc("path cannot be null"); // NOI18N
326
}
327         MimePath mimePath = EMPTY;
328         int pathLen = path.length();
329         int startIndex = 0;
330         while (true) {
331             int index = startIndex;
332             int slashIndex = -1;
333             // Search for first slash
334
while (index < pathLen) {
335                 if (path.charAt(index) == '/') { //NOI18N
336
slashIndex = index;
337                     break; // first slash found
338
}
339                 index++;
340             }
341             if (slashIndex == -1) { // no slash found
342
if (index != startIndex) {
343                     throw new IllegalArgumentException JavaDoc("mimeType '" // NOI18N
344
+ path.substring(startIndex) + "' does not contain '/'."); // NOI18N
345
}
346                 // Empty mimeType
347
break;
348             }
349             index++; // move after slash
350
while (index < pathLen) {
351                 if (path.charAt(index) == '/') { //NOI18N
352
if (index == slashIndex + 1) { // empty second part of mimeType
353
throw new IllegalArgumentException JavaDoc("Two successive slashes in '" // NOI18N
354
+ path.substring(startIndex) + "'"); // NOI18N
355
}
356                     break;
357                 }
358                 index++;
359             }
360             if (index == slashIndex + 1) { // nothing after first slash
361
throw new IllegalArgumentException JavaDoc("Empty string after '/' in '" // NOI18N
362
+ path.substring(startIndex) + "'"); // NOI18N
363
}
364             
365             // Mime type found
366
String JavaDoc mimeType = path.substring(startIndex, index);
367             mimePath = get(mimePath, mimeType);
368             
369             startIndex = index + 1; // after slash or after end of path
370
}
371         return mimePath;
372     }
373
374     /**
375      * Gets the <code>MimePathLookup</code> for the given mime path. The lookups
376      * are cached and reused.
377      *
378      * @param The mime path to get the lookup for.
379      *
380      * @return The mime path specific lookup.
381      */

382     /* package */ MimePathLookup getLookup() {
383         synchronized (LOOKUP_LOCK) {
384             if (lookup == null) {
385                 lookup = new MimePathLookup(this);
386             }
387             return lookup;
388         }
389     }
390     
391 }
392
Popular Tags