filetypemanager perf fix
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / fileTypes / impl / FileTypeManagerImpl.java
blob8d9b4420803ad65c5594e525a35393feebbcabd0
1 /*
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;
47 import javax.swing.*;
48 import java.io.File;
49 import java.util.*;
50 import java.util.regex.Pattern;
52 /**
53 * @author Yura Cangea
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";
100 static {
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());
118 if (type != null) {
119 for (FileNameMatcher matcher : fileNameMatchers) type.matchers.add(matcher);
121 else {
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) {
128 try {
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 // -------------------------------------------------------------------------
142 // Constructor
143 // -------------------------------------------------------------------------
145 public FileTypeManagerImpl(MessageBus bus, SchemesManagerFactory schemesManagerFactory) {
146 myMessageBus = bus;
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);
159 if (table != null) {
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);
167 return type;
171 return null;
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);
193 else {
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;
243 return true;
246 @NotNull
247 public FileType getStdFileType(@NotNull @NonNls String name) {
248 StandardFileType stdFileType = ourStandardFileTypes.get(name);
249 return stdFileType != null ? stdFileType.fileType : new PlainTextFileType();
252 @NotNull
253 public File[] getExportFiles() {
254 return new File[]{getOrCreateFileTypesDir(), PathManager.getOptionsFile(this)};
257 @NotNull
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 // -------------------------------------------------------------------------
275 @NotNull
276 public FileType getFileTypeByFileName(@NotNull String fileName) {
277 FileType type = myPatternsTable.findAssociatedFileType(fileName);
278 return type == null ? UnknownFileType.INSTANCE : type;
281 @NotNull
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())) {
300 return true;
303 return false;
306 @NotNull
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() {
317 public void run() {
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);
341 @NotNull
342 public FileType[] getRegisteredFileTypes() {
343 Collection<FileType> fileTypes = mySchemesManager.getAllSchemes();
344 return fileTypes.toArray(new FileType[fileTypes.size()]);
347 @NotNull
348 public String getExtension(String fileName) {
349 int index = fileName.lastIndexOf('.');
350 if (index < 0) return "";
351 return fileName.substring(index + 1);
354 @NotNull
355 public String getIgnoredFilesList() {
356 StringBuilder sb = new StringBuilder();
357 for (String ignoreMask : myIgnoredFileMasksSet) {
358 sb.append(ignoreMask);
359 sb.append(';');
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);
386 //[mike]
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);
411 return true;
415 myNotIgnoredFiles.add(name);
416 return false;
419 @SuppressWarnings({"deprecation"})
420 @NotNull
421 public String[] getAssociatedExtensions(@NotNull FileType type) {
422 return myPatternsTable.getAssociatedExtensions(type);
425 @NotNull
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();
480 return true; //TODO?
483 // -------------------------------------------------------------------------
484 // Implementation of NamedExternalizable interface
485 // -------------------------------------------------------------------------
487 public String getExternalFileName() {
488 return "filetypes";
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) {
511 addIgnore(".svn");
513 if (savedVersion < 2) {
514 restoreStandardFileExtensions();
516 if (savedVersion < 4) {
517 addIgnore("*.pyc");
518 addIgnore("*.pyo");
519 addIgnore(".git");
521 if (savedVersion < 5) {
522 addIgnore("*.hprof");
524 if (savedVersion < VERSION) {
525 addIgnore("_svn");
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());
535 if (type != null) {
536 associate(type, association.getFirst(), false);
538 else {
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());
547 if (type != null) {
548 removeAssociation(type, removedAssociation.getFirst(), false);
550 else {
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;
602 try {
603 return Integer.parseInt(verString);
605 catch (NumberFormatException e) {
606 return 0;
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 // -------------------------------------------------------------------------
669 // Helper methods
670 // -------------------------------------------------------------------------
672 @Nullable
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()));
684 return list;
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();
725 boolean res = false;
726 for (AbstractFileType fileType : collection) {
727 ReadFileType readFileType = (ReadFileType)fileType;
728 FileType loadedFileType = loadFileType(readFileType);
729 res |= myInitialAssociations.hasAssociationsFor(loadedFileType);
732 return res;
736 private FileType loadFileType(final ReadFileType readFileType) {
737 return loadFileType(readFileType.getElement(), false, mySchemesManager.isShared(readFileType) ? readFileType.getExternalInfo() : null,
738 true);
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) {
765 try {
766 ((JDOMExternalizable)type).readExternal(typeElement);
768 catch (InvalidDataException e) {
769 throw new RuntimeException(e);
773 else {
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);
786 if (isDefaults) {
787 myDefaultTypes.add(type);
788 if (type instanceof ExternalizableFileType) {
789 ((ExternalizableFileType)type).markDefaultSettings();
792 else {
793 Element extensions = typeElement.getChild(AbstractFileType.ELEMENT_EXTENSIONMAP);
794 if (extensions != null) {
795 readMappingsForFileType(extensions, type);
799 return 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) {
808 list.add(extension);
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);
820 if (table != null) {
821 if (info == null) {
822 type = new AbstractFileType(table);
824 else {
825 type = new ImportedFileType(table, info);
826 ((ImportedFileType)type).readOriginalMatchers(typeElement);
828 ((AbstractFileType)type).initSupport();
829 return type;
832 for (CustomFileTypeFactory factory : Extensions.getExtensions(CustomFileTypeFactory.EP_NAME)) {
833 type = factory.createFileType(typeElement);
834 if (type != null) {
835 break;
838 if (type == null) {
839 type = new UserBinaryFileType();
841 return type;
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);
850 ft.setIcon(icon);
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());
863 return null;
866 return directory;
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 // -------------------------------------------------------------------------
882 // Setup
883 // -------------------------------------------------------------------------
885 @NotNull
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)) {
912 if (fireChange) {
913 fireBeforeFileTypesChanged();
915 myPatternsTable.addAssociation(matcher, fileType);
916 if (fireChange) {
917 fireFileTypesChanged();
922 public void removeAssociation(FileType fileType, FileNameMatcher matcher, boolean fireChange) {
923 if (myPatternsTable.isAssociatedWith(fileType, matcher)) {
924 if (fireChange) {
925 fireBeforeFileTypesChanged();
927 myPatternsTable.removeAssociation(matcher, fileType);
928 if (fireChange) {
929 fireFileTypesChanged();
934 @Nullable
935 public FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file) {
936 return FileTypeChooser.getKnownFileTypeOrAssociate(file);