KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > internal > content > ContentTypeCatalog


1 /*******************************************************************************
2  * Copyright (c) 2004, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.core.internal.content;
12
13 import java.io.*;
14 import java.util.*;
15 import org.eclipse.core.runtime.*;
16 import org.eclipse.core.runtime.content.*;
17 import org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy;
18 import org.eclipse.core.runtime.preferences.IScopeContext;
19
20 public final class ContentTypeCatalog {
21     private static final IContentType[] NO_CONTENT_TYPES = new IContentType[0];
22
23     private Map allChildren = new HashMap();
24     private Map contentTypes = new HashMap();
25
26     private Map fileExtensions = new HashMap();
27
28     private Map fileNames = new HashMap();
29
30     private int generation;
31
32     private ContentTypeManager manager;
33
34     /**
35      * A sorting policy where the more generic content type wins. Lexicographical comparison is done
36      * as a last resort when all other criteria fail.
37      */

38     private Comparator policyConstantGeneralIsBetter = new Comparator() {
39         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
40             ContentType type1 = (ContentType) o1;
41             ContentType type2 = (ContentType) o2;
42             // first criteria: depth - the lower, the better
43
int depthCriteria = type1.getDepth() - type2.getDepth();
44             if (depthCriteria != 0)
45                 return depthCriteria;
46             // second criteria: priority - the higher, the better
47
int priorityCriteria = type1.getPriority() - type2.getPriority();
48             if (priorityCriteria != 0)
49                 return -priorityCriteria;
50             // they have same depth and priority - choose one arbitrarily (stability is important)
51
return type1.getId().compareTo(type2.getId());
52         }
53     };
54
55     /**
56      * A sorting policy where the more specific content type wins. Lexicographical comparison is done
57      * as a last resort when all other criteria fail.
58      */

59     private Comparator policyConstantSpecificIsBetter = new Comparator() {
60         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
61             ContentType type1 = (ContentType) o1;
62             ContentType type2 = (ContentType) o2;
63             // first criteria: depth - the higher, the better
64
int depthCriteria = type1.getDepth() - type2.getDepth();
65             if (depthCriteria != 0)
66                 return -depthCriteria;
67             // second criteria: priority - the higher, the better
68
int priorityCriteria = type1.getPriority() - type2.getPriority();
69             if (priorityCriteria != 0)
70                 return -priorityCriteria;
71             // they have same depth and priority - choose one arbitrarily (stability is important)
72
return type1.getId().compareTo(type2.getId());
73         }
74     };
75
76     /**
77      * A sorting policy where the more general content type wins.
78      */

79     private Comparator policyGeneralIsBetter = new Comparator() {
80         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
81             ContentType type1 = (ContentType) o1;
82             ContentType type2 = (ContentType) o2;
83             // first criteria: depth - the lower, the better
84
int depthCriteria = type1.getDepth() - type2.getDepth();
85             if (depthCriteria != 0)
86                 return depthCriteria;
87             // second criteria: priority - the higher, the better
88
int priorityCriteria = type1.getPriority() - type2.getPriority();
89             if (priorityCriteria != 0)
90                 return -priorityCriteria;
91             return 0;
92         }
93     };
94
95     /**
96      * A sorting policy where content types are sorted by id.
97      */

98     private Comparator policyLexicographical = new Comparator() {
99         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
100             ContentType type1 = (ContentType) o1;
101             ContentType type2 = (ContentType) o2;
102             return type1.getId().compareTo(type2.getId());
103         }
104     };
105     /**
106      * A sorting policy where the more specific content type wins.
107      */

