cleanup (inspection "Java | Class structure | Utility class is not 'final'")
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / impl / DataManagerImpl.java
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.ide.impl;
3
4 import com.intellij.ide.DataManager;
5 import com.intellij.ide.IdeEventQueue;
6 import com.intellij.ide.ProhibitAWTEvents;
7 import com.intellij.ide.impl.dataRules.*;
8 import com.intellij.openapi.actionSystem.*;
9 import com.intellij.openapi.application.AccessToken;
10 import com.intellij.openapi.application.ApplicationManager;
11 import com.intellij.openapi.application.ModalityState;
12 import com.intellij.openapi.diagnostic.Logger;
13 import com.intellij.openapi.editor.Editor;
14 import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher;
15 import com.intellij.openapi.progress.ProgressManager;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.util.Key;
18 import com.intellij.openapi.util.KeyedExtensionCollector;
19 import com.intellij.openapi.util.UserDataHolder;
20 import com.intellij.openapi.util.registry.Registry;
21 import com.intellij.openapi.wm.IdeFocusManager;
22 import com.intellij.openapi.wm.IdeFrame;
23 import com.intellij.openapi.wm.WindowManager;
24 import com.intellij.openapi.wm.ex.WindowManagerEx;
25 import com.intellij.openapi.wm.impl.FloatingDecorator;
26 import com.intellij.reference.SoftReference;
27 import com.intellij.util.ReflectionUtil;
28 import com.intellij.util.containers.ContainerUtil;
29 import com.intellij.util.ui.SwingHelper;
30 import com.intellij.util.ui.UIUtil;
31 import gnu.trove.THashSet;
32 import org.jetbrains.annotations.NonNls;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.jetbrains.concurrency.AsyncPromise;
36 import org.jetbrains.concurrency.Promise;
37
38 import javax.swing.*;
39 import java.awt.*;
40 import java.lang.ref.Reference;
41 import java.lang.ref.WeakReference;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.concurrent.ConcurrentHashMap;
45 import java.util.concurrent.ConcurrentMap;
46 import java.util.stream.Stream;
47
48 public class DataManagerImpl extends DataManager {
49   private static final Logger LOG = Logger.getInstance(DataManagerImpl.class);
50   private final ConcurrentMap<String, GetDataRule> myDataConstantToRuleMap = new ConcurrentHashMap<>();
51
52   private final KeyedExtensionCollector<GetDataRule, String> myDataRuleCollector = new KeyedExtensionCollector<>(GetDataRule.EP_NAME);
53
54   public DataManagerImpl() {
55     myDataConstantToRuleMap.put(PlatformDataKeys.COPY_PROVIDER.getName(), new CopyProviderRule());
56     myDataConstantToRuleMap.put(PlatformDataKeys.CUT_PROVIDER.getName(), new CutProviderRule());
57     myDataConstantToRuleMap.put(PlatformDataKeys.PASTE_PROVIDER.getName(), new PasteProviderRule());
58     myDataConstantToRuleMap.put(PlatformDataKeys.FILE_TEXT.getName(), new FileTextRule());
59     myDataConstantToRuleMap.put(PlatformDataKeys.FILE_EDITOR.getName(), new FileEditorRule());
60     myDataConstantToRuleMap.put(CommonDataKeys.NAVIGATABLE_ARRAY.getName(), new NavigatableArrayRule());
61     myDataConstantToRuleMap.put(CommonDataKeys.EDITOR_EVEN_IF_INACTIVE.getName(), new InactiveEditorRule());
62   }
63
64   private @Nullable Object getData(@NotNull String dataId, final Component focusedComponent) {
65     GetDataRule rule = getDataRule(dataId);
66     try (AccessToken ignored = ProhibitAWTEvents.start("getData")) {
67       for (Component c = focusedComponent; c != null; c = c.getParent()) {
68         final DataProvider dataProvider = getDataProviderEx(c);
69         if (dataProvider == null) continue;
70         Object data = getDataFromProvider(dataProvider, dataId, null, rule);
71         if (data != null) return data;
72       }
73     }
74     return null;
75   }
76
77   public @Nullable Object getDataFromProvider(final @NotNull DataProvider provider, @NotNull String dataId, @Nullable Set<String> alreadyComputedIds) {
78     return getDataFromProvider(provider, dataId, alreadyComputedIds, getDataRule(dataId));
79   }
80
81   private @Nullable Object getDataFromProvider(@NotNull DataProvider provider,
82                                                @NotNull String dataId,
83                                                @Nullable Set<String> alreadyComputedIds,
84                                                @Nullable GetDataRule dataRule) {
85     ProgressManager.checkCanceled();
86     if (alreadyComputedIds != null && alreadyComputedIds.contains(dataId)) {
87       return null;
88     }
89     try {
90       Object data = provider.getData(dataId);
91       if (data != null) return validated(data, dataId, provider);
92
93       if (dataRule != null) {
94         final Set<String> ids = alreadyComputedIds == null ? new THashSet<>() : alreadyComputedIds;
95         ids.add(dataId);
96         data = dataRule.getData(id -> getDataFromProvider(provider, id, ids));
97
98         if (data != null) return validated(data, dataId, provider);
99       }
100
101       return null;
102     }
103     finally {
104       if (alreadyComputedIds != null) alreadyComputedIds.remove(dataId);
105     }
106   }
107
108   public static @Nullable DataProvider getDataProviderEx(Object component) {
109     DataProvider dataProvider = null;
110     if (component instanceof DataProvider) {
111       dataProvider = (DataProvider)component;
112     }
113     else if (component instanceof TypeSafeDataProvider) {
114       dataProvider = new TypeSafeDataProviderAdapter((TypeSafeDataProvider) component);
115     }
116     else if (component instanceof JComponent) {
117       dataProvider = getDataProvider((JComponent)component);
118     }
119
120     return dataProvider;
121   }
122
123   public @Nullable GetDataRule getDataRule(@NotNull String dataId) {
124     GetDataRule rule = getRuleFromMap(dataId);
125     if (rule != null) {
126       return rule;
127     }
128
129     final GetDataRule plainRule = getRuleFromMap(AnActionEvent.uninjectedId(dataId));
130     if (plainRule != null) {
131       return dataProvider -> plainRule.getData(id -> dataProvider.getData(AnActionEvent.injectedId(id)));
132     }
133
134     return null;
135   }
136
137   private @Nullable GetDataRule getRuleFromMap(@NotNull String dataId) {
138     GetDataRule rule = myDataConstantToRuleMap.get(dataId);
139     if (rule != null) {
140       return rule;
141     }
142     return myDataRuleCollector.findSingle(dataId);
143   }
144
145   private static @Nullable Object validated(@NotNull Object data, @NotNull String dataId, @NotNull Object dataSource) {
146     Object invalidData = DataValidator.findInvalidData(dataId, data, dataSource);
147     if (invalidData != null) {
148       return null;
149       /*
150       LOG.assertTrue(false, "Data isn't valid. " + dataId + "=" + invalidData + " Provided by: " + dataSource.getClass().getName() + " (" +
151                             dataSource.toString() + ")");
152       */
153     }
154     return data;
155   }
156
157   @Override
158   public @NotNull DataContext getDataContext(Component component) {
159     return new MyDataContext(component);
160   }
161
162   @Override
163   public @NotNull DataContext getDataContext(@NotNull Component component, int x, int y) {
164     if (x < 0 || x >= component.getWidth() || y < 0 || y >= component.getHeight()) {
165       throw new IllegalArgumentException("wrong point: x=" + x + "; y=" + y);
166     }
167
168     // Point inside JTabbedPane has special meaning. If point is inside tab bounds then
169     // we construct DataContext by the component which corresponds to the (x, y) tab.
170     if (component instanceof JTabbedPane) {
171       JTabbedPane tabbedPane = (JTabbedPane)component;
172       int index = tabbedPane.getUI().tabForCoordinate(tabbedPane, x, y);
173       return getDataContext(index != -1 ? tabbedPane.getComponentAt(index) : tabbedPane);
174     }
175     else {
176       return getDataContext(component);
177     }
178   }
179
180   @Override
181   public @NotNull DataContext getDataContext() {
182     Component component = null;
183     if (Registry.is("actionSystem.getContextByRecentMouseEvent")) {
184       component = SwingHelper.getComponentFromRecentMouseEvent();
185     }
186     return getDataContext(component != null ? component : getFocusedComponent());
187   }
188
189   @Override
190   public @NotNull Promise<DataContext> getDataContextFromFocusAsync() {
191     AsyncPromise<DataContext> result = new AsyncPromise<>();
192     IdeFocusManager.getGlobalInstance()
193                    .doWhenFocusSettlesDown(() -> result.setResult(getDataContext()), ModalityState.any());
194     return result;
195   }
196
197   public @NotNull DataContext getDataContextTest(Component component) {
198     DataContext dataContext = getDataContext(component);
199
200     WindowManager windowManager = WindowManager.getInstance();
201     if (!(windowManager instanceof WindowManagerEx)) {
202       return dataContext;
203     }
204
205     Project project = CommonDataKeys.PROJECT.getData(dataContext);
206     Component focusedComponent = ((WindowManagerEx)windowManager).getFocusedComponent(project);
207     if (focusedComponent != null) {
208       dataContext = getDataContext(focusedComponent);
209     }
210     return dataContext;
211   }
212
213   private static @Nullable Component getFocusedComponent() {
214     WindowManager windowManager = WindowManager.getInstance();
215     if (!(windowManager instanceof WindowManagerEx)) {
216       return null;
217     }
218
219     WindowManagerEx windowManagerEx = (WindowManagerEx)windowManager;
220     Window activeWindow = windowManagerEx.getMostRecentFocusedWindow();
221     if (activeWindow == null) {
222       activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
223       if (activeWindow == null) {
224         activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
225         if (activeWindow == null) return null;
226       }
227     }
228
229     // In case we have an active floating toolwindow and some component in another window focused,
230     // we want this other component to receive key events.
231     // Walking up the window ownership hierarchy from the floating toolwindow would have led us to the main IdeFrame
232     // whereas we want to be able to type in other frames as well.
233     if (activeWindow instanceof FloatingDecorator) {
234       IdeFocusManager ideFocusManager = IdeFocusManager.findInstanceByComponent(activeWindow);
235       IdeFrame lastFocusedFrame = ideFocusManager.getLastFocusedFrame();
236       JComponent frameComponent = lastFocusedFrame != null ? lastFocusedFrame.getComponent() : null;
237       Window lastFocusedWindow = frameComponent != null ? SwingUtilities.getWindowAncestor(frameComponent) : null;
238       boolean toolWindowIsNotFocused = windowManagerEx.getFocusedComponent(activeWindow) == null;
239       if (toolWindowIsNotFocused && lastFocusedWindow != null) {
240         activeWindow = lastFocusedWindow;
241       }
242     }
243
244     // try to find first parent window that has focus
245     Window window = activeWindow;
246     Component focusedComponent = null;
247     while (window != null) {
248       focusedComponent = windowManagerEx.getFocusedComponent(window);
249       if (focusedComponent != null) {
250         break;
251       }
252       window = window.getOwner();
253     }
254     if (focusedComponent == null) {
255       focusedComponent = activeWindow;
256     }
257
258     return focusedComponent;
259   }
260
261   @Override
262   public <T> void saveInDataContext(DataContext dataContext, @NotNull Key<T> dataKey, @Nullable T data) {
263     if (dataContext instanceof UserDataHolder) {
264       ((UserDataHolder)dataContext).putUserData(dataKey, data);
265     }
266   }
267
268   @Override
269   public @Nullable <T> T loadFromDataContext(@NotNull DataContext dataContext, @NotNull Key<T> dataKey) {
270     return dataContext instanceof UserDataHolder ? ((UserDataHolder)dataContext).getUserData(dataKey) : null;
271   }
272
273   public static @Nullable Editor validateEditor(Editor editor) {
274     Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
275     if (focusOwner instanceof JComponent) {
276       final JComponent jComponent = (JComponent)focusOwner;
277       if (jComponent.getClientProperty(UIUtil.HIDE_EDITOR_FROM_DATA_CONTEXT_PROPERTY) != null) return null;
278     }
279
280     return editor;
281   }
282
283   private static final class NullResult {
284     public static final NullResult INSTANCE = new NullResult();
285   }
286
287   private static final Set<String> ourSafeKeys = ContainerUtil.set(
288     CommonDataKeys.PROJECT.getName(),
289     CommonDataKeys.EDITOR.getName(),
290     PlatformDataKeys.IS_MODAL_CONTEXT.getName(),
291     PlatformDataKeys.CONTEXT_COMPONENT.getName(),
292     PlatformDataKeys.MODALITY_STATE.getName()
293   );
294
295   /**
296    * todo make private in 2020
297    * @deprecated use {@link DataManager#getDataContext(Component)} instead
298    */
299   @Deprecated
300   public static class MyDataContext implements DataContext, UserDataHolder {
301     private int myEventCount;
302     // To prevent memory leak we have to wrap passed component into
303     // the weak reference. For example, Swing often remembers menu items
304     // that have DataContext as a field.
305     private final Reference<Component> myRef;
306     private Map<Key<?>, Object> myUserData;
307     private final Map<String, Object> myCachedData = ContainerUtil.createWeakValueMap();
308
309     public MyDataContext(@Nullable Component component) {
310       myEventCount = -1;
311       myRef = component == null ? null : new WeakReference<>(component);
312     }
313
314     public void setEventCount(int eventCount) {
315       assert ReflectionUtil.getCallerClass(3) == IdeKeyEventDispatcher.class :
316         "This method might be accessible from " + IdeKeyEventDispatcher.class.getName() + " only";
317       myCachedData.clear();
318       myEventCount = eventCount;
319     }
320
321     @Override
322     public Object getData(@NotNull String dataId) {
323       ProgressManager.checkCanceled();
324       boolean cacheable = Registry.is("actionSystem.cache.data") || ourSafeKeys.contains(dataId);
325       if (ApplicationManager.getApplication().isDispatchThread()) {
326         int currentEventCount = IdeEventQueue.getInstance().getEventCount();
327         if (myEventCount != -1 && myEventCount != currentEventCount) {
328           LOG.error("cannot share data context between Swing events; initial event count = " + myEventCount + "; current event count = " +
329                     currentEventCount);
330           cacheable = false;
331         }
332       }
333
334       Object answer = cacheable ? myCachedData.get(dataId) : null;
335       if (answer != null) {
336         return answer != NullResult.INSTANCE ? answer : null;
337       }
338
339       answer = doGetData(dataId);
340       if (cacheable && !(answer instanceof Stream)) {
341         myCachedData.put(dataId, answer == null ? NullResult.INSTANCE : answer);
342       }
343       return answer;
344     }
345
346     private @Nullable Object doGetData(@NotNull String dataId) {
347       Component component = SoftReference.dereference(myRef);
348       if (PlatformDataKeys.IS_MODAL_CONTEXT.is(dataId)) {
349         if (component == null) {
350           return null;
351         }
352         return IdeKeyEventDispatcher.isModalContext(component);
353       }
354       if (PlatformDataKeys.CONTEXT_COMPONENT.is(dataId)) {
355         return component;
356       }
357       if (PlatformDataKeys.MODALITY_STATE.is(dataId)) {
358         return component != null ? ModalityState.stateForComponent(component) : ModalityState.NON_MODAL;
359       }
360       Object data = calcData(dataId, component);
361       if (CommonDataKeys.EDITOR.is(dataId) || CommonDataKeys.HOST_EDITOR.is(dataId)) {
362         return validateEditor((Editor)data);
363       }
364       return data;
365     }
366
367     protected Object calcData(@NotNull String dataId, Component component) {
368       return ((DataManagerImpl)DataManager.getInstance()).getData(dataId, component);
369     }
370
371     @Override
372     @NonNls
373     public String toString() {
374       return "component=" + SoftReference.dereference(myRef);
375     }
376
377     @Override
378     public <T> T getUserData(@NotNull Key<T> key) {
379       //noinspection unchecked
380       return (T)getOrCreateMap().get(key);
381     }
382
383     @Override
384     public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
385       getOrCreateMap().put(key, value);
386     }
387
388     private @NotNull Map<Key<?>, Object> getOrCreateMap() {
389       Map<Key<?>, Object> userData = myUserData;
390       if (userData == null) {
391         myUserData = userData = ContainerUtil.createWeakValueMap();
392       }
393       return userData;
394     }
395   }
396 }