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());
294 public FileType
getFileTypeByExtension(@NotNull String extension
) {
295 return getFileTypeByFileName("IntelliJ_IDEA_RULES." + extension
);
298 public void registerFileType(FileType fileType
) {
299 registerFileType(fileType
, ArrayUtil
.EMPTY_STRING_ARRAY
);
302 public void registerFileType(@NotNull final FileType type
, @NotNull final List
<FileNameMatcher
> defaultAssociations
) {
303 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
305 fireBeforeFileTypesChanged();
306 registerFileTypeWithoutNotification(type
, defaultAssociations
);
307 fireFileTypesChanged();
313 public void unregisterFileType(FileType fileType
) {
314 fireBeforeFileTypesChanged();
315 unregisterFileTypeWithoutNotification(fileType
);
316 fireFileTypesChanged();
319 private void unregisterFileTypeWithoutNotification(FileType fileType
) {
320 removeAllAssociations(fileType
);
321 mySchemesManager
.removeScheme(fileType
);
322 if (fileType
instanceof FileTypeIdentifiableByVirtualFile
) {
323 final FileTypeIdentifiableByVirtualFile fakeFileType
= (FileTypeIdentifiableByVirtualFile
)fileType
;
324 mySpecialFileTypes
.remove(fakeFileType
);
329 public FileType
[] getRegisteredFileTypes() {
330 Collection
<FileType
> fileTypes
= mySchemesManager
.getAllSchemes();
331 return fileTypes
.toArray(new FileType
[fileTypes
.size()]);
335 public String
getExtension(String fileName
) {
336 int index
= fileName
.lastIndexOf('.');
337 if (index
< 0) return "";
338 return fileName
.substring(index
+ 1);
342 public String
getIgnoredFilesList() {
343 StringBuilder sb
= new StringBuilder();
344 for (String ignoreMask
: myIgnoredFileMasksSet
) {
345 sb
.append(ignoreMask
);
348 return sb
.toString();
351 public void setIgnoredFilesList(@NotNull String list
) {
352 fireBeforeFileTypesChanged();
353 setIgnoredFilesListWithoutNotification(list
);
355 fireFileTypesChanged();
358 private void setIgnoredFilesListWithoutNotification(String list
) {
359 myIgnoredFileMasksSet
.clear();
360 myIgnorePatterns
.clear();
362 StringTokenizer tokenizer
= new StringTokenizer(list
, ";");
363 while (tokenizer
.hasMoreTokens()) {
364 String ignoredFile
= tokenizer
.nextToken();
365 if (ignoredFile
!= null && !myIgnoredFileMasksSet
.contains(ignoredFile
)) {
366 if (!myIgnoredFileMasksSet
.contains(ignoredFile
)) {
367 myIgnorePatterns
.add(PatternUtil
.fromMask(ignoredFile
));
369 myIgnoredFileMasksSet
.add(ignoredFile
);
374 //To make async delete work. See FileUtil.asyncDelete.
375 //Quite a hack, but still we need to have some name, which
376 //won't be catched by VF for sure.
377 //noinspection HardCodedStringLiteral
378 Pattern p
= Pattern
.compile(".*\\.__del__");
379 myIgnorePatterns
.add(p
);
382 public boolean isIgnoredFilesListEqualToCurrent(String list
) {
383 Set
<String
> tempSet
= new THashSet
<String
>();
384 StringTokenizer tokenizer
= new StringTokenizer(list
, ";");
385 while (tokenizer
.hasMoreTokens()) {
386 tempSet
.add(tokenizer
.nextToken());
388 return tempSet
.equals(myIgnoredFileMasksSet
);
391 public boolean isFileIgnored(@NotNull String name
) {
392 if (myNotIgnoredFiles
.contains(name
)) return false;
393 if (myIgnoredFiles
.contains(name
)) return true;
395 for (Pattern pattern
: myIgnorePatterns
) {
396 if (pattern
.matcher(name
).matches()) {
397 myIgnoredFiles
.add(name
);
402 myNotIgnoredFiles
.add(name
);
406 @SuppressWarnings({"deprecation"})
408 public String
[] getAssociatedExtensions(@NotNull FileType type
) {
409 return myPatternsTable
.getAssociatedExtensions(type
);
413 public List
<FileNameMatcher
> getAssociations(@NotNull FileType type
) {
414 return myPatternsTable
.getAssociations(type
);
417 public void associate(@NotNull FileType type
, @NotNull FileNameMatcher matcher
) {
418 associate(type
, matcher
, true);
421 public void removeAssociation(@NotNull FileType type
, @NotNull FileNameMatcher matcher
) {
422 removeAssociation(type
, matcher
, true);
425 private void removeAllAssociations(FileType type
) {
426 myPatternsTable
.removeAllAssociations(type
);
429 public void fireBeforeFileTypesChanged() {
430 FileTypeEvent event
= new FileTypeEvent(this);
431 myMessageBus
.syncPublisher(AppTopics
.FILE_TYPES
).beforeFileTypesChanged(event
);
434 public SchemesManager
<FileType
, AbstractFileType
> getSchemesManager() {
435 return mySchemesManager
;
438 public void fireFileTypesChanged() {
439 myNotIgnoredFiles
.clear();
440 myIgnoredFiles
.clear();
442 final FileTypeEvent event
= new FileTypeEvent(this);
443 myMessageBus
.syncPublisher(AppTopics
.FILE_TYPES
).fileTypesChanged(event
);
446 private final Map
<FileTypeListener
, MessageBusConnection
> myAdapters
= new HashMap
<FileTypeListener
, MessageBusConnection
>();
448 public void addFileTypeListener(@NotNull FileTypeListener listener
) {
449 final MessageBusConnection connection
= myMessageBus
.connect();
450 connection
.subscribe(AppTopics
.FILE_TYPES
, listener
);
451 myAdapters
.put(listener
, connection
);
454 public void removeFileTypeListener(@NotNull FileTypeListener listener
) {
455 final MessageBusConnection connection
= myAdapters
.remove(listener
);
456 if (connection
!= null) {
457 connection
.disconnect();
462 @SuppressWarnings({"SimplifiableIfStatement"})
463 private static boolean isDefaultModified(FileType fileType
) {
464 if (fileType
instanceof ExternalizableFileType
) {
465 return ((ExternalizableFileType
)fileType
).isModified();
470 // -------------------------------------------------------------------------
471 // Implementation of NamedExternalizable interface
472 // -------------------------------------------------------------------------
474 public String
getExternalFileName() {
478 public void readExternal(Element parentNode
) throws InvalidDataException
{
479 int savedVersion
= getVersion(parentNode
);
480 for (final Object o
: parentNode
.getChildren()) {
481 final Element e
= (Element
)o
;
482 if (ELEMENT_FILETYPES
.equals(e
.getName())) {
483 List children
= e
.getChildren(ELEMENT_FILETYPE
);
484 for (final Object aChildren
: children
) {
485 Element element
= (Element
)aChildren
;
486 loadFileType(element
, true, null, false);
489 else if (ELEMENT_IGNOREFILES
.equals(e
.getName())) {
490 setIgnoredFilesListWithoutNotification(e
.getAttributeValue(ATTRIBUTE_LIST
));
492 else if (AbstractFileType
.ELEMENT_EXTENSIONMAP
.equals(e
.getName())) {
493 readGlobalMappings(e
);
497 if (savedVersion
== 0) {
500 if (savedVersion
< 2) {
501 restoreStandardFileExtensions();
503 if (savedVersion
< 4) {
508 if (savedVersion
< 5) {
509 addIgnore("*.hprof");
511 if (savedVersion
< VERSION
) {
516 private void readGlobalMappings(final Element e
) {
518 List
<Pair
<FileNameMatcher
, String
>> associations
= AbstractFileType
.readAssociations(e
);
520 for (Pair
<FileNameMatcher
, String
> association
: associations
) {
521 FileType type
= getFileTypeByName(association
.getSecond());
523 associate(type
, association
.getFirst(), false);
526 myUnresolvedMappings
.put(association
.getFirst(), association
.getSecond());
530 List
<Pair
<FileNameMatcher
, String
>> removedAssociations
= AbstractFileType
.readRemovedAssociations(e
);
532 for (Pair
<FileNameMatcher
, String
> removedAssociation
: removedAssociations
) {
533 FileType type
= getFileTypeByName(removedAssociation
.getSecond());
535 removeAssociation(type
, removedAssociation
.getFirst(), false);
538 myUnresolvedRemovedMappings
.put(removedAssociation
.getFirst(), removedAssociation
.getSecond());
543 private void readMappingsForFileType(final Element e
, FileType type
) {
545 List
<Pair
<FileNameMatcher
, String
>> associations
= AbstractFileType
.readAssociations(e
);
547 for (Pair
<FileNameMatcher
, String
> association
: associations
) {
548 associate(type
, association
.getFirst(), false);
551 List
<Pair
<FileNameMatcher
, String
>> removedAssociations
= AbstractFileType
.readRemovedAssociations(e
);
553 for (Pair
<FileNameMatcher
, String
> removedAssociation
: removedAssociations
) {
554 removeAssociation(type
, removedAssociation
.getFirst(), false);
559 private void addIgnore(@NonNls final String ignoreMask
) {
560 if (!myIgnoredFileMasksSet
.contains(ignoreMask
)) {
561 myIgnorePatterns
.add(PatternUtil
.fromMask(ignoreMask
));
562 myIgnoredFileMasksSet
.add(ignoreMask
);
566 private void restoreStandardFileExtensions() {
567 for (final String name
: FILE_TYPES_WITH_PREDEFINED_EXTENSIONS
) {
568 final StandardFileType stdFileType
= ourStandardFileTypes
.get(name
);
569 if (stdFileType
!= null) {
570 FileType fileType
= stdFileType
.fileType
;
571 for (FileNameMatcher matcher
: myPatternsTable
.getAssociations(fileType
)) {
572 FileType defaultFileType
= myInitialAssociations
.findAssociatedFileType(matcher
);
573 if (defaultFileType
!= null && defaultFileType
!= fileType
) {
574 removeAssociation(fileType
, matcher
, false);
575 associate(defaultFileType
, matcher
, false);
579 for (FileNameMatcher matcher
: myInitialAssociations
.getAssociations(fileType
)) {
580 associate(fileType
, matcher
, false);
586 private static int getVersion(final Element node
) {
587 final String verString
= node
.getAttributeValue(ATTRIBUTE_VERSION
);
588 if (verString
== null) return 0;
590 return Integer
.parseInt(verString
);
592 catch (NumberFormatException e
) {
597 public void writeExternal(Element parentNode
) throws WriteExternalException
{
598 parentNode
.setAttribute(ATTRIBUTE_VERSION
, String
.valueOf(VERSION
));
600 Element element
= new Element(ELEMENT_IGNOREFILES
);
601 parentNode
.addContent(element
);
602 element
.setAttribute(ATTRIBUTE_LIST
, getIgnoredFilesList());
603 Element map
= new Element(AbstractFileType
.ELEMENT_EXTENSIONMAP
);
604 parentNode
.addContent(map
);
606 final List
<FileType
> fileTypes
= Arrays
.asList(getRegisteredFileTypes());
607 Collections
.sort(fileTypes
, new Comparator
<FileType
>() {
608 public int compare(FileType o1
, FileType o2
) {
609 return o1
.getName().compareTo(o2
.getName());
612 for (FileType type
: fileTypes
) {
613 writeExtensionsMap(map
, type
, true);
617 private void writeExtensionsMap(final Element map
, final FileType type
, boolean specifyTypeName
) {
618 final List
<FileNameMatcher
> assocs
= myPatternsTable
.getAssociations(type
);
619 final Set
<FileNameMatcher
> defaultAssocs
= new HashSet
<FileNameMatcher
>(myInitialAssociations
.getAssociations(type
));
621 for (FileNameMatcher matcher
: assocs
) {
622 if (defaultAssocs
.contains(matcher
)) {
623 defaultAssocs
.remove(matcher
);
625 else if (shouldSave(type
)) {
626 if (!(type
instanceof ImportedFileType
) || !((ImportedFileType
)type
).getOriginalPatterns().contains(matcher
)) {
627 Element content
= AbstractFileType
.writeMapping(type
, matcher
, specifyTypeName
);
628 if (content
!= null) {
629 map
.addContent(content
);
635 for (FileNameMatcher matcher
: defaultAssocs
) {
636 Element content
= AbstractFileType
.writeRemovedMapping(type
, matcher
, specifyTypeName
);
637 if (content
!= null) {
638 map
.addContent(content
);
642 if (type
instanceof ImportedFileType
) {
643 List
<FileNameMatcher
> original
= ((ImportedFileType
)type
).getOriginalPatterns();
644 for (FileNameMatcher matcher
: original
) {
645 if (!assocs
.contains(matcher
)) {
646 Element content
= AbstractFileType
.writeRemovedMapping(type
, matcher
, specifyTypeName
);
647 if (content
!= null) {
648 map
.addContent(content
);
655 // -------------------------------------------------------------------------
657 // -------------------------------------------------------------------------
660 private FileType
getFileTypeByName(String name
) {
661 return mySchemesManager
.findSchemeByName(name
);
664 private static List
<FileNameMatcher
> parse(@NonNls String semicolonDelimited
) {
665 if (semicolonDelimited
== null) return Collections
.emptyList();
666 StringTokenizer tokenizer
= new StringTokenizer(semicolonDelimited
, FileTypeConsumer
.EXTENSION_DELIMITER
, false);
667 ArrayList
<FileNameMatcher
> list
= new ArrayList
<FileNameMatcher
>();
668 while (tokenizer
.hasMoreTokens()) {
669 list
.add(new ExtensionFileNameMatcher(tokenizer
.nextToken().trim()));
675 * Registers a standard file type. Doesn't notifyListeners any change events.
677 private void registerFileTypeWithoutNotification(FileType fileType
, List
<FileNameMatcher
> matchers
) {
678 mySchemesManager
.addNewScheme(fileType
, true);
679 for (FileNameMatcher matcher
: matchers
) {
680 myPatternsTable
.addAssociation(matcher
, fileType
);
681 myInitialAssociations
.addAssociation(matcher
, fileType
);
684 if (fileType
instanceof FileTypeIdentifiableByVirtualFile
) {
685 mySpecialFileTypes
.add((FileTypeIdentifiableByVirtualFile
)fileType
);
688 // Resolve unresolved mappings initialized before certain plugin initialized.
689 for (FileNameMatcher matcher
: new THashSet
<FileNameMatcher
>(myUnresolvedMappings
.keySet())) {
690 String name
= myUnresolvedMappings
.get(matcher
);
691 if (Comparing
.equal(name
, fileType
.getName())) {
692 myPatternsTable
.addAssociation(matcher
, fileType
);
693 myUnresolvedMappings
.remove(matcher
);
697 for (FileNameMatcher matcher
: new THashSet
<FileNameMatcher
>(myUnresolvedRemovedMappings
.keySet())) {
698 String name
= myUnresolvedRemovedMappings
.get(matcher
);
699 if (Comparing
.equal(name
, fileType
.getName())) {
700 removeAssociation(fileType
, matcher
, false);
701 myUnresolvedRemovedMappings
.remove(matcher
);
707 // returns true if at least one standard file type has been read
708 @SuppressWarnings({"EmptyCatchBlock"})
709 private boolean loadAllFileTypes() {
710 Collection
<AbstractFileType
> collection
= mySchemesManager
.loadSchemes();
713 for (AbstractFileType fileType
: collection
) {
714 ReadFileType readFileType
= (ReadFileType
)fileType
;
715 FileType loadedFileType
= loadFileType(readFileType
);
716 res
|= myInitialAssociations
.hasAssociationsFor(loadedFileType
);
723 private FileType
loadFileType(final ReadFileType readFileType
) {
724 return loadFileType(readFileType
.getElement(), false, mySchemesManager
.isShared(readFileType
) ? readFileType
.getExternalInfo() : null,
729 private FileType
loadFileType(Element typeElement
, boolean isDefaults
, final ExternalInfo info
, boolean ignoreExisting
) {
730 String fileTypeName
= typeElement
.getAttributeValue(ATTRIBUTE_NAME
);
731 String fileTypeDescr
= typeElement
.getAttributeValue(ATTRIBUTE_DESCRIPTION
);
732 String iconPath
= typeElement
.getAttributeValue(ATTRIBUTE_ICON
);
733 String extensionsStr
= typeElement
.getAttributeValue(ATTRIBUTE_EXTENSIONS
); // TODO: support wildcards
735 FileType type
= getFileTypeByName(fileTypeName
);
737 if (isDefaults
&& !ignoreExisting
) {
738 extensionsStr
= filterAlreadyRegisteredExtensions(extensionsStr
);
741 List
<FileNameMatcher
> exts
= parse(extensionsStr
);
742 if (type
!= null && !ignoreExisting
) {
743 if (isDefaults
) return type
;
744 if (extensionsStr
!= null) {
745 removeAllAssociations(type
);
746 for (FileNameMatcher ext
: exts
) {
747 associate(type
, ext
, false);
751 if (type
instanceof JDOMExternalizable
) {
753 ((JDOMExternalizable
)type
).readExternal(typeElement
);
755 catch (InvalidDataException e
) {
756 throw new RuntimeException(e
);
761 type
= loadCustomFile(typeElement
, info
);
762 if (type
instanceof UserFileType
) {
763 setFileTypeAttributes(fileTypeName
, fileTypeDescr
, iconPath
, (UserFileType
)type
);
765 registerFileTypeWithoutNotification(type
, exts
);
768 if (type
instanceof UserFileType
) {
769 UserFileType ft
= (UserFileType
)type
;
770 setFileTypeAttributes(fileTypeName
, fileTypeDescr
, iconPath
, ft
);
774 myDefaultTypes
.add(type
);
775 if (type
instanceof ExternalizableFileType
) {
776 ((ExternalizableFileType
)type
).markDefaultSettings();
780 Element extensions
= typeElement
.getChild(AbstractFileType
.ELEMENT_EXTENSIONMAP
);
781 if (extensions
!= null) {
782 readMappingsForFileType(extensions
, type
);
789 private String
filterAlreadyRegisteredExtensions(String semicolonDelimited
) {
790 StringTokenizer tokenizer
= new StringTokenizer(semicolonDelimited
, FileTypeConsumer
.EXTENSION_DELIMITER
, false);
791 ArrayList
<String
> list
= new ArrayList
<String
>();
792 while (tokenizer
.hasMoreTokens()) {
793 final String extension
= tokenizer
.nextToken().trim();
794 if (getFileTypeByExtension(extension
) == UnknownFileType
.INSTANCE
) {
798 return StringUtil
.join(list
, FileTypeConsumer
.EXTENSION_DELIMITER
);
801 private static FileType
loadCustomFile(final Element typeElement
, ExternalInfo info
) {
802 FileType type
= null;
804 Element element
= typeElement
.getChild(AbstractFileType
.ELEMENT_HIGHLIGHTING
);
805 if (element
!= null) {
806 final SyntaxTable table
= AbstractFileType
.readSyntaxTable(element
);
809 type
= new AbstractFileType(table
);
812 type
= new ImportedFileType(table
, info
);
813 ((ImportedFileType
)type
).readOriginalMatchers(typeElement
);
815 ((AbstractFileType
)type
).initSupport();
819 for (CustomFileTypeFactory factory
: Extensions
.getExtensions(CustomFileTypeFactory
.EP_NAME
)) {
820 type
= factory
.createFileType(typeElement
);
826 type
= new UserBinaryFileType();
831 private static void setFileTypeAttributes(final String fileTypeName
,
832 final String fileTypeDescr
,
833 final String iconPath
,
834 final UserFileType ft
) {
835 if (iconPath
!= null && !"".equals(iconPath
.trim())) {
836 Icon icon
= IconLoader
.getIcon(iconPath
);
840 if (fileTypeDescr
!= null) ft
.setDescription(fileTypeDescr
);
841 if (fileTypeName
!= null) ft
.setName(fileTypeName
);
844 private static File
getOrCreateFileTypesDir() {
845 String directoryPath
= PathManager
.getConfigPath() + File
.separator
+ ELEMENT_FILETYPES
;
846 File directory
= new File(directoryPath
);
847 if (!directory
.exists()) {
848 if (!directory
.mkdir()) {
849 LOG
.error("Could not create directory: " + directory
.getAbsolutePath());
856 private static boolean shouldSave(FileType fileType
) {
857 return fileType
!= FileTypes
.UNKNOWN
&& !fileType
.isReadOnly();
860 private static void writeHeader(Element root
, FileType fileType
) {
861 root
.setAttribute(ATTRIBUTE_BINARY
, String
.valueOf(fileType
.isBinary()));
862 root
.setAttribute(ATTRIBUTE_DEFAULT_EXTENSION
, fileType
.getDefaultExtension());
864 root
.setAttribute(ATTRIBUTE_DESCRIPTION
, fileType
.getDescription());
865 root
.setAttribute(ATTRIBUTE_NAME
, fileType
.getName());
868 // -------------------------------------------------------------------------
870 // -------------------------------------------------------------------------
873 public String
getComponentName() {
874 if ("Idea".equals(System
.getProperty("idea.platform.prefix"))) {
875 return "CommunityFileTypes";
877 return "FileTypeManager";
880 public FileTypeAssocTable
getExtensionMap() {
881 return myPatternsTable
;
884 public void setPatternsTable(Set
<FileType
> fileTypes
, FileTypeAssocTable assocTable
) {
885 fireBeforeFileTypesChanged();
886 mySchemesManager
.clearAllSchemes();
887 for (FileType fileType
: fileTypes
) {
888 mySchemesManager
.addNewScheme(fileType
, true);
889 if (fileType
instanceof AbstractFileType
) {
890 ((AbstractFileType
)fileType
).initSupport();
893 myPatternsTable
= assocTable
.copy();
894 fireFileTypesChanged();
897 public void associate(FileType fileType
, FileNameMatcher matcher
, boolean fireChange
) {
898 if (!myPatternsTable
.isAssociatedWith(fileType
, matcher
)) {
900 fireBeforeFileTypesChanged();
902 myPatternsTable
.addAssociation(matcher
, fileType
);
904 fireFileTypesChanged();
909 public void removeAssociation(FileType fileType
, FileNameMatcher matcher
, boolean fireChange
) {
910 if (myPatternsTable
.isAssociatedWith(fileType
, matcher
)) {
912 fireBeforeFileTypesChanged();
914 myPatternsTable
.removeAssociation(matcher
, fileType
);
916 fireFileTypesChanged();
922 public FileType
getKnownFileTypeOrAssociate(@NotNull VirtualFile file
) {
923 return FileTypeChooser
.getKnownFileTypeOrAssociate(file
);