d84a059b1d563565e5913a8fa7a88ed3a38a3630
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / fileTypes / impl / FileTypeManagerImpl.java
blobd84a059b1d563565e5913a8fa7a88ed3a38a3630
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 @NotNull
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() {
304 public void run() {
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);
328 @NotNull
329 public FileType[] getRegisteredFileTypes() {
330 Collection<FileType> fileTypes = mySchemesManager.getAllSchemes();
331 return fileTypes.toArray(new FileType[fileTypes.size()]);
334 @NotNull
335 public String getExtension(String fileName) {
336 int index = fileName.lastIndexOf('.');
337 if (index < 0) return "";
338 return fileName.substring(index + 1);
341 @NotNull
342 public String getIgnoredFilesList() {
343 StringBuilder sb = new StringBuilder();
344 for (String ignoreMask : myIgnoredFileMasksSet) {
345 sb.append(ignoreMask);
346 sb.append(';');
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);
373 //[mike]
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);
398 return true;
402 myNotIgnoredFiles.add(name);
403 return false;
406 @SuppressWarnings({"deprecation"})
407 @NotNull
408 public String[] getAssociatedExtensions(@NotNull FileType type) {
409 return myPatternsTable.getAssociatedExtensions(type);
412 @NotNull
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();
467 return true; //TODO?
470 // -------------------------------------------------------------------------
471 // Implementation of NamedExternalizable interface
472 // -------------------------------------------------------------------------
474 public String getExternalFileName() {
475 return "filetypes";
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) {
498 addIgnore(".svn");
500 if (savedVersion < 2) {
501 restoreStandardFileExtensions();
503 if (savedVersion < 4) {
504 addIgnore("*.pyc");
505 addIgnore("*.pyo");
506 addIgnore(".git");
508 if (savedVersion < 5) {
509 addIgnore("*.hprof");
511 if (savedVersion < VERSION) {
512 addIgnore("_svn");
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());
522 if (type != null) {
523 associate(type, association.getFirst(), false);
525 else {
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());
534 if (type != null) {
535 removeAssociation(type, removedAssociation.getFirst(), false);
537 else {
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;
589 try {
590 return Integer.parseInt(verString);
592 catch (NumberFormatException e) {
593 return 0;
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 // -------------------------------------------------------------------------
656 // Helper methods
657 // -------------------------------------------------------------------------
659 @Nullable
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()));
671 return list;
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();
712 boolean res = false;
713 for (AbstractFileType fileType : collection) {
714 ReadFileType readFileType = (ReadFileType)fileType;
715 FileType loadedFileType = loadFileType(readFileType);
716 res |= myInitialAssociations.hasAssociationsFor(loadedFileType);
719 return res;
723 private FileType loadFileType(final ReadFileType readFileType) {
724 return loadFileType(readFileType.getElement(), false, mySchemesManager.isShared(readFileType) ? readFileType.getExternalInfo() : null,
725 true);
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) {
752 try {
753 ((JDOMExternalizable)type).readExternal(typeElement);
755 catch (InvalidDataException e) {
756 throw new RuntimeException(e);
760 else {
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);
773 if (isDefaults) {
774 myDefaultTypes.add(type);
775 if (type instanceof ExternalizableFileType) {
776 ((ExternalizableFileType)type).markDefaultSettings();
779 else {
780 Element extensions = typeElement.getChild(AbstractFileType.ELEMENT_EXTENSIONMAP);
781 if (extensions != null) {
782 readMappingsForFileType(extensions, type);
786 return 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) {
795 list.add(extension);
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);
807 if (table != null) {
808 if (info == null) {
809 type = new AbstractFileType(table);
811 else {
812 type = new ImportedFileType(table, info);
813 ((ImportedFileType)type).readOriginalMatchers(typeElement);
815 ((AbstractFileType)type).initSupport();
816 return type;
819 for (CustomFileTypeFactory factory : Extensions.getExtensions(CustomFileTypeFactory.EP_NAME)) {
820 type = factory.createFileType(typeElement);
821 if (type != null) {
822 break;
825 if (type == null) {
826 type = new UserBinaryFileType();
828 return type;
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);
837 ft.setIcon(icon);
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());
850 return null;
853 return directory;
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 // -------------------------------------------------------------------------
869 // Setup
870 // -------------------------------------------------------------------------
872 @NotNull
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)) {
899 if (fireChange) {
900 fireBeforeFileTypesChanged();
902 myPatternsTable.addAssociation(matcher, fileType);
903 if (fireChange) {
904 fireFileTypesChanged();
909 public void removeAssociation(FileType fileType, FileNameMatcher matcher, boolean fireChange) {
910 if (myPatternsTable.isAssociatedWith(fileType, matcher)) {
911 if (fireChange) {
912 fireBeforeFileTypesChanged();
914 myPatternsTable.removeAssociation(matcher, fileType);
915 if (fireChange) {
916 fireFileTypesChanged();
921 @Nullable
922 public FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file) {
923 return FileTypeChooser.getKnownFileTypeOrAssociate(file);