108     private Comparator policySpecificIsBetter = new Comparator() {
109         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
110             ContentType type1 = (ContentType) o1;
111             ContentType type2 = (ContentType) o2;
112             // first criteria: depth - the higher, the better
113
int depthCriteria = type1.getDepth() - type2.getDepth();
114             if (depthCriteria != 0)
115                 return -depthCriteria;
116             // second criteria: priority - the higher, the better
117
int priorityCriteria = type1.getPriority() - type2.getPriority();
118             if (priorityCriteria != 0)
119                 return -priorityCriteria;
120             return 0;
121         }
122     };
123
124     private static IContentType[] concat(IContentType[][] types) {
125         if (types[0].length == 0)
126             return types[1];
127         if (types[1].length == 0)
128             return types[0];
129         IContentType[] result = new IContentType[types[0].length + types[1].length];
130         System.arraycopy(types[0], 0, result, 0, types[0].length);
131         System.arraycopy(types[1], 0, result, types[0].length, types[1].length);
132         return result;
133     }
134
135     public ContentTypeCatalog(ContentTypeManager manager, int generation) {
136         this.manager = manager;
137         this.generation = generation;
138     }
139
140     void addContentType(IContentType contentType) {
141         contentTypes.put(contentType.getId(), contentType);
142     }
143
144     /**
145      * Applies a client-provided selection policy.
146      */

147     private IContentType[] applyPolicy(final IContentTypeManager.ISelectionPolicy policy, final IContentType[] candidates, final boolean fileName, final boolean contents) {
148         final IContentType[][] result = new IContentType[][] {candidates};
149         SafeRunner.run(new ISafeRunnable() {
150             public void handleException(Throwable JavaDoc exception) {
151                 // already logged in SafeRunner#run()
152
// default result is the original array
153
// nothing to be done
154
}
155
156             public void run() throws Exception JavaDoc {
157                 result[0] = policy.select(candidates, fileName, contents);
158             }
159         });
160         return result[0];
161     }
162
163     void associate(ContentType contentType) {
164         String JavaDoc[] builtInFileNames = contentType.getFileSpecs(IContentType.IGNORE_USER_DEFINED | IContentType.FILE_NAME_SPEC);
165         for (int i = 0; i < builtInFileNames.length; i++)
166             associate(contentType, builtInFileNames[i], IContentType.FILE_NAME_SPEC);
167         String JavaDoc[] builtInFileExtensions = contentType.getFileSpecs(IContentType.IGNORE_USER_DEFINED | IContentType.FILE_EXTENSION_SPEC);
168         for (int i = 0; i < builtInFileExtensions.length; i++)
169             associate(contentType, builtInFileExtensions[i], IContentType.FILE_EXTENSION_SPEC);
170     }
171
172     void associate(ContentType contentType, String JavaDoc text, int type) {
173         Map fileSpecMap = ((type & IContentType.FILE_NAME_SPEC) != 0) ? fileNames : fileExtensions;
174         String JavaDoc mappingKey = FileSpec.getMappingKeyFor(text);
175         Set existing = (Set) fileSpecMap.get(mappingKey);
176         if (existing == null)
177             fileSpecMap.put(mappingKey, existing = new HashSet());
178         existing.add(contentType);
179     }
180
181     private int collectMatchingByContents(int valid, IContentType[] subset, List destination, ILazySource contents) throws IOException {
182         for (int i = 0; i < subset.length; i++) {
183             ContentType current = (ContentType) subset[i];
184             IContentDescriber describer = current.getDescriber();
185             int status = IContentDescriber.INDETERMINATE;
186             if (describer != null) {
187                 if (contents.isText() && !(describer instanceof ITextContentDescriber))
188                     // for text streams we skip content types that do not provide text-based content describers
189
continue;
190                 status = current.describe(describer, contents, null);
191                 if (status == IContentDescriber.INVALID)
192                     continue;
193             }
194             if (status == IContentDescriber.VALID)
195                 destination.add(valid++, current);
196             else
197                 destination.add(current);
198         }
199         return valid;
200     }
201
202     void dissociate(ContentType contentType, String JavaDoc text, int type) {
203         Map fileSpecMap = ((type & IContentType.FILE_NAME_SPEC) != 0) ? fileNames : fileExtensions;
204         String JavaDoc mappingKey = FileSpec.getMappingKeyFor(text);
205         Set existing = (Set) fileSpecMap.get(mappingKey);
206         if (existing == null)
207             return;
208         existing.remove(contentType);
209     }
210
211     /**
212      * A content type will be valid if:
213      * <ol>
214      * <li>it does not designate a base type, or</li>
215      * <li>it designates a base type that exists and is valid</li>
216      * </ol>
217      * <p>And</p>:
218      * <ol>
219      * <li>it does not designate an alias type, or</li>
220      * <li>it designates an alias type that does not exist, or</li>
221      * <li>it designates an alias type that exists and is valid</li>
222      * </ol>
223      */

