several optimizations for speeding up getFileType() (part of IJPL-162944 speedup find usages in kotlin files):

- Do not query FileTypeOverrider extension point in hot path; store in fileTypeOverriderCache instead
- Store FileTypeManager instance in FileTypeRegistry field to avoid indirection in hot path
- Use fields to store some services queried in hot path
- perform modifications under myPendingInitializationLock.writeLock() to avoid corruption

GitOrigin-RevId: 9a6ec245f31a779542e2e830089573e8ce25333b
This commit is contained in:
Alexey Kudravtsev
2024-09-30 17:53:45 +00:00
committed by intellij-monorepo-bot
parent 77000cdb9f
commit 92996cecaa
13 changed files with 335 additions and 199 deletions
@@ -48,12 +48,11 @@ public class ApplicationManager {
) {
Application old = ourApplication;
setApplication(instance);
Supplier<? extends FileTypeRegistry> oldFileTypeRegistry = FileTypeRegistry.setInstanceSupplier(fileTypeRegistryGetter);
FileTypeRegistry.setInstanceSupplier(fileTypeRegistryGetter, parent);
Disposer.register(parent, () -> {
if (old != null) {
// to prevent NPEs in threads still running
setApplication(old);
FileTypeRegistry.setInstanceSupplier(oldFileTypeRegistry);
}
});
}
@@ -2,9 +2,12 @@
package com.intellij.openapi.fileTypes;
import com.intellij.lang.Language;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.fileTypes.ex.FileTypeIdentifiableByVirtualFile;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.ByteSequence;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.ApiStatus;
@@ -31,18 +34,23 @@ import java.util.function.Supplier;
* {@link com.intellij.openapi.application.ReadAction#nonBlocking}.
*/
public abstract class FileTypeRegistry {
private static Supplier<? extends FileTypeRegistry> instanceGetter;
private static volatile FileTypeRegistry instance;
static {
ApplicationManager.registerCleaner(() -> instance = null);
}
@ApiStatus.Internal
public static Supplier<? extends FileTypeRegistry> setInstanceSupplier(@NotNull Supplier<? extends FileTypeRegistry> supplier) {
Supplier<? extends FileTypeRegistry> oldValue = instanceGetter;
instanceGetter = supplier;
return oldValue;
public static void setInstanceSupplier(@NotNull Supplier<? extends FileTypeRegistry> supplier, @NotNull Disposable parentDisposable) {
FileTypeRegistry oldInstance = instance;
instance = supplier.get();
Disposer.register(parentDisposable, () -> {
instance = oldInstance;
});
}
@ApiStatus.Internal
public static boolean isInstanceSupplierSet() {
return instanceGetter != null;
return instance != null;
}
public abstract boolean isFileIgnored(@NotNull VirtualFile file);
@@ -59,13 +67,20 @@ public abstract class FileTypeRegistry {
}
public static FileTypeRegistry getInstance() {
Supplier<? extends FileTypeRegistry> instanceGetter = FileTypeRegistry.instanceGetter;
if (instanceGetter == null) {
// in tests FileTypeManager service maybe not preloaded, so, ourInstanceGetter is not set
//noinspection deprecation
return ApplicationManager.getApplication().getServiceByClassName("com.intellij.openapi.fileTypes.FileTypeManager");
FileTypeRegistry cached = instance;
if (cached == null) {
// in tests FileTypeManager service maybe not preloaded, so, instance is not set
Application application = ApplicationManager.getApplication();
Class<? extends FileTypeRegistry> aClass = null;
try {
aClass = (Class<? extends FileTypeRegistry>)Class.forName("com.intellij.openapi.fileTypes.FileTypeManager");
}
catch (ClassNotFoundException ignored) {
}
instance = cached = application == null || aClass == null || !application.hasComponent(aClass)
? new StubFileTypeRegistry() : application.getService(aClass);
}
return instanceGetter.get();
return cached;
}
/**
@@ -0,0 +1,76 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.fileTypes;
import com.intellij.lang.Language;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* Supplies empty {@link FileTypeRegistry} implementation when {@link com.intellij.openapi.fileTypes.FileTypeManager} is not available
*/
final class StubFileTypeRegistry extends FileTypeRegistry {
@Override
public @NotNull FileType getFileTypeByFileName(@NotNull String fileName) {
return StubLanguageFileType.INSTANCE;
}
@Override
public @NotNull FileType getFileTypeByFile(@NotNull VirtualFile file) {
return StubLanguageFileType.INSTANCE;
}
@Override
public @NotNull FileType getFileTypeByExtension(@NotNull String extension) {
return StubLanguageFileType.INSTANCE;
}
@Override
public FileType @NotNull [] getRegisteredFileTypes() {
return new FileType[] {StubLanguageFileType.INSTANCE};
}
@Override
public boolean isFileIgnored(@NotNull VirtualFile file) {
return false;
}
@Override
public @Nullable FileType findFileTypeByName(@NotNull String fileTypeName) {
return null;
}
private static final class StubLanguageFileType extends LanguageFileType {
static final LanguageFileType INSTANCE = new StubLanguageFileType();
private StubLanguageFileType() {
super(Language.ANY);
}
@Override
@NotNull
public String getName() {
return "Mock";
}
@Override
@NotNull
public String getDescription() {
//noinspection HardCodedStringLiteral
return "Mock";
}
@Override
@NotNull
public String getDefaultExtension() {
return ".mockExtensionThatProbablyWon'tEverExist";
}
@Override
public Icon getIcon() {
return null;
}
}
}
+3 -19
View File
@@ -1570,11 +1570,13 @@ com.intellij.openapi.fileTypes.FileTypeListener
- fileTypesChanged(com.intellij.openapi.fileTypes.FileTypeEvent):V
a:com.intellij.openapi.fileTypes.FileTypeManager
- com.intellij.openapi.fileTypes.FileTypeRegistry
- com.intellij.openapi.Disposable
- sf:TOPIC:com.intellij.util.messages.Topic
- <init>():V
- p:<init>():V
- a:associate(com.intellij.openapi.fileTypes.FileType,com.intellij.openapi.fileTypes.FileNameMatcher):V
- f:associateExtension(com.intellij.openapi.fileTypes.FileType,java.lang.String):V
- f:associatePattern(com.intellij.openapi.fileTypes.FileType,java.lang.String):V
- dispose():V
- a:getAssociations(com.intellij.openapi.fileTypes.FileType):java.util.List
- a:getIgnoredFilesList():java.lang.String
- s:getInstance():com.intellij.openapi.fileTypes.FileTypeManager
@@ -1591,24 +1593,6 @@ c:com.intellij.openapi.fileTypes.FileTypes
- sf:PLAIN_TEXT:com.intellij.openapi.fileTypes.LanguageFileType
- sf:UNKNOWN:com.intellij.openapi.fileTypes.FileType
- p:<init>():V
f:com.intellij.openapi.fileTypes.MockFileTypeManager
- com.intellij.openapi.fileTypes.FileTypeManager
- <init>():V
- associate(com.intellij.openapi.fileTypes.FileType,com.intellij.openapi.fileTypes.FileNameMatcher):V
- findFileTypeByName(java.lang.String):com.intellij.openapi.fileTypes.FileType
- getAssociations(com.intellij.openapi.fileTypes.FileType):java.util.List
- getFileTypeByExtension(java.lang.String):com.intellij.openapi.fileTypes.FileType
- getFileTypeByFile(com.intellij.openapi.vfs.VirtualFile):com.intellij.openapi.fileTypes.FileType
- getFileTypeByFileName(java.lang.String):com.intellij.openapi.fileTypes.FileType
- getIgnoredFilesList():java.lang.String
- getKnownFileTypeOrAssociate(com.intellij.openapi.vfs.VirtualFile,com.intellij.openapi.project.Project):com.intellij.openapi.fileTypes.FileType
- getRegisteredFileTypes():com.intellij.openapi.fileTypes.FileType[]
- getStdFileType(java.lang.String):com.intellij.openapi.fileTypes.FileType
- isFileIgnored(com.intellij.openapi.vfs.VirtualFile):Z
- isFileIgnored(java.lang.String):Z
- registerFileType(com.intellij.openapi.fileTypes.FileType,java.lang.String[]):V
- removeAssociation(com.intellij.openapi.fileTypes.FileType,com.intellij.openapi.fileTypes.FileNameMatcher):V
- setIgnoredFilesList(java.lang.String):V
f:com.intellij.openapi.fileTypes.MockLanguageFileType
- com.intellij.openapi.fileTypes.LanguageFileType
- sf:INSTANCE:com.intellij.openapi.fileTypes.LanguageFileType
@@ -1,9 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.fileTypes;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.CachedSingletonsRegistry;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.messages.Topic;
@@ -12,21 +10,11 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.fileTypes.FileNameMatcherFactory;
import java.util.List;
import java.util.function.Supplier;
/**
* Manages the relationship between filenames and {@link FileType} instances.
*/
public abstract class FileTypeManager extends FileTypeRegistry {
static {
FileTypeRegistry.setInstanceSupplier(FileTypeManager::getInstance);
}
private static final Supplier<FileTypeManager> ourInstance = CachedSingletonsRegistry.lazy(() -> {
Application app = ApplicationManager.getApplication();
return app == null ? new MockFileTypeManager() : app.getService(FileTypeManager.class);
});
public abstract class FileTypeManager extends FileTypeRegistry implements Disposable {
@Topic.AppLevel
public static final @NotNull Topic<FileTypeListener> TOPIC = new Topic<>(FileTypeListener.class, Topic.BroadcastDirection.TO_DIRECT_CHILDREN);
@@ -35,7 +23,15 @@ public abstract class FileTypeManager extends FileTypeRegistry {
*/
@SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
public static FileTypeManager getInstance() {
return ourInstance.get();
FileTypeRegistry instance = FileTypeRegistry.getInstance();
return instance instanceof FileTypeManager ftm ? ftm : new StubFileTypeManager();
}
protected FileTypeManager() {
}
@Override
public void dispose() {
}
/** @deprecated use {@code com.intellij.fileType} extension point instead */
@@ -8,7 +8,10 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
public final class MockFileTypeManager extends FileTypeManager {
/**
* empty implementation of {@link FileTypeManager} in case the {@link com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl} is not available
*/
final class StubFileTypeManager extends FileTypeManager {
@Override
public @NotNull FileType getFileTypeByFileName(@NotNull String fileName) {
return MockLanguageFileType.INSTANCE;
@@ -12,9 +12,11 @@ import org.jetbrains.annotations.Nullable;
* Substitutes type for files which users explicitly marked with "Override File Type" action
*/
final class UserFileTypeOverrider implements FileTypeOverrider {
private final OverrideFileTypeManager myOverrideFileTypeManager = OverrideFileTypeManager.getInstance();
@Override
public @Nullable FileType getOverriddenFileType(@NotNull VirtualFile file) {
String overriddenType = OverrideFileTypeManager.getInstance().getFileValue(file);
String overriddenType = myOverrideFileTypeManager.getFileValue(file);
if (overriddenType != null) {
return FileTypeManager.getInstance().findFileTypeByName(overriddenType);
}
@@ -13423,12 +13423,10 @@ f:com.intellij.openapi.fileTypes.impl.FileTypeBean
- setPluginDescriptor(com.intellij.openapi.extensions.PluginDescriptor):V
c:com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl
- com.intellij.openapi.fileTypes.ex.FileTypeManagerEx
- com.intellij.openapi.Disposable
- com.intellij.openapi.components.PersistentStateComponent
- com.intellij.openapi.extensions.ExtensionPointListener
- p:<init>(kotlinx.coroutines.CoroutineScope):V
- associate(com.intellij.openapi.fileTypes.FileType,com.intellij.openapi.fileTypes.FileNameMatcher):V
- dispose():V
- drainReDetectQueue():V
- extensionAdded(com.intellij.openapi.fileTypes.impl.FileTypeBean,com.intellij.openapi.extensions.PluginDescriptor):V
- extensionRemoved(com.intellij.openapi.fileTypes.impl.FileTypeBean,com.intellij.openapi.extensions.PluginDescriptor):V
@@ -13461,7 +13459,6 @@ c:com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl
- registerFileType(com.intellij.openapi.fileTypes.FileType,java.lang.String[]):V
- removeAssociation(com.intellij.openapi.fileTypes.FileType,com.intellij.openapi.fileTypes.FileNameMatcher):V
- removePlainTextAssociationsForFile(java.lang.CharSequence):V
- setConflictResultConsumer(java.util.function.Consumer):V
- setIgnoredFilesList(java.lang.String):V
- unregisterFileType(com.intellij.openapi.fileTypes.FileType,com.intellij.openapi.extensions.PluginDescriptor):V
*:com.intellij.openapi.fileTypes.impl.FileTypeOverrider
@@ -3,11 +3,13 @@ package com.intellij.ide.plugins
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.JsonGenerator
import com.intellij.openapi.application.*
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModernApplicationStarter
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.fileTypes.PlainTextLikeFileType
import com.intellij.openapi.progress.blockingContext
import com.intellij.util.io.jackson.array
import com.intellij.util.io.jackson.obj
import com.intellij.util.lang.UrlClassLoader
@@ -3,6 +3,7 @@ package com.intellij.openapi.fileChooser.impl
import com.intellij.core.CoreFileTypeRegistry
import com.intellij.ide.highlighter.ArchiveFileType
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.impl.LaterInvocator
import com.intellij.openapi.command.CommandProcessorEx
@@ -10,6 +11,7 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.SystemInfo
@@ -30,7 +32,7 @@ import java.awt.KeyboardFocusManager
import java.nio.file.FileSystems
import java.nio.file.Path
internal class FileChooserDialogHelper(private val descriptor: FileChooserDescriptor) {
internal class FileChooserDialogHelper(private val descriptor: FileChooserDescriptor) : Disposable {
@Suppress("SpellCheckingInspection")
private val ZIP_FS_TYPE = "zipfs"
@@ -46,10 +48,14 @@ internal class FileChooserDialogHelper(private val descriptor: FileChooserDescri
val registry = CoreFileTypeRegistry()
registry.registerFileType(ArchiveFileType.INSTANCE, "zip")
registry.registerFileType(ArchiveFileType.INSTANCE, "jar")
FileTypeRegistry.setInstanceSupplier { registry }
FileTypeRegistry.setInstanceSupplier( { registry }, this)
}
}
override fun dispose() {
}
fun setNativeDialogProperties() {
if (SystemInfo.isWindows) {
System.setProperty("sun.awt.windows.useCommonItemDialog", "true")
@@ -74,6 +80,7 @@ internal class FileChooserDialogHelper(private val descriptor: FileChooserDescri
IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown { previousFocusOwner.requestFocus() }
}
}
Disposer.dispose(this)
}
}
@@ -49,13 +49,13 @@ import org.jdom.Element;
import org.jetbrains.annotations.*;
import org.jetbrains.jps.model.fileTypes.FileNameMatcherFactory;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
@@ -68,7 +68,7 @@ import static com.intellij.openapi.fileTypes.impl.AlarmAdapterKt.singleAlarm;
storages = @Storage("filetypes.xml"),
additionalExportDirectory = FileTypeManagerImpl.FILE_SPEC,
category = SettingsCategory.CODE)
public class FileTypeManagerImpl extends FileTypeManagerEx implements PersistentStateComponent<Element>, Disposable, ExtensionPointListener<FileTypeBean> {
public class FileTypeManagerImpl extends FileTypeManagerEx implements PersistentStateComponent<Element>, ExtensionPointListener<FileTypeBean> {
static final ExtensionPointName<FileTypeBean> EP_NAME = new ExtensionPointName<>("com.intellij.fileType");
private static final Logger LOG = Logger.getInstance(FileTypeManagerImpl.class);
@@ -135,7 +135,9 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
private final Map<String, StandardFileType> standardFileTypes = new LinkedHashMap<>();
private final SchemeManager<FileTypeWithDescriptor> schemeManager;
protected FileTypeManagerImpl(@NotNull CoroutineScope coroutineScope) {
protected FileTypeManagerImpl(@NotNull CoroutineScope coroutineScope) throws IOException {
myDetectionService = new FileTypeDetectionService(this, coroutineScope);
NonLazySchemeProcessor<FileTypeWithDescriptor, FileTypeWithDescriptor> abstractTypesProcessor = new NonLazySchemeProcessor<>() {
@Override
public @NotNull FileTypeWithDescriptor readScheme(@NotNull Element element, boolean duringLoad) {
@@ -198,14 +200,25 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
};
schemeManager = SchemeManagerFactory.getInstance().create(FILE_SPEC, abstractTypesProcessor, null, null, SettingsCategory.CODE);
myDetectionService = new FileTypeDetectionService(this, coroutineScope);
Disposer.register(this, myDetectionService);
EP_NAME.addExtensionPointListener(this, this);
checkDuplicatedAlarm = singleAlarm(400, coroutineScope, this::checkUnique);
EP_NAME.addExtensionPointListener(this, this);
FileTypeOverrider.EP_NAME.addExtensionPointListener(new ExtensionPointListener<>() {
@Override
public void extensionAdded(FileTypeOverrider extension, @NotNull PluginDescriptor pluginDescriptor) {
fileTypeOverriderCache = null;
}
@Override
public void extensionRemoved(FileTypeOverrider extension, @NotNull PluginDescriptor pluginDescriptor) {
fileTypeOverriderCache = null;
}
});
}
private FileTypeOverrider[] fileTypeOverriderCache;
public void extensionAdded(@NotNull FileTypeBean fileTypeBean, @NotNull PluginDescriptor pluginDescriptor) {
fireBeforeFileTypesChanged();
initializeMatchers(pluginDescriptor, fileTypeBean);
@@ -363,6 +376,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
@TestOnly
void clearStandardFileTypesBeforeTest() {
assert ApplicationManager.getApplication().isUnitTestMode();
myPendingInitializationLock.writeLock().lock();
try {
pendingAssociations.clear();
@@ -473,22 +487,27 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
private @NotNull FileType mergeOrInstantiateFileTypeBean(@NotNull FileTypeBean fileTypeBean) {
StandardFileType type = withReadLock(() -> standardFileTypes.get(fileTypeBean.name));
if (type == null) {
return getFileTypeOrUnknown(instantiateFileTypeBean(fileTypeBean));
myPendingInitializationLock.writeLock().lock();
try {
StandardFileType type = standardFileTypes.get(fileTypeBean.name);
if (type == null) {
return getFileTypeOrUnknown(instantiateFileTypeBean(fileTypeBean));
}
type.matchers.addAll(fileTypeBean.getMatchers());
for (FileNameMatcher matcher : fileTypeBean.getMatchers()) {
patternsTable.addAssociation(matcher, type.getDescriptor());
}
return type.fileType;
}
type.matchers.addAll(fileTypeBean.getMatchers());
for (FileNameMatcher matcher : fileTypeBean.getMatchers()) {
patternsTable.addAssociation(matcher, type.getDescriptor());
finally {
myPendingInitializationLock.writeLock().unlock();
}
return type.fileType;
}
final Set<String> INSTANTIATED = PluginManagerCore.isUnitTestMode ? ContainerUtil.newConcurrentSet() : null;
private @Nullable FileTypeWithDescriptor instantiateFileTypeBean(@NotNull FileTypeBean bean) {
Lock writeLock = myPendingInitializationLock.writeLock();
writeLock.lock();
myPendingInitializationLock.writeLock().lock();
try {
String fileTypeName = bean.name;
// DCL
@@ -569,7 +588,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
return new FileTypeWithDescriptor(fileType, bean.getPluginDescriptor());
}
finally {
writeLock.unlock();
myPendingInitializationLock.writeLock().unlock();
}
}
@@ -740,38 +759,44 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
@Override
public boolean isFileOfType(@NotNull VirtualFile file, @NotNull FileType requestedFileType) {
public boolean isFileOfType(@NotNull VirtualFile virtualFile, @NotNull FileType requestedFileType) {
//MAYBE RC: shouldn't we first check file.isValid() and return false eagerly?
FileType temporarilyFixedFileType = getTemporarilyFixedFileType(file);
FileType temporarilyFixedFileType = getTemporarilyFixedFileType(virtualFile);
if (temporarilyFixedFileType != null) return temporarilyFixedFileType.equals(requestedFileType);
FileType overriddenFileType = FileTypeOverrider.EP_NAME.computeSafeIfAny(overrider -> overrider.getOverriddenFileType(file));
if (overriddenFileType != null) {
return overriddenFileType.equals(requestedFileType);
FileTypeOverrider[] overriders = fileTypeOverriderCache;
if (overriders == null) {
fileTypeOverriderCache = overriders = FileTypeOverrider.EP_NAME.getExtensions();
}
for (FileTypeOverrider overrider : overriders) {
FileType overriddenFileType = overrider.getOverriddenFileType(virtualFile);
if (overriddenFileType != null) {
return overriddenFileType.equals(requestedFileType);
}
}
if (file instanceof VirtualFileWithAssignedFileType) {
FileType fileType = ((VirtualFileWithAssignedFileType)file).getAssignedFileType();
if (virtualFile instanceof VirtualFileWithAssignedFileType) {
FileType fileType = ((VirtualFileWithAssignedFileType)virtualFile).getAssignedFileType();
if (fileType != null) {
return fileType.equals(requestedFileType);
}
}
if (requestedFileType instanceof FileTypeIdentifiableByVirtualFile
&& ((FileTypeIdentifiableByVirtualFile)requestedFileType).isMyFileType(file)) {
&& ((FileTypeIdentifiableByVirtualFile)requestedFileType).isMyFileType(virtualFile)) {
return true;
}
// otherwise, we can skip all the mySpecialFileTypes because it's certain this file type is not one of them
FileType fileType = getFileTypeByFileName(file.getNameSequence());
FileType fileType = getFileTypeByFileName(virtualFile.getNameSequence());
if (fileType == UnknownFileType.INSTANCE) {
fileType = null;
}
if (fileType == null && file instanceof FakeVirtualFile && ScratchUtil.isScratch(file.getParent())) {
if (fileType == null && virtualFile instanceof FakeVirtualFile && ScratchUtil.isScratch(virtualFile.getParent())) {
return PlainTextFileType.INSTANCE.equals(requestedFileType);
}
if (fileType == null || fileType == DetectedByContentFileType.INSTANCE) {
FileType detected = myDetectionService.getOrDetectFromContent(file, null, fileType);
FileType detected = myDetectionService.getOrDetectFromContent(virtualFile, null, fileType);
if (detected == UnknownFileType.INSTANCE && fileType == DetectedByContentFileType.INSTANCE) {
return DetectedByContentFileType.INSTANCE.equals(requestedFileType);
}
@@ -805,27 +830,33 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
}
@Override
public @NotNull FileType getFileTypeByFile(@NotNull VirtualFile file, byte @Nullable [] content) {
FileType temporarilyFixedFileType = getTemporarilyFixedFileType(file);
public @NotNull FileType getFileTypeByFile(@NotNull VirtualFile virtualFile, byte @Nullable [] content) {
FileType temporarilyFixedFileType = getTemporarilyFixedFileType(virtualFile);
if (temporarilyFixedFileType != null) return temporarilyFixedFileType;
FileType overriddenFileType = FileTypeOverrider.EP_NAME.computeSafeIfAny(overrider -> overrider.getOverriddenFileType(file));
if (overriddenFileType != null) {
return overriddenFileType;
FileTypeOverrider[] overriders = fileTypeOverriderCache;
if (overriders == null) {
fileTypeOverriderCache = overriders = FileTypeOverrider.EP_NAME.getExtensions();
}
for (FileTypeOverrider overrider : overriders) {
FileType overriddenFileType = overrider.getOverriddenFileType(virtualFile);
if (overriddenFileType != null) {
return overriddenFileType;
}
}
FileType fileType = getByFile(file);
if (file instanceof StubVirtualFile) {
if (fileType == null && content == null && file instanceof FakeVirtualFile) {
if (ScratchUtil.isScratch(file.getParent())) return PlainTextFileType.INSTANCE;
FileType fileType = getByFile(virtualFile);
if (virtualFile instanceof StubVirtualFile) {
if (fileType == null && content == null && virtualFile instanceof FakeVirtualFile) {
if (ScratchUtil.isScratch(virtualFile.getParent())) return PlainTextFileType.INSTANCE;
}
}
else if (fileType == null || fileType == DetectedByContentFileType.INSTANCE) {
fileType = internalContinueToDetectFileTypeByFile(file, content, fileType);
fileType = internalContinueToDetectFileTypeByFile(virtualFile, content, fileType);
}
FileType result = fileType == null ? UnknownFileType.INSTANCE : fileType;
CachedFileTypes cached = CACHED;
if (cached != null && file instanceof VirtualFileWithId vfid) {
if (cached != null && virtualFile instanceof VirtualFileWithId vfid) {
cached.fileTypes().put(vfid.getId(), result);
}
return result;
@@ -840,8 +871,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
private FileType getTemporarilyFixedFileType(@NotNull VirtualFile file) {
CachedFileTypes cached = CACHED;
FileType fileType = cached != null && file instanceof VirtualFileWithId vfid ? cached.fileTypes().get(vfid.getId()) : null;
return fileType;
return cached != null && file instanceof VirtualFileWithId vfid ? cached.fileTypes().get(vfid.getId()) : null;
}
// null means all conventional detect methods returned UnknownFileType.INSTANCE, have to detect from content
@@ -1002,9 +1032,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
FileTypeWithDescriptor ftd = FileTypeWithDescriptor.allFor(fileType);
List<FileNameMatcher> matchers = patternsTable.getAssociations(ftd);
// delete all records "extension xxx is removed from standard type X because the new fileType grabbed it to itself"
removedMappingTracker.removeIf(mapping -> {
return mapping.getFileTypeName().equals(fileType.getName()) || matchers.contains(mapping.getFileNameMatcher());
});
removedMappingTracker.removeIf(mapping -> mapping.getFileTypeName().equals(fileType.getName()) || matchers.contains(mapping.getFileNameMatcher()));
patternsTable.removeAllAssociations(ftd);
initialAssociations.removeAllAssociations(fileType);
schemeManager.removeScheme(fileType.getName());
@@ -1018,14 +1046,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
@Override
public FileType @NotNull [] getRegisteredFileTypes() {
instantiatePendingFileTypes();
Collection<FileTypeWithDescriptor> descriptors = getAllFileTypeWithDescriptors();
FileType[] result = new FileType[descriptors.size()];
int i = 0;
for (FileTypeManagerImpl.FileTypeWithDescriptor descriptor : descriptors) {
result[i++] = descriptor.fileType();
}
return result;
return ContainerUtil.map2Array(getRegisteredFileTypeWithDescriptors(), FileType.class, ftd->ftd.fileType);
}
@NotNull List<FileTypeWithDescriptor> getRegisteredFileTypeWithDescriptors() {
@@ -1326,65 +1347,67 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
@Override
public @NotNull Element getState() {
Element state = new Element("state");
return withReadLock(() -> {
Element state = new Element("state");
List<String> ignoreFiles = new ArrayList<>(ignoredPatterns.getIgnoreMasks());
ignoreFiles.sort(null);
if (!isEqualToDefaultIgnoreMasks(ignoreFiles)) {
// empty means empty list - we need to distinguish null and empty to apply or not to apply default value
state.addContent(new Element(ELEMENT_IGNORE_FILES).setAttribute(ATTRIBUTE_LIST, String.join(";", ignoreFiles)));
}
Element extensionMap = new Element(ELEMENT_EXTENSION_MAP);
List<FileTypeWithDescriptor> notExternalizableFileTypes = new ArrayList<>();
for (FileTypeWithDescriptor fileTypeDescriptor: getAllFileTypeWithDescriptors()) {
if (!(fileTypeDescriptor.fileType() instanceof AbstractFileType) || defaultTypes.contains(fileTypeDescriptor)) {
notExternalizableFileTypes.add(fileTypeDescriptor);
List<String> ignoreFiles = new ArrayList<>(ignoredPatterns.getIgnoreMasks());
ignoreFiles.sort(null);
if (!isEqualToDefaultIgnoreMasks(ignoreFiles)) {
// empty means empty list - we need to distinguish null and empty to apply or not to apply default value
state.addContent(new Element(ELEMENT_IGNORE_FILES).setAttribute(ATTRIBUTE_LIST, String.join(";", ignoreFiles)));
}
}
if (!notExternalizableFileTypes.isEmpty()) {
notExternalizableFileTypes.sort(Comparator.comparing(it -> it.fileType().getName()));
for (FileTypeWithDescriptor ftd : notExternalizableFileTypes) {
writeExtensionsMap(extensionMap, ftd, true);
}
}
// https://youtrack.jetbrains.com/issue/IDEA-138366
removedMappingTracker.save(extensionMap);
Element extensionMap = new Element(ELEMENT_EXTENSION_MAP);
if (!unresolvedMappings.isEmpty()) {
List<Map.Entry<FileNameMatcher, String>> entries = new ArrayList<>(unresolvedMappings.entrySet());
entries.sort(Comparator.comparing(e -> e.getKey().getPresentableString()));
for (Map.Entry<FileNameMatcher, String> entry : entries) {
FileNameMatcher fileNameMatcher = entry.getKey();
String typeName = entry.getValue();
Element content = AbstractFileType.writeMapping(typeName, fileNameMatcher, true);
if (content != null) {
extensionMap.addContent(content);
List<FileTypeWithDescriptor> notExternalizableFileTypes = new ArrayList<>();
for (FileTypeWithDescriptor fileTypeDescriptor: getAllFileTypeWithDescriptors()) {
if (!(fileTypeDescriptor.fileType() instanceof AbstractFileType) || defaultTypes.contains(fileTypeDescriptor)) {
notExternalizableFileTypes.add(fileTypeDescriptor);
}
}
}
if (!unresolvedHashBangs.isEmpty()) {
List<Map.Entry<String, String>> entries = new ArrayList<>(unresolvedHashBangs.entrySet());
entries.sort(Map.Entry.comparingByKey());
for (Map.Entry<String, String> entry : entries) {
String pattern = entry.getKey();
String typeName = entry.getValue();
writeHashBang(extensionMap, pattern, typeName);
if (!notExternalizableFileTypes.isEmpty()) {
notExternalizableFileTypes.sort(Comparator.comparing(it -> it.fileType().getName()));
for (FileTypeWithDescriptor ftd : notExternalizableFileTypes) {
writeExtensionsMap(extensionMap, ftd, true);
}
}
}
if (!extensionMap.getChildren().isEmpty()) {
state.addContent(extensionMap);
}
// https://youtrack.jetbrains.com/issue/IDEA-138366
removedMappingTracker.save(extensionMap);
if (!state.getChildren().isEmpty()) {
state.setAttribute(ATTRIBUTE_VERSION, String.valueOf(VERSION));
}
if (!unresolvedMappings.isEmpty()) {
List<Map.Entry<FileNameMatcher, String>> entries = new ArrayList<>(unresolvedMappings.entrySet());
entries.sort(Comparator.comparing(e -> e.getKey().getPresentableString()));
for (Map.Entry<FileNameMatcher, String> entry : entries) {
FileNameMatcher fileNameMatcher = entry.getKey();
String typeName = entry.getValue();
Element content = AbstractFileType.writeMapping(typeName, fileNameMatcher, true);
if (content != null) {
extensionMap.addContent(content);
}
}
}
if (!unresolvedHashBangs.isEmpty()) {
List<Map.Entry<String, String>> entries = new ArrayList<>(unresolvedHashBangs.entrySet());
entries.sort(Map.Entry.comparingByKey());
return state;
for (Map.Entry<String, String> entry : entries) {
String pattern = entry.getKey();
String typeName = entry.getValue();
writeHashBang(extensionMap, pattern, typeName);
}
}
if (!extensionMap.getChildren().isEmpty()) {
state.addContent(extensionMap);
}
if (!state.getChildren().isEmpty()) {
state.setAttribute(ATTRIBUTE_VERSION, String.valueOf(VERSION));
}
return state;
});
}
private static boolean isEqualToDefaultIgnoreMasks(@NotNull List<String> newList) {
@@ -1634,33 +1657,39 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
if (isDefault && fileTypeDescr != null && fileTypeDescr.contains("syntax highlighting only")) {
fileTypeDescr = fileTypeDescr.replace("syntax highlighting only", FileTypesBundle.message("filetype.default.syntax.highlighting.only.description"));
}
String iconPath = typeElement.getAttributeValue("icon");
setFileTypeAttributes(type, fileTypeName, fileTypeDescr, iconPath);
registerFileTypeWithoutNotification(type,
pluginDescriptor,
parseExtensions(context, extensionsStr),
pluginAdvertiserExtensionsStateService,
isDefault);
myPendingInitializationLock.writeLock().lock();
try {
String iconPath = typeElement.getAttributeValue("icon");
setFileTypeAttributes(type, fileTypeName, fileTypeDescr, iconPath);
registerFileTypeWithoutNotification(type,
pluginDescriptor,
parseExtensions(context, extensionsStr),
pluginAdvertiserExtensionsStateService,
isDefault);
if (isDefault) {
defaultTypes.add(new FileTypeWithDescriptor(type, pluginDescriptor));
if (type instanceof ExternalizableFileType) {
((ExternalizableFileType)type).markDefaultSettings();
}
}
else {
Element extensions = typeElement.getChild(ELEMENT_EXTENSION_MAP);
if (extensions != null) {
FileTypeWithDescriptor newFtd = new FileTypeWithDescriptor(type, pluginDescriptor);
for (Pair<FileNameMatcher, String> association : AbstractFileType.readAssociations(extensions)) {
associate(newFtd, association.getFirst(), false);
}
for (RemovedMappingTracker.RemovedMapping removedAssociation : RemovedMappingTracker.readRemovedMappings(extensions)) {
removeAssociation(newFtd, removedAssociation.getFileNameMatcher(), false);
if (isDefault) {
defaultTypes.add(new FileTypeWithDescriptor(type, pluginDescriptor));
if (type instanceof ExternalizableFileType) {
((ExternalizableFileType)type).markDefaultSettings();
}
}
else {
Element extensions = typeElement.getChild(ELEMENT_EXTENSION_MAP);
if (extensions != null) {
FileTypeWithDescriptor newFtd = new FileTypeWithDescriptor(type, pluginDescriptor);
for (Pair<FileNameMatcher, String> association : AbstractFileType.readAssociations(extensions)) {
associate(newFtd, association.getFirst(), false);
}
for (RemovedMappingTracker.RemovedMapping removedAssociation : RemovedMappingTracker.readRemovedMappings(extensions)) {
removeAssociation(newFtd, removedAssociation.getFileNameMatcher(), false);
}
}
}
return type;
}
finally {
myPendingInitializationLock.writeLock().unlock();
}
return type;
}
private @NotNull String filterAlreadyRegisteredExtensions(@NotNull String semicolonDelimited) {
@@ -1705,9 +1734,6 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
return patternsTable;
}
@Override
public void dispose() { }
void setPatternsTable(@NotNull Set<FileTypeWithDescriptor> fileTypes, @NotNull FileTypeAssocTable<FileTypeWithDescriptor> assocTable) {
Map<FileNameMatcher, FileTypeWithDescriptor> removedMappings = getExtensionMap().getRemovedMappings(assocTable, fileTypes);
String message = "set patterns table called: file types " + fileTypes + ", ass. table:" + assocTable;
@@ -1770,15 +1796,21 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
private void registerReDetectedMappings(@NotNull StandardFileType pair) {
FileType fileType = pair.fileType;
if (fileType == PlainTextFileType.INSTANCE) return;
for (FileNameMatcher matcher : pair.matchers) {
registerReDetectedMapping(fileType.getName(), matcher);
if (matcher instanceof ExtensionFileNameMatcher extMatcher) {
// also check exact file name matcher
registerReDetectedMapping(fileType.getName(), new ExactFileNameMatcher("." + extMatcher.getExtension()));
myPendingInitializationLock.writeLock().lock();
try {
FileType fileType = pair.fileType;
if (fileType == PlainTextFileType.INSTANCE) return;
for (FileNameMatcher matcher : pair.matchers) {
registerReDetectedMapping(fileType.getName(), matcher);
if (matcher instanceof ExtensionFileNameMatcher extMatcher) {
// also check exact file name matcher
registerReDetectedMapping(fileType.getName(), new ExactFileNameMatcher("." + extMatcher.getExtension()));
}
}
}
finally {
myPendingInitializationLock.writeLock().unlock();
}
}
private void registerReDetectedMapping(@NotNull String fileTypeName, @NotNull FileNameMatcher matcher) {
@@ -1870,8 +1902,37 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent
}
@TestOnly
public void setConflictResultConsumer(@Nullable Consumer<? super ConflictingFileTypeMappingTracker.ResolveConflictResult> consumer) {
void setConflictResultConsumer(@Nullable Consumer<? super ConflictingFileTypeMappingTracker.ResolveConflictResult> consumer) {
if (!ApplicationManager.getApplication().isUnitTestMode()) throw new IllegalStateException();
conflictResultConsumer = consumer;
}
@Override
public String toString() {
return super.toString() + " FileTypeManagerImpl{" +
"myDetectionService=" + myDetectionService +
", CACHED=" + CACHED +
", defaultTypes=" + defaultTypes +
", specialFileTypes=" + Arrays.toString(specialFileTypes) +
", patternsTable=" + patternsTable +
", ignoredPatterns=" + ignoredPatterns +
", myIgnoredFileCache=" + myIgnoredFileCache +
", initialAssociations=" + initialAssociations +
", unresolvedMappings=" + unresolvedMappings +
", unresolvedHashBangs=" + unresolvedHashBangs +
", removedMappingTracker=" + removedMappingTracker +
", conflictingMappingTracker=" + conflictingMappingTracker +
", pendingFileTypes=" + pendingFileTypes +
", pendingAssociations=" + pendingAssociations +
", myPendingInitializationLock=" + myPendingInitializationLock +
", fileTypesPerPlugin=" + fileTypesPerPlugin +
", checkDuplicatedAlarm=" + checkDuplicatedAlarm +
", conflictResultConsumer=" + conflictResultConsumer +
", standardFileTypes=" + standardFileTypes +
", schemeManager=" + schemeManager +
", fileTypeOverriderCache=" + Arrays.toString(fileTypeOverriderCache) +
", INSTANTIATED=" + INSTANTIATED +
", toLog=" + toLog +
'}';
}
}
@@ -7,7 +7,6 @@ import com.intellij.ide.highlighter.ArchiveFileType;
import com.intellij.ide.highlighter.ModuleFileType;
import com.intellij.ide.highlighter.ProjectFileType;
import com.intellij.ide.highlighter.WorkspaceFileType;
import com.intellij.ide.highlighter.custom.SyntaxTable;
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
import com.intellij.ide.plugins.PluginDescriptorTestKt;
import com.intellij.ide.plugins.PluginManagerCore;
@@ -1086,7 +1085,6 @@ public class FileTypesTest extends HeavyPlatformTestCase {
bean.setPluginDescriptor(pluginDescriptor);
Disposable disposable = Disposer.newDisposable();
WriteAction.run(() -> FileTypeManagerImpl.EP_NAME.getPoint().registerExtension(bean, disposable));
((FileTypeManagerImpl)FileTypeManager.getInstance()).extensionRemoved(bean, pluginDescriptor); // avoid contamination of production FileTypeManager
return disposable;
}
@@ -1324,11 +1322,6 @@ public class FileTypesTest extends HeavyPlatformTestCase {
assertInstanceOf(myFileTypeManager.getFileTypeByFileName("foo.hs"), MyHaskellFileType.class);
WriteAction.run(() -> Disposer.dispose(disposable));
// todo restore old AbstractFileType automatically?
AbstractFileType old = new AbstractFileType(new SyntaxTable());
old.setName(MyHaskellFileType.NAME);
myFileTypeManager.registerFileType(old, List.of(), myFileTypeManager, FileTypeManagerImpl.coreIdeaPluginDescriptor());
}
private static class MyCustomImageFileType implements FileType {
@@ -7,6 +7,7 @@ package org.toml.lang.psi
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.fileTypes.ex.FileTypeIdentifiableByVirtualFile
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.TokenSet
@@ -27,7 +28,7 @@ object TomlFileType : LanguageFileType(TomlLanguage), FileTypeIdentifiableByVirt
override fun getCharset(file: VirtualFile, content: ByteArray): String = "UTF-8"
override fun isMyFileType(file: VirtualFile): Boolean {
return file.name == "config" && file.parent?.name == ".cargo"
return StringUtil.equal(file.nameSequence, "config", true) && file.parent?.name == ".cargo"
}
}