1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.internal.statistic.collectors.fus.fileTypes;
4 import com.intellij.internal.statistic.eventLog.*;
5 import com.intellij.internal.statistic.eventLog.fus.FeatureUsageLogger;
6 import com.intellij.internal.statistic.eventLog.validator.ValidationResultType;
7 import com.intellij.internal.statistic.eventLog.validator.rules.EventContext;
8 import com.intellij.internal.statistic.eventLog.validator.rules.impl.CustomValidationRule;
9 import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector;
10 import com.intellij.openapi.actionSystem.AnAction;
11 import com.intellij.openapi.actionSystem.AnActionEvent;
12 import com.intellij.openapi.actionSystem.CommonDataKeys;
13 import com.intellij.openapi.actionSystem.DataContext;
14 import com.intellij.openapi.actionSystem.ex.AnActionListener;
15 import com.intellij.openapi.diagnostic.Logger;
16 import com.intellij.openapi.editor.Editor;
17 import com.intellij.openapi.editor.actionSystem.EditorAction;
18 import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
19 import com.intellij.openapi.extensions.ExtensionPointName;
20 import com.intellij.openapi.fileEditor.FileDocumentManager;
21 import com.intellij.openapi.fileEditor.FileEditorManager;
22 import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
23 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
24 import com.intellij.openapi.fileTypes.FileType;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Key;
27 import com.intellij.openapi.util.text.StringUtil;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.serviceContainer.BaseKeyedLazyInstance;
30 import com.intellij.util.ArrayUtil;
31 import com.intellij.util.KeyedLazyInstance;
32 import com.intellij.util.xmlb.annotations.Attribute;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
36 import static com.intellij.internal.statistic.utils.PluginInfoDetectorKt.getPluginInfo;
38 public class FileTypeUsageCounterCollector extends CounterUsagesCollector {
39 private static final Logger LOG = Logger.getInstance(FileTypeUsageCounterCollector.class);
41 private static final ExtensionPointName<FileTypeUsageSchemaDescriptorEP<FileTypeUsageSchemaDescriptor>> EP =
42 ExtensionPointName.create("com.intellij.fileTypeUsageSchemaDescriptor");
44 private static final EventLogGroup GROUP = new EventLogGroup("file.types.usage", FeatureUsageLogger.INSTANCE.getConfig().getVersion());
46 private static final EventField<String> FILE_TYPE = EventFields.String("file_type").withCustomRule("file_type");
47 private static final EventField<String> SCHEMA = EventFields.String("schema").withCustomRule("file_type_schema");
48 private static final EventField<Boolean> IS_WRITABLE = EventFields.Boolean("is_writable");
51 public EventLogGroup getGroup() {
55 private static VarargEventId registerFileTypeEvent(String eventId, EventField<?> ... extraFields) {
56 EventField<?>[] baseFields = {EventFields.PluginInfoFromInstance, FILE_TYPE, EventFields.AnonymizedPath, SCHEMA};
57 return GROUP.registerVarargEvent(eventId, ArrayUtil.mergeArrays(baseFields, extraFields));
60 private static final VarargEventId SELECT = registerFileTypeEvent("select");
61 private static final VarargEventId EDIT = registerFileTypeEvent("edit");
62 private static final VarargEventId OPEN = registerFileTypeEvent("open", IS_WRITABLE);
63 private static final VarargEventId CLOSE = registerFileTypeEvent("close", IS_WRITABLE);
65 public static void triggerEdit(@NotNull Project project, @NotNull VirtualFile file) {
66 log(EDIT, project, file);
69 public static void triggerSelect(@NotNull Project project, @Nullable VirtualFile file) {
71 log(SELECT, project, file);
78 public static void triggerOpen(@NotNull Project project, @NotNull VirtualFile file) {
79 OPEN.log(project, ArrayUtil.append(buildCommonEventPairs(file), IS_WRITABLE.with(file.isWritable())));
82 public static void triggerClosed(@NotNull Project project, @NotNull VirtualFile file) {
83 CLOSE.log(project, ArrayUtil.append(buildCommonEventPairs(file), IS_WRITABLE.with(file.isWritable())));
86 private static void log(@NotNull VarargEventId eventId, @NotNull Project project, @NotNull VirtualFile file) {
87 eventId.log(project, buildCommonEventPairs(file));
90 private static EventPair<?> @NotNull [] buildCommonEventPairs(@NotNull VirtualFile file) {
91 FileType fileType = file.getFileType();
92 return new EventPair[]{EventFields.PluginInfoFromInstance.with(fileType),
93 FILE_TYPE.with(FileTypeUsagesCollector.getSafeFileTypeName(fileType)),
94 EventFields.AnonymizedPath.with(file.getPath()),
95 SCHEMA.with(findSchema(file))};
98 private static void logEmptyFile() {
99 SELECT.log(EventFields.AnonymizedPath.with(null));
102 public static @Nullable String findSchema(@NotNull VirtualFile file) {
103 for (FileTypeUsageSchemaDescriptorEP<FileTypeUsageSchemaDescriptor> ext : EP.getExtensionList()) {
104 FileTypeUsageSchemaDescriptor instance = ext.getInstance();
105 if (ext.schema == null) {
106 LOG.warn("Extension " + ext.implementationClass + " should define a 'schema' attribute");
110 if(instance.describes(file)) {
111 return getPluginInfo(instance.getClass()).isSafeToReport() ? ext.schema : "third.party";
117 public static final class FileTypeUsageSchemaDescriptorEP<T> extends BaseKeyedLazyInstance<T> implements KeyedLazyInstance<T> {
118 // these must be public for scrambling compatibility
120 public String schema;
122 @Attribute("implementationClass")
123 public String implementationClass;
127 protected String getImplementationClassName() {
128 return implementationClass;
132 public String getKey() {
137 public static final class FileTypeSchemaValidator extends CustomValidationRule {
140 public boolean acceptRuleId(@Nullable String ruleId) {
141 return "file_type_schema".equals(ruleId);
146 protected ValidationResultType doValidate(@NotNull String data, @NotNull EventContext context) {
147 if (isThirdPartyValue(data)) return ValidationResultType.ACCEPTED;
149 for (FileTypeUsageSchemaDescriptorEP<FileTypeUsageSchemaDescriptor> ext : EP.getExtensionList()) {
150 if (StringUtil.equals(ext.schema, data)) {
151 return getPluginInfo(ext.getInstance().getClass()).isSafeToReport() ?
152 ValidationResultType.ACCEPTED : ValidationResultType.THIRD_PARTY;
155 return ValidationResultType.REJECTED;
159 public static class MyAnActionListener implements AnActionListener {
160 private static final Key<Long> LAST_EDIT_USAGE = Key.create("LAST_EDIT_USAGE");
163 public void beforeActionPerformed(@NotNull AnAction action, @NotNull DataContext dataContext, @NotNull AnActionEvent event) {
164 if (action instanceof EditorAction && ((EditorAction)action).getHandlerOfType(EditorWriteActionHandler.class) != null) {
165 onChange(dataContext);
169 private static void onChange(DataContext dataContext) {
170 final Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
171 if (editor == null) return;
172 Project project = editor.getProject();
173 if (project == null) return;
174 VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument());
176 Long lastEdit = editor.getUserData(LAST_EDIT_USAGE);
177 if (lastEdit == null || System.currentTimeMillis() - lastEdit > 60 * 1000) {
178 editor.putUserData(LAST_EDIT_USAGE, System.currentTimeMillis());
179 triggerEdit(project, file);
185 public void beforeEditorTyping(char c, @NotNull DataContext dataContext) {
186 onChange(dataContext);
190 public static class MyFileEditorManagerListener implements FileEditorManagerListener {
192 public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
193 triggerOpen(source.getProject(), file);
197 public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
198 triggerClosed(source.getProject(), file);
202 public void selectionChanged(@NotNull FileEditorManagerEvent event) {
203 triggerSelect(event.getManager().getProject(), event.getNewFile());