224     private boolean ensureValid(ContentType type) {
225         if (type.getValidation() != ContentType.STATUS_UNKNOWN)
226             // already processed
227
return type.isValid();
228         // set this type temporarily as invalid to prevent cycles
229
// all types in a cycle would remain as invalid
230
type.setValidation(ContentType.STATUS_INVALID);
231         if (type.isAlias())
232             // it is an alias, leave as invalid
233
return false;
234         // check base type
235
ContentType baseType = null;
236         if (type.getBaseTypeId() != null) {
237             baseType = (ContentType) contentTypes.get(type.getBaseTypeId());
238             if (baseType == null)
239                 // invalid: specified base type is not known
240
return false;
241             // base type exists, ensure it is valid
242
baseType = baseType.getAliasTarget(true);
243             ensureValid(baseType);
244             if (baseType.getValidation() != ContentType.STATUS_VALID)
245                 // invalid: base type was invalid
246
return false;
247         }
248         // valid: all conditions satisfied
249
type.setValidation(ContentType.STATUS_VALID);
250         type.setBaseType(baseType);
251         return true;
252     }
253
254     IContentType[] findContentTypesFor(ContentTypeMatcher matcher, InputStream contents, String JavaDoc fileName) throws IOException {
255         final ILazySource buffer = ContentTypeManager.readBuffer(contents);
256         IContentType[] selected = internalFindContentTypesFor(matcher, buffer, fileName, true);
257         // give the policy a chance to change the results
258
ISelectionPolicy policy = matcher.getPolicy();
259         if (policy != null)
260             selected = applyPolicy(policy, selected, fileName != null, true);
261         return selected;
262     }
263
264     IContentType[] findContentTypesFor(ContentTypeMatcher matcher, final String JavaDoc fileName) {
265         IContentType[] selected = concat(internalFindContentTypesFor(matcher, fileName, policyConstantGeneralIsBetter));
266         // give the policy a chance to change the results
267
ISelectionPolicy policy = matcher.getPolicy();
268         if (policy != null)
269             selected = applyPolicy(policy, selected, true, false);
270         return selected;
271     }
272
273     public IContentType[] getAllContentTypes() {
274         List result = new ArrayList(contentTypes.size());
275         for (Iterator i = contentTypes.values().iterator(); i.hasNext();) {
276             ContentType type = (ContentType) i.next();
277             if (type.isValid() && !type.isAlias())
278                 result.add(type);
279         }
280         return (IContentType[]) result.toArray(new IContentType[result.size()]);
281     }
282
283     public ContentType[] getChildren(ContentType parent) {
284         ContentType[] children = (ContentType[]) allChildren.get(parent);
285         if (children != null)
286             return children;
287         List result = new ArrayList(5);
288         for (Iterator i = this.contentTypes.values().iterator(); i.hasNext();) {
289             ContentType next = (ContentType) i.next();
290             if (next.getBaseType() == parent)
291                 result.add(next);
292         }
293         children = (ContentType[]) result.toArray(new ContentType[result.size()]);
294         allChildren.put(parent, children);
295         return children;
296     }
297
298     public ContentType getContentType(String JavaDoc contentTypeIdentifier) {
299         ContentType type = internalGetContentType(contentTypeIdentifier);
300         return (type != null && type.isValid() && !type.isAlias()) ? type : null;
301     }
302
303     private IContentDescription getDescriptionFor(ContentTypeMatcher matcher, ILazySource contents, String JavaDoc fileName, QualifiedName[] options) throws IOException {
304         IContentType[] selected = internalFindContentTypesFor(matcher, contents, fileName, false);
305         if (selected.length == 0)
306             return null;
307         // give the policy a chance to change the results
308
ISelectionPolicy policy = matcher.getPolicy();
309         if (policy != null) {
310             selected = applyPolicy(policy, selected, fileName != null, true);
311             if (selected.length == 0)
312                 return null;
313         }
314         return matcher.getSpecificDescription(((ContentType) selected[0]).internalGetDescriptionFor(contents, options));
315     }
316
317     public IContentDescription getDescriptionFor(ContentTypeMatcher matcher, InputStream contents, String JavaDoc fileName, QualifiedName[] options) throws IOException {
318         return getDescriptionFor(matcher, ContentTypeManager.readBuffer(contents), fileName, options);
319     }
320
321     public IContentDescription getDescriptionFor(ContentTypeMatcher matcher, Reader contents, String JavaDoc fileName, QualifiedName[] options) throws IOException {
322         return getDescriptionFor(matcher, ContentTypeManager.readBuffer(contents), fileName, options);
323     }
324
325     public int getGeneration() {
326         return generation;
327     }
328
329     public ContentTypeManager getManager() {
330         return manager;
331     }
332
333     public boolean internalAccept(ContentTypeVisitor visitor, ContentType root) {
334         if (!root.isValid() || root.isAlias())
335             return true;
336         int result = visitor.visit(root);
337         switch (result) {
338             // stop traversing the tree
339
case ContentTypeVisitor.STOP :
340                 return false;
341             // stop traversing this subtree
342
case ContentTypeVisitor.RETURN :
343                 return true;
344         }
345         ContentType[] children = getChildren(root);
346         if (children == null)
347             // this content type has no sub-types - keep traversing the tree
348
return true;
349         for (int i = 0; i < children.length; i++)
350             if (!internalAccept(visitor, children[i]))
351                 // stop the traversal
352
return false;
353         return true;
354     }
355
356     public IContentType[] internalFindContentTypesFor(ILazySource buffer, IContentType[][] subset, Comparator validPolicy, Comparator indeterminatePolicy) throws IOException {
357         final List appropriate = new ArrayList(5);
358         final int validFullName = collectMatchingByContents(0, subset[0], appropriate, buffer);
359         final int appropriateFullName = appropriate.size();
360         final int validExtension = collectMatchingByContents(validFullName, subset[1], appropriate, buffer) - validFullName;
361         final int appropriateExtension = appropriate.size() - appropriateFullName;
362         IContentType[] result = (IContentType[]) appropriate.toArray(new IContentType[appropriate.size()]);
363         if (validFullName > 1)
364             Arrays.sort(result, 0, validFullName, validPolicy);
365         if (validExtension > 1)
366             Arrays.sort(result, validFullName, validFullName + validExtension, validPolicy);
367         if (appropriateFullName - validFullName > 1)
368             Arrays.sort(result, validFullName + validExtension, appropriateFullName + validExtension, indeterminatePolicy);
369         if (appropriateExtension - validExtension > 1)
370             Arrays.sort(result, appropriateFullName + validExtension, appropriate.size(), indeterminatePolicy);
371         return result;
372     }
373
374     private IContentType[] internalFindContentTypesFor(ContentTypeMatcher matcher, ILazySource buffer, String JavaDoc fileName, boolean forceValidation) throws IOException {
375         final IContentType[][] subset;
376         final Comparator validPolicy;
377         Comparator indeterminatePolicy;
378         if (fileName == null) {
379             // we only have a single array, by need to provide a two-dimensional, 2-element array
380
subset = new IContentType[][] {getAllContentTypes(), NO_CONTENT_TYPES};
381             indeterminatePolicy = policyConstantGeneralIsBetter;
382             validPolicy = policyConstantSpecificIsBetter;
383         } else {
384             subset = internalFindContentTypesFor(matcher, fileName, policyLexicographical);
385             indeterminatePolicy = policyGeneralIsBetter;
386             validPolicy = policySpecificIsBetter;
387         }
388         int total = subset[0].length + subset[1].length;
389         if (total == 0)
390             // don't do further work if subset is empty
391
return NO_CONTENT_TYPES;
392         if (!forceValidation && total == 1) {
393             // do not do validation if not forced and only one was found (caller will validate later)
394
IContentType[] found = subset[0].length == 1 ? subset[0] : subset[1];
395             // bug 100032 - ignore binary content type if contents are text
396
if (!buffer.isText())
397                 // binary buffer, caller can call the describer with no risk
398
return found;
399             // text buffer, need to check describer
400
IContentDescriber describer = ((ContentType) found[0]).getDescriber();
401             if (describer == null || describer instanceof ITextContentDescriber)
402                 // no describer or text describer, that is fine
403
return found;
404             // only eligible content type is binary and contents are text, ignore it
405
return NO_CONTENT_TYPES;
406         }
407         return internalFindContentTypesFor(buffer, subset, validPolicy, indeterminatePolicy);
408     }
409
410     /**
411      * This is the implementation for file name based content type matching.
412      *
413      * @return all matching content types in the preferred order
414      * @see IContentTypeManager#findContentTypesFor(String)
415      */

