2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.openapi
.fileTypes
.impl
;
18 import com
.intellij
.AppTopics
;
19 import com
.intellij
.ide
.highlighter
.custom
.SyntaxTable
;
20 import com
.intellij
.ide
.highlighter
.custom
.impl
.ReadFileType
;
21 import com
.intellij
.ide
.plugins
.PluginManager
;
22 import com
.intellij
.openapi
.application
.ApplicationManager
;
23 import com
.intellij
.openapi
.application
.PathManager
;
24 import com
.intellij
.openapi
.components
.ExportableApplicationComponent
;
25 import com
.intellij
.openapi
.components
.RoamingType
;
26 import com
.intellij
.openapi
.diagnostic
.Logger
;
27 import com
.intellij
.openapi
.extensions
.Extensions
;
28 import com
.intellij
.openapi
.fileTypes
.*;
29 import com
.intellij
.openapi
.fileTypes
.ex
.*;
30 import com
.intellij
.openapi
.options
.*;
31 import com
.intellij
.openapi
.util
.*;
32 import com
.intellij
.openapi
.util
.text
.StringUtil
;
33 import com
.intellij
.openapi
.vfs
.VirtualFile
;
34 import com
.intellij
.util
.ArrayUtil
;
35 import com
.intellij
.util
.PatternUtil
;
36 import com
.intellij
.util
.containers
.ConcurrentHashSet
;
37 import com
.intellij
.util
.messages
.MessageBus
;
38 import com
.intellij
.util
.messages
.MessageBusConnection
;
39 import gnu
.trove
.THashMap
;
40 import gnu
.trove
.THashSet
;
41 import org
.jdom
.Document
;
42 import org
.jdom
.Element
;
43 import org
.jetbrains
.annotations
.NonNls
;
44 import org
.jetbrains
.annotations
.NotNull
;
45 import org
.jetbrains
.annotations
.Nullable
;
50 import java
.util
.regex
.Pattern
;
55 public class FileTypeManagerImpl
extends FileTypeManagerEx
implements NamedJDOMExternalizable
, ExportableApplicationComponent
{
56 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl");
57 private static final int VERSION
= 6;
59 private final Set
<FileType
> myDefaultTypes
= new THashSet
<FileType
>();
60 private final ArrayList
<FileTypeIdentifiableByVirtualFile
> mySpecialFileTypes
= new ArrayList
<FileTypeIdentifiableByVirtualFile
>();
61 private final ArrayList
<Pattern
> myIgnorePatterns
= new ArrayList
<Pattern
>();
63 private FileTypeAssocTable
<FileType
> myPatternsTable
= new FileTypeAssocTable
<FileType
>();
64 private final Set
<String
> myIgnoredFileMasksSet
= new LinkedHashSet
<String
>();
65 private final Set
<String
> myNotIgnoredFiles
= new ConcurrentHashSet
<String
>();
66 private final Set
<String
> myIgnoredFiles
= new ConcurrentHashSet
<String
>();
67 private final FileTypeAssocTable
<FileType
> myInitialAssociations
= new FileTypeAssocTable
<FileType
>();
68 private final Map
<FileNameMatcher
, String
> myUnresolvedMappings
= new THashMap
<FileNameMatcher
, String
>();
69 private final Map
<FileNameMatcher
, String
> myUnresolvedRemovedMappings
= new THashMap
<FileNameMatcher
, String
>();
71 @NonNls private static final String ELEMENT_FILETYPE
= "filetype";
72 @NonNls private static final String ELEMENT_FILETYPES
= "filetypes";
73 @NonNls private static final String ELEMENT_IGNOREFILES
= "ignoreFiles";
74 @NonNls private static final String ATTRIBUTE_LIST
= "list";
76 @NonNls private static final String ATTRIBUTE_VERSION
= "version";
77 @NonNls private static final String ATTRIBUTE_NAME
= "name";
78 @NonNls private static final String ATTRIBUTE_DESCRIPTION
= "description";
79 @NonNls private static final String ATTRIBUTE_ICON
= "icon";
80 @NonNls private static final String ATTRIBUTE_EXTENSIONS
= "extensions";
81 @NonNls private static final String ATTRIBUTE_BINARY
= "binary";
82 @NonNls private static final String ATTRIBUTE_DEFAULT_EXTENSION
= "default_extension";
84 private static class StandardFileType
{
85 private final FileType fileType
;
86 private final List
<FileNameMatcher
> matchers
;
88 private StandardFileType(final FileType fileType
, final List
<FileNameMatcher
> matchers
) {
89 this.fileType
= fileType
;
90 this.matchers
= matchers
;
94 private final MessageBus myMessageBus
;
95 private static final Map
<String
, StandardFileType
> ourStandardFileTypes
= new LinkedHashMap
<String
, StandardFileType
>();
96 @NonNls private static final String
[] FILE_TYPES_WITH_PREDEFINED_EXTENSIONS
= {"JSP", "JSPX", "DTD", "HTML", "Properties", "XHTML"};
97 private final SchemesManager
<FileType
, AbstractFileType
> mySchemesManager
;
98 @NonNls private static final String FILE_SPEC
= "$ROOT_CONFIG$/filetypes";
101 final FileTypeConsumer consumer
= new FileTypeConsumer() {
102 public void consume(@NotNull final FileType fileType
, final String extensions
) {
103 register(fileType
, parse(extensions
));
106 public void consume(@NotNull final FileType fileType
, final FileNameMatcher
... matchers
) {
107 register(fileType
, new ArrayList
<FileNameMatcher
>(Arrays
.asList(matchers
)));
110 public FileType
getStandardFileTypeByName(@NotNull final String name
) {
111 final StandardFileType type
= ourStandardFileTypes
.get(name
);
112 return type
!= null ? type
.fileType
: null;
115 private void register(final FileType fileType
, final List
<FileNameMatcher
> fileNameMatchers
) {
116 final StandardFileType type
= ourStandardFileTypes
.get(fileType
.getName());
119 for (FileNameMatcher matcher
: fileNameMatchers
) type
.matchers
.add(matcher
);
122 ourStandardFileTypes
.put(fileType
.getName(), new StandardFileType(fileType
, fileNameMatchers
));
126 final FileTypeFactory
[] fileTypeFactories
= Extensions
.getExtensions(FileTypeFactory
.FILE_TYPE_FACTORY_EP
);
127 for (final FileTypeFactory factory
: fileTypeFactories
) {
129 initFactory(consumer
, factory
);
131 catch (final Error ex
) {
132 PluginManager
.disableIncompatiblePlugin(factory
, ex
);
137 private static void initFactory(final FileTypeConsumer consumer
, final FileTypeFactory factory
) {
138 factory
.createFileTypes(consumer
);
141 // -------------------------------------------------------------------------
143 // -------------------------------------------------------------------------
145 public FileTypeManagerImpl(MessageBus bus
, SchemesManagerFactory schemesManagerFactory
) {
147 mySchemesManager
= schemesManagerFactory
.createSchemesManager(FILE_SPEC
, new SchemeProcessor
<AbstractFileType
>() {
148 public AbstractFileType
readScheme(final Document document
) throws InvalidDataException
{
149 if (document
== null) {
150 throw new InvalidDataException();
152 Element root
= document
.getRootElement();
153 if (root
== null || !ELEMENT_FILETYPE
.equals(root
.getName())) {
154 throw new InvalidDataException();
156 Element element
= root
.getChild(AbstractFileType
.ELEMENT_HIGHLIGHTING
);
157 if (element
!= null) {
158 final SyntaxTable table
= AbstractFileType
.readSyntaxTable(element
);
160 ReadFileType type
= new ReadFileType(table
, root
);
161 String fileTypeName
= root
.getAttributeValue(ATTRIBUTE_NAME
);
162 String fileTypeDescr
= root
.getAttributeValue(ATTRIBUTE_DESCRIPTION
);
163 String iconPath
= root
.getAttributeValue(ATTRIBUTE_ICON
);
165 setFileTypeAttributes(fileTypeName
, fileTypeDescr
, iconPath
, type
);
175 public boolean shouldBeSaved(final AbstractFileType fileType
) {
176 return shouldBeSavedToFile(fileType
);
180 public Document
writeScheme(final AbstractFileType fileType
) throws WriteExternalException
{
181 Element root
= new Element(ELEMENT_FILETYPE
);
183 writeHeader(root
, fileType
);
185 fileType
.writeExternal(root
);
187 Element map
= new Element(AbstractFileType
.ELEMENT_EXTENSIONMAP
);
188 root
.addContent(map
);
190 if (fileType
instanceof ImportedFileType
) {
191 writeImportedExtensionsMap(map
, (ImportedFileType
)fileType
);
194 writeExtensionsMap(map
, fileType
, false);
197 return new Document(root
);
201 public void initScheme(final AbstractFileType scheme
) {
205 public void onSchemeAdded(final AbstractFileType scheme
) {
206 fireBeforeFileTypesChanged();
207 if (scheme
instanceof ReadFileType
) {
208 loadFileType((ReadFileType
)scheme
);
210 fireFileTypesChanged();
213 public void onSchemeDeleted(final AbstractFileType scheme
) {
214 fireBeforeFileTypesChanged();
215 myPatternsTable
.removeAllAssociations(scheme
);
216 fireFileTypesChanged();
219 public void onCurrentSchemeChanged(final Scheme newCurrentScheme
) {
222 }, RoamingType
.PER_USER
);
223 for (final StandardFileType pair
: ourStandardFileTypes
.values()) {
224 registerFileTypeWithoutNotification(pair
.fileType
, pair
.matchers
);
226 if (loadAllFileTypes()) {
227 restoreStandardFileExtensions();
231 private static void writeImportedExtensionsMap(final Element map
, final ImportedFileType type
) {
232 for (FileNameMatcher matcher
: type
.getOriginalPatterns()) {
233 Element content
= AbstractFileType
.writeMapping(type
, matcher
, false);
234 if (content
!= null) {
235 map
.addContent(content
);
240 private boolean shouldBeSavedToFile(final FileType fileType
) {
241 if (!(fileType
instanceof JDOMExternalizable
) || !shouldSave(fileType
)) return false;
242 if (myDefaultTypes
.contains(fileType
) && !isDefaultModified(fileType
)) return false;
247 public FileType
getStdFileType(@NotNull @NonNls String name
) {
248 StandardFileType stdFileType
= ourStandardFileTypes
.get(name
);
249 return stdFileType
!= null ? stdFileType
.fileType
: new PlainTextFileType();
253 public File
[] getExportFiles() {
254 return new File
[]{getOrCreateFileTypesDir(), PathManager
.getOptionsFile(this)};
258 public String
getPresentableName() {
259 return FileTypesBundle
.message("filetype.settings.component");
261 // -------------------------------------------------------------------------
262 // ApplicationComponent interface implementation
263 // -------------------------------------------------------------------------
265 public void disposeComponent() {
268 public void initComponent() {
271 // -------------------------------------------------------------------------
272 // Implementation of abstract methods
273 // -------------------------------------------------------------------------
276 public FileType
getFileTypeByFileName(@NotNull String fileName
) {
277 FileType type
= myPatternsTable
.findAssociatedFileType(fileName
);
278 return type
== null ? UnknownFileType
.INSTANCE
: type
;
282 public FileType
getFileTypeByFile(@NotNull VirtualFile file
) {
283 // first let file recognize its type
284 //noinspection ForLoopReplaceableByForEach
285 for (int i
= 0; i
< mySpecialFileTypes
.size(); i
++) {
286 final FileTypeIdentifiableByVirtualFile fileType
= mySpecialFileTypes
.get(i
);
287 if (fileType
.isMyFileType(file
)) return fileType
;
290 return getFileTypeByFileName(file
.getName());
293 public boolean isFileOfType(VirtualFile file
, FileType type
) {
294 if (type
instanceof FileTypeIdentifiableByVirtualFile
) {
295 return ((FileTypeIdentifiableByVirtualFile
) type
).isMyFileType(file
);
297 final List
<FileNameMatcher
> matchers
= getAssociations(type
);
298 for (FileNameMatcher matcher
: matchers
) {
299 if (matcher
.accept(file
.getName())) {
307 public FileType
getFileTypeByExtension(@NotNull String extension
) {
308 return getFileTypeByFileName("IntelliJ_IDEA_RULES." + extension
);
311 public void registerFileType(FileType fileType
) {
312 registerFileType(fileType
, ArrayUtil
.EMPTY_STRING_ARRAY
);
315 public void registerFileType(@NotNull final FileType type
, @NotNull final List
<FileNameMatcher
> defaultAssociations
) {
316 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
318 fireBeforeFileTypesChanged();
319 registerFileTypeWithoutNotification(type
, defaultAssociations
);
320 fireFileTypesChanged();
326 public void unregisterFileType(FileType fileType
) {
327 fireBeforeFileTypesChanged();
328 unregisterFileTypeWithoutNotification(fileType
);
329 fireFileTypesChanged();
332 private void unregisterFileTypeWithoutNotification(FileType fileType
) {
333 removeAllAssociations(fileType
);
334 mySchemesManager
.removeScheme(fileType
);
335 if (fileType
instanceof FileTypeIdentifiableByVirtualFile
) {
336 final FileTypeIdentifiableByVirtualFile fakeFileType
= (FileTypeIdentifiableByVirtualFile
)fileType
;
337 mySpecialFileTypes
.remove(fakeFileType
);
342 public FileType
[] getRegisteredFileTypes() {
343 Collection
<FileType
> fileTypes
= mySchemesManager
.getAllSchemes();
344 return fileTypes
.toArray(new FileType
[fileTypes
.size()]);
348 public String
getExtension(String fileName
) {
349 int index
= fileName
.lastIndexOf('.');
350 if (index
< 0) return "";
351 return fileName
.substring(index
+ 1);
355 public String
getIgnoredFilesList() {
356 StringBuilder sb
= new StringBuilder();
357 for (String ignoreMask
: myIgnoredFileMasksSet
) {
358 sb
.append(ignoreMask
);
361 return sb
.toString();
364 public void setIgnoredFilesList(@NotNull String list
) {
365 fireBeforeFileTypesChanged();
366 setIgnoredFilesListWithoutNotification(list
);
368 fireFileTypesChanged();
371 private void setIgnoredFilesListWithoutNotification(String list
) {
372 myIgnoredFileMasksSet
.clear();
373 myIgnorePatterns
.clear();
375 StringTokenizer tokenizer
= new StringTokenizer(list
, ";");
376 while (tokenizer
.hasMoreTokens()) {
377 String ignoredFile
= tokenizer
.nextToken();
378 if (ignoredFile
!= null && !myIgnoredFileMasksSet
.contains(ignoredFile
)) {
379 if (!myIgnoredFileMasksSet
.contains(ignoredFile
)) {
380 myIgnorePatterns
.add(PatternUtil
.fromMask(ignoredFile
));
382 myIgnoredFileMasksSet
.add(ignoredFile
);
387 //To make async delete work. See FileUtil.asyncDelete.
388 //Quite a hack, but still we need to have some name, which
389 //won't be catched by VF for sure.
390 //noinspection HardCodedStringLiteral
391 Pattern p
= Pattern
.compile(".*\\.__del__");
392 myIgnorePatterns
.add(p
);
395 public boolean isIgnoredFilesListEqualToCurrent(String list
) {
396 Set
<String
> tempSet
= new THashSet
<String
>();
397 StringTokenizer tokenizer
= new StringTokenizer(list
, ";");
398 while (tokenizer
.hasMoreTokens()) {
399 tempSet
.add(tokenizer
.nextToken());
401 return tempSet
.equals(myIgnoredFileMasksSet
);
404 public boolean isFileIgnored(@NotNull String name
) {
405 if (myNotIgnoredFiles
.contains(name
)) return false;
406 if (myIgnoredFiles
.contains(name
)) return true;
408 for (Pattern pattern
: myIgnorePatterns
) {
409 if (pattern
.matcher(name
).matches()) {
410 myIgnoredFiles
.add(name
);
415 myNotIgnoredFiles
.add(name
);
419 @SuppressWarnings({"deprecation"})
421 public String
[] getAssociatedExtensions(@NotNull FileType type
) {
422 return myPatternsTable
.getAssociatedExtensions(type
);
426 public List
<FileNameMatcher
> getAssociations(@NotNull FileType type
) {
427 return myPatternsTable
.getAssociations(type
);
430 public void associate(@NotNull FileType type
, @NotNull FileNameMatcher matcher
) {
431 associate(type
, matcher
, true);
434 public void removeAssociation(@NotNull FileType type
, @NotNull FileNameMatcher matcher
) {
435 removeAssociation(type
, matcher
, true);
438 private void removeAllAssociations(FileType type
) {
439 myPatternsTable
.removeAllAssociations(type
);
442 public void fireBeforeFileTypesChanged() {
443 FileTypeEvent event
= new FileTypeEvent(this);
444 myMessageBus
.syncPublisher(AppTopics
.FILE_TYPES
).beforeFileTypesChanged(event
);
447 public SchemesManager
<FileType
, AbstractFileType
> getSchemesManager() {
448 return mySchemesManager
;
451 public void fireFileTypesChanged() {
452 myNotIgnoredFiles
.clear();
453 myIgnoredFiles
.clear();
455 final FileTypeEvent event
= new FileTypeEvent(this);
456 myMessageBus
.syncPublisher(AppTopics
.FILE_TYPES
).fileTypesChanged(event
);
459 private final Map
<FileTypeListener
, MessageBusConnection
> myAdapters
= new HashMap
<FileTypeListener
, MessageBusConnection
>();
461 public void addFileTypeListener(@NotNull FileTypeListener listener
) {
462 final MessageBusConnection connection
= myMessageBus
.connect();
463 connection
.subscribe(AppTopics
.FILE_TYPES
, listener
);
464 myAdapters
.put(listener
, connection
);
467 public void removeFileTypeListener(@NotNull FileTypeListener listener
) {
468 final MessageBusConnection connection
= myAdapters
.remove(listener
);
469 if (connection
!= null) {
470 connection
.disconnect();
475 @SuppressWarnings({"SimplifiableIfStatement"})
476 private static boolean isDefaultModified(FileType fileType
) {
477 if (fileType
instanceof ExternalizableFileType
) {
478 return ((ExternalizableFileType
)fileType
).isModified();
483 // -------------------------------------------------------------------------
484 // Implementation of NamedExternalizable interface
485 // -------------------------------------------------------------------------
487 public String
getExternalFileName() {
491 public void readExternal(Element parentNode
) throws InvalidDataException
{
492 int savedVersion
= getVersion(parentNode
);
493 for (final Object o
: parentNode
.getChildren()) {
494 final Element e
= (Element
)o
;
495 if (ELEMENT_FILETYPES
.equals(e
.getName())) {
496 List children
= e
.getChildren(ELEMENT_FILETYPE
);
497 for (final Object aChildren
: children
) {
498 Element element
= (Element
)aChildren
;
499 loadFileType(element
, true, null, false);
502 else if (ELEMENT_IGNOREFILES
.equals(e
.getName())) {
503 setIgnoredFilesListWithoutNotification(e
.getAttributeValue(ATTRIBUTE_LIST
));
505 else if (AbstractFileType
.ELEMENT_EXTENSIONMAP
.equals(e
.getName())) {
506 readGlobalMappings(e
);
510 if (savedVersion
== 0) {
513 if (savedVersion
< 2) {
514 restoreStandardFileExtensions();
516 if (savedVersion
< 4) {
521 if (savedVersion
< 5) {
522 addIgnore("*.hprof");
524 if (savedVersion
< VERSION
) {
529 private void readGlobalMappings(final Element e
) {
531 List
<Pair
<FileNameMatcher
, String
>> associations
= AbstractFileType
.readAssociations(e
);
533 for (Pair
<FileNameMatcher
, String
> association
: associations
) {
534 FileType type
= getFileTypeByName(association
.getSecond());
536 associate(type
, association
.getFirst(), false);
539 myUnresolvedMappings
.put(association
.getFirst(), association
.getSecond());
543 List
<Pair
<FileNameMatcher
, String
>> removedAssociations
= AbstractFileType
.readRemovedAssociations(e
);
545 for (Pair
<FileNameMatcher
, String
> removedAssociation
: removedAssociations
) {
546 FileType type
= getFileTypeByName(removedAssociation
.getSecond());
548 removeAssociation(type
, removedAssociation
.getFirst(), false);
551 myUnresolvedRemovedMappings
.put(removedAssociation
.getFirst(), removedAssociation
.getSecond());
556 private void readMappingsForFileType(final Element e
, FileType type
) {
558 List
<Pair
<FileNameMatcher
, String
>> associations
= AbstractFileType
.readAssociations(e
);
560 for (Pair
<FileNameMatcher
, String
> association
: associations
) {
561 associate(type
, association
.getFirst(), false);
564 List
<Pair
<FileNameMatcher
, String
>> removedAssociations
= AbstractFileType
.readRemovedAssociations(e
);
566 for (Pair
<FileNameMatcher
, String
> removedAssociation
: removedAssociations
) {
567 removeAssociation(type
, removedAssociation
.getFirst(), false);
572 private void addIgnore(@NonNls final String ignoreMask
) {
573 if (!myIgnoredFileMasksSet
.contains(ignoreMask
)) {
574 myIgnorePatterns
.add(PatternUtil
.fromMask(ignoreMask
));
575 myIgnoredFileMasksSet
.add(ignoreMask
);
579 private void restoreStandardFileExtensions() {
580 for (final String name
: FILE_TYPES_WITH_PREDEFINED_EXTENSIONS
) {
581 final StandardFileType stdFileType
= ourStandardFileTypes
.get(name
);
582 if (stdFileType
!= null) {
583 FileType fileType
= stdFileType
.fileType
;
584 for (FileNameMatcher matcher
: myPatternsTable
.getAssociations(fileType
)) {
585 FileType defaultFileType
= myInitialAssociations
.findAssociatedFileType(matcher
);
586 if (defaultFileType
!= null && defaultFileType
!= fileType
) {
587 removeAssociation(fileType
, matcher
, false);
588 associate(defaultFileType
, matcher
, false);
592 for (FileNameMatcher matcher
: myInitialAssociations
.getAssociations(fileType
)) {
593 associate(fileType
, matcher
, false);
599 private static int getVersion(final Element node
) {
600 final String verString
= node
.getAttributeValue(ATTRIBUTE_VERSION
);
601 if (verString
== null) return 0;
603 return Integer
.parseInt(verString
);
605 catch (NumberFormatException e
) {
610 public void writeExternal(Element parentNode
) throws WriteExternalException
{
611 parentNode
.setAttribute(ATTRIBUTE_VERSION
, String
.valueOf(VERSION
));
613 Element element
= new Element(ELEMENT_IGNOREFILES
);
614 parentNode
.addContent(element
);
615 element
.setAttribute(ATTRIBUTE_LIST
, getIgnoredFilesList());
616 Element map
= new Element(AbstractFileType
.ELEMENT_EXTENSIONMAP
);
617 parentNode
.addContent(map
);
619 final List
<FileType
> fileTypes
= Arrays
.asList(getRegisteredFileTypes());
620 Collections
.sort(fileTypes
, new Comparator
<FileType
>() {
621 public int compare(FileType o1
, FileType o2
) {
622 return o1
.getName().compareTo(o2
.getName());
625 for (FileType type
: fileTypes
) {
626 writeExtensionsMap(map
, type
, true);
630 private void writeExtensionsMap(final Element map
, final FileType type
, boolean specifyTypeName
) {
631 final List
<FileNameMatcher
> assocs
= myPatternsTable
.getAssociations(type
);
632 final Set
<FileNameMatcher
> defaultAssocs
= new HashSet
<FileNameMatcher
>(myInitialAssociations
.getAssociations(type
));
634 for (FileNameMatcher matcher
: assocs
) {
635 if (defaultAssocs
.contains(matcher
)) {
636 defaultAssocs
.remove(matcher
);
638 else if (shouldSave(type
)) {
639 if (!(type
instanceof ImportedFileType
) || !((ImportedFileType
)type
).getOriginalPatterns().contains(matcher
)) {
640 Element content
= AbstractFileType
.writeMapping(type
, matcher
, specifyTypeName
);
641 if (content
!= null) {
642 map
.addContent(content
);
648 for (FileNameMatcher matcher
: defaultAssocs
) {
649 Element content
= AbstractFileType
.writeRemovedMapping(type
, matcher
, specifyTypeName
);
650 if (content
!= null) {
651 map
.addContent(content
);
655 if (type
instanceof ImportedFileType
) {
656 List
<FileNameMatcher
> original
= ((ImportedFileType
)type
).getOriginalPatterns();
657 for (FileNameMatcher matcher
: original
) {
658 if (!assocs
.contains(matcher
)) {
659 Element content
= AbstractFileType
.writeRemovedMapping(type
, matcher
, specifyTypeName
);
660 if (content
!= null) {
661 map
.addContent(content
);
668 // -------------------------------------------------------------------------
670 // -------------------------------------------------------------------------
673 private FileType
getFileTypeByName(String name
) {
674 return mySchemesManager
.findSchemeByName(name
);
677 private static List
<FileNameMatcher
> parse(@NonNls String semicolonDelimited
) {
678 if (semicolonDelimited
== null) return Collections
.emptyList();
679 StringTokenizer tokenizer
= new StringTokenizer(semicolonDelimited
, FileTypeConsumer
.EXTENSION_DELIMITER
, false);
680 ArrayList
<FileNameMatcher
> list
= new ArrayList
<FileNameMatcher
>();
681 while (tokenizer
.hasMoreTokens()) {
682 list
.add(new ExtensionFileNameMatcher(tokenizer
.nextToken().trim()));
688 * Registers a standard file type. Doesn't notifyListeners any change events.
690 private void registerFileTypeWithoutNotification(FileType fileType
, List
<FileNameMatcher
> matchers
) {
691 mySchemesManager
.addNewScheme(fileType
, true);
692 for (FileNameMatcher matcher
: matchers
) {
693 myPatternsTable
.addAssociation(matcher
, fileType
);
694 myInitialAssociations
.addAssociation(matcher
, fileType
);
697 if (fileType
instanceof FileTypeIdentifiableByVirtualFile
) {
698 mySpecialFileTypes
.add((FileTypeIdentifiableByVirtualFile
)fileType
);
701 // Resolve unresolved mappings initialized before certain plugin initialized.
702 for (FileNameMatcher matcher
: new THashSet
<FileNameMatcher
>(myUnresolvedMappings
.keySet())) {
703 String name
= myUnresolvedMappings
.get(matcher
);
704 if (Comparing
.equal(name
, fileType
.getName())) {
705 myPatternsTable
.addAssociation(matcher
, fileType
);
706 myUnresolvedMappings
.remove(matcher
);
710 for (FileNameMatcher matcher
: new THashSet
<FileNameMatcher
>(myUnresolvedRemovedMappings
.keySet())) {
711 String name
= myUnresolvedRemovedMappings
.get(matcher
);
712 if (Comparing
.equal(name
, fileType
.getName())) {
713 removeAssociation(fileType
, matcher
, false);
714 myUnresolvedRemovedMappings
.remove(matcher
);
720 // returns true if at least one standard file type has been read
721 @SuppressWarnings({"EmptyCatchBlock"})
722 private boolean loadAllFileTypes() {
723 Collection
<AbstractFileType
> collection
= mySchemesManager
.loadSchemes();
726 for (AbstractFileType fileType
: collection
) {
727 ReadFileType readFileType
= (ReadFileType
)fileType
;
728 FileType loadedFileType
= loadFileType(readFileType
);
729 res
|= myInitialAssociations
.hasAssociationsFor(loadedFileType
);
736 private FileType
loadFileType(final ReadFileType readFileType
) {
737 return loadFileType(readFileType
.getElement(), false, mySchemesManager
.isShared(readFileType
) ? readFileType
.getExternalInfo() : null,
742 private FileType
loadFileType(Element typeElement
, boolean isDefaults
, final ExternalInfo info
, boolean ignoreExisting
) {
743 String fileTypeName
= typeElement
.getAttributeValue(ATTRIBUTE_NAME
);
744 String fileTypeDescr
= typeElement
.getAttributeValue(ATTRIBUTE_DESCRIPTION
);
745 String iconPath
= typeElement
.getAttributeValue(ATTRIBUTE_ICON
);
746 String extensionsStr
= typeElement
.getAttributeValue(ATTRIBUTE_EXTENSIONS
); // TODO: support wildcards
748 FileType type
= getFileTypeByName(fileTypeName
);
750 if (isDefaults
&& !ignoreExisting
) {
751 extensionsStr
= filterAlreadyRegisteredExtensions(extensionsStr
);
754 List
<FileNameMatcher
> exts
= parse(extensionsStr
);
755 if (type
!= null && !ignoreExisting
) {
756 if (isDefaults
) return type
;
757 if (extensionsStr
!= null) {
758 removeAllAssociations(type
);
759 for (FileNameMatcher ext
: exts
) {
760 associate(type
, ext
, false);
764 if (type
instanceof JDOMExternalizable
) {
766 ((JDOMExternalizable
)type
).readExternal(typeElement
);
768 catch (InvalidDataException e
) {
769 throw new RuntimeException(e
);
774 type
= loadCustomFile(typeElement
, info
);
775 if (type
instanceof UserFileType
) {
776 setFileTypeAttributes(fileTypeName
, fileTypeDescr
, iconPath
, (UserFileType
)type
);
778 registerFileTypeWithoutNotification(type
, exts
);
781 if (type
instanceof UserFileType
) {
782 UserFileType ft
= (UserFileType
)type
;
783 setFileTypeAttributes(fileTypeName
, fileTypeDescr
, iconPath
, ft
);
787 myDefaultTypes
.add(type
);
788 if (type
instanceof ExternalizableFileType
) {
789 ((ExternalizableFileType
)type
).markDefaultSettings();
793 Element extensions
= typeElement
.getChild(AbstractFileType
.ELEMENT_EXTENSIONMAP
);
794 if (extensions
!= null) {
795 readMappingsForFileType(extensions
, type
);
802 private String
filterAlreadyRegisteredExtensions(String semicolonDelimited
) {
803 StringTokenizer tokenizer
= new StringTokenizer(semicolonDelimited
, FileTypeConsumer
.EXTENSION_DELIMITER
, false);
804 ArrayList
<String
> list
= new ArrayList
<String
>();
805 while (tokenizer
.hasMoreTokens()) {
806 final String extension
= tokenizer
.nextToken().trim();
807 if (getFileTypeByExtension(extension
) == UnknownFileType
.INSTANCE
) {
811 return StringUtil
.join(list
, FileTypeConsumer
.EXTENSION_DELIMITER
);
814 private static FileType
loadCustomFile(final Element typeElement
, ExternalInfo info
) {
815 FileType type
= null;
817 Element element
= typeElement
.getChild(AbstractFileType
.ELEMENT_HIGHLIGHTING
);
818 if (element
!= null) {
819 final SyntaxTable table
= AbstractFileType
.readSyntaxTable(element
);
822 type
= new AbstractFileType(table
);
825 type
= new ImportedFileType(table
, info
);
826 ((ImportedFileType
)type
).readOriginalMatchers(typeElement
);
828 ((AbstractFileType
)type
).initSupport();
832 for (CustomFileTypeFactory factory
: Extensions
.getExtensions(CustomFileTypeFactory
.EP_NAME
)) {
833 type
= factory
.createFileType(typeElement
);
839 type
= new UserBinaryFileType();
844 private static void setFileTypeAttributes(final String fileTypeName
,
845 final String fileTypeDescr
,
846 final String iconPath
,
847 final UserFileType ft
) {
848 if (iconPath
!= null && !"".equals(iconPath
.trim())) {
849 Icon icon
= IconLoader
.getIcon(iconPath
);
853 if (fileTypeDescr
!= null) ft
.setDescription(fileTypeDescr
);
854 if (fileTypeName
!= null) ft
.setName(fileTypeName
);
857 private static File
getOrCreateFileTypesDir() {
858 String directoryPath
= PathManager
.getConfigPath() + File
.separator
+ ELEMENT_FILETYPES
;
859 File directory
= new File(directoryPath
);
860 if (!directory
.exists()) {
861 if (!directory
.mkdir()) {
862 LOG
.error("Could not create directory: " + directory
.getAbsolutePath());
869 private static boolean shouldSave(FileType fileType
) {
870 return fileType
!= FileTypes
.UNKNOWN
&& !fileType
.isReadOnly();
873 private static void writeHeader(Element root
, FileType fileType
) {
874 root
.setAttribute(ATTRIBUTE_BINARY
, String
.valueOf(fileType
.isBinary()));
875 root
.setAttribute(ATTRIBUTE_DEFAULT_EXTENSION
, fileType
.getDefaultExtension());
877 root
.setAttribute(ATTRIBUTE_DESCRIPTION
, fileType
.getDescription());
878 root
.setAttribute(ATTRIBUTE_NAME
, fileType
.getName());
881 // -------------------------------------------------------------------------
883 // -------------------------------------------------------------------------
886 public String
getComponentName() {
887 if ("Idea".equals(System
.getProperty("idea.platform.prefix"))) {
888 return "CommunityFileTypes";
890 return "FileTypeManager";
893 public FileTypeAssocTable
getExtensionMap() {
894 return myPatternsTable
;
897 public void setPatternsTable(Set
<FileType
> fileTypes
, FileTypeAssocTable assocTable
) {
898 fireBeforeFileTypesChanged();
899 mySchemesManager
.clearAllSchemes();
900 for (FileType fileType
: fileTypes
) {
901 mySchemesManager
.addNewScheme(fileType
, true);
902 if (fileType
instanceof AbstractFileType
) {
903 ((AbstractFileType
)fileType
).initSupport();
906 myPatternsTable
= assocTable
.copy();
907 fireFileTypesChanged();
910 public void associate(FileType fileType
, FileNameMatcher matcher
, boolean fireChange
) {
911 if (!myPatternsTable
.isAssociatedWith(fileType
, matcher
)) {
913 fireBeforeFileTypesChanged();
915 myPatternsTable
.addAssociation(matcher
, fileType
);
917 fireFileTypesChanged();
922 public void removeAssociation(FileType fileType
, FileNameMatcher matcher
, boolean fireChange
) {
923 if (myPatternsTable
.isAssociatedWith(fileType
, matcher
)) {
925 fireBeforeFileTypesChanged();
927 myPatternsTable
.removeAssociation(matcher
, fileType
);
929 fireFileTypesChanged();
935 public FileType
getKnownFileTypeOrAssociate(@NotNull VirtualFile file
) {
936 return FileTypeChooser
.getKnownFileTypeOrAssociate(file
);