416     public IContentType[][] internalFindContentTypesFor(ContentTypeMatcher matcher, final String JavaDoc fileName, Comparator sortingPolicy) {
417         IScopeContext context = matcher.getContext();
418         IContentType[][] result = {NO_CONTENT_TYPES, NO_CONTENT_TYPES};
419
420         final Set allByFileName;
421
422         if (context.equals(manager.getContext()))
423             allByFileName = getDirectlyAssociated(fileName, IContentTypeSettings.FILE_NAME_SPEC);
424         else {
425             allByFileName = new HashSet(getDirectlyAssociated(fileName, IContentTypeSettings.FILE_NAME_SPEC | IContentType.IGNORE_USER_DEFINED));
426             allByFileName.addAll(matcher.getDirectlyAssociated(this, fileName, IContentTypeSettings.FILE_NAME_SPEC));
427         }
428         Set selectedByName = selectMatchingByName(context, allByFileName, Collections.EMPTY_SET, fileName, IContentType.FILE_NAME_SPEC);
429         result[0] = (IContentType[]) selectedByName.toArray(new IContentType[selectedByName.size()]);
430         final String JavaDoc fileExtension = ContentTypeManager.getFileExtension(fileName);
431         if (fileExtension != null) {
432             final Set allByFileExtension;
433             if (context.equals(manager.getContext()))
434                 allByFileExtension = getDirectlyAssociated(fileExtension, IContentTypeSettings.FILE_EXTENSION_SPEC);
435             else {
436                 allByFileExtension = new HashSet(getDirectlyAssociated(fileExtension, IContentTypeSettings.FILE_EXTENSION_SPEC | IContentType.IGNORE_USER_DEFINED));
437                 allByFileExtension.addAll(matcher.getDirectlyAssociated(this, fileExtension, IContentTypeSettings.FILE_EXTENSION_SPEC));
438             }
439             Set selectedByExtension = selectMatchingByName(context, allByFileExtension, selectedByName, fileExtension, IContentType.FILE_EXTENSION_SPEC);
440             if (!selectedByExtension.isEmpty())
441                 result[1] = (IContentType[]) selectedByExtension.toArray(new IContentType[selectedByExtension.size()]);
442         }
443         if (result[0].length > 1)
444             Arrays.sort(result[0], sortingPolicy);
445         if (result[1].length > 1)
446             Arrays.sort(result[1], sortingPolicy);
447         return result;
448     }
449
450     /**
451      * Returns content types directly associated with the given file spec.
452      *
453      * @param text a file name or extension
454      * @param typeMask a bit-wise or of the following flags:
455      * <ul>
456      * <li>IContentType.FILE_NAME, </li>
457      * <li>IContentType.FILE_EXTENSION, </li>
458      * <li>IContentType.IGNORE_PRE_DEFINED, </li>
459      * <li>IContentType.IGNORE_USER_DEFINED</li>
460      * </ul>
461      * @return a set of content types
462      */

463     public Set getDirectlyAssociated(String JavaDoc text, int typeMask) {
464         Map associations = (typeMask & IContentTypeSettings.FILE_NAME_SPEC) != 0 ? fileNames : fileExtensions;
465         Set result = null;
466         if ((typeMask & (IContentType.IGNORE_PRE_DEFINED | IContentType.IGNORE_USER_DEFINED)) == 0)
467             // no restrictions, get everything
468
result = (Set) associations.get(FileSpec.getMappingKeyFor(text));
469         else {
470             // only those specs satisfying the type mask should be included
471
Set initialSet = (Set) associations.get(FileSpec.getMappingKeyFor(text));
472             if (initialSet != null && !initialSet.isEmpty()) {
473                 // copy so we can modify
474
result = new HashSet(initialSet);
475                 // invert the last two bits so it is easier to compare
476
typeMask ^= (IContentType.IGNORE_PRE_DEFINED | IContentType.IGNORE_USER_DEFINED);
477                 for (Iterator i = result.iterator(); i.hasNext();) {
478                     ContentType contentType = (ContentType) i.next();
479                     if (!contentType.hasFileSpec(text, typeMask, true))
480                         i.remove();
481                 }
482             }
483         }
484         return result == null ? Collections.EMPTY_SET : result;
485     }
486
487     ContentType internalGetContentType(String JavaDoc contentTypeIdentifier) {
488         return (ContentType) contentTypes.get(contentTypeIdentifier);
489     }
490
491     void makeAliases() {
492         // process all content types marking aliases appropriately
493
for (Iterator i = contentTypes.values().iterator(); i.hasNext();) {
494             ContentType type = (ContentType) i.next();
495             String JavaDoc targetId = type.getAliasTargetId();
496             if (targetId == null)
497                 continue;
498             ContentType target = internalGetContentType(targetId);
499             if (target != null)
500                 type.setAliasTarget(target);
501         }
502     }
503
504     /**
505      * Resolves inter-content type associations (inheritance and aliasing).
506      */

507     protected void organize() {
508         // build the aliasing
509
makeAliases();
510         // do the validation
511
for (Iterator i = contentTypes.values().iterator(); i.hasNext();) {
512             ContentType type = (ContentType) i.next();
513             if (ensureValid(type))
514                 associate(type);
515         }
516         if (ContentTypeManager.DEBUGGING)
517             for (Iterator i = contentTypes.values().iterator(); i.hasNext();) {
518                 ContentType type = (ContentType) i.next();
519                 if (!type.isValid())
520                     ContentMessages.message("Invalid: " + type); //$NON-NLS-1$
521
}
522     }
523
524     /**
525      * Processes all content types in source, adding those matching the given file spec to the
526      * destination collection.
527      */

528     private Set selectMatchingByName(final IScopeContext context, Collection source, final Collection existing, final String JavaDoc fileSpecText, final int fileSpecType) {
529         if (source == null || source.isEmpty())
530             return Collections.EMPTY_SET;
531         final Set destination = new HashSet(5);
532         // process all content types in the given collection
533
for (Iterator i = source.iterator(); i.hasNext();) {
534             final ContentType root = (ContentType) i.next();
535             // From a given content type, check if it matches, and
536
// include any children that match as well.
537
internalAccept(new ContentTypeVisitor() {
538                 public int visit(ContentType type) {
539                     if (type != root && type.hasBuiltInAssociations())
540                         // this content type has built-in associations - visit it later as root
541
return RETURN;
542                     if (type == root && !type.hasFileSpec(context, fileSpecText, fileSpecType))
543                         // it is the root and does not match the file name - do not add it nor look into its children
544
return RETURN;
545                     // either the content type is the root and matches the file name or
546
// is a sub content type and does not have built-in files specs
547
if (!existing.contains(type))
548                         destination.add(type);
549                     return CONTINUE;
550                 }
551             }, root);
552         }
553         return destination;
554     }
555 }
556
Popular Tags