fb9caa5c96eb7e647498c20fd0abd60a6c1fc47f
[idea/community.git] / platform / projectModel-impl / src / com / intellij / openapi / roots / impl / ModuleRootManagerImpl.java
1 /*
2  * Copyright 2000-2017 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.
3  */
4 package com.intellij.openapi.roots.impl;
5
6 import com.intellij.openapi.Disposable;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.application.ReadAction;
9 import com.intellij.openapi.application.WriteAction;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.module.ModifiableModuleModel;
12 import com.intellij.openapi.module.Module;
13 import com.intellij.openapi.module.ModuleManager;
14 import com.intellij.openapi.module.ModuleServiceManager;
15 import com.intellij.openapi.project.Project;
16 import com.intellij.openapi.projectRoots.Sdk;
17 import com.intellij.openapi.roots.*;
18 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
19 import com.intellij.openapi.util.Disposer;
20 import com.intellij.openapi.util.InvalidDataException;
21 import com.intellij.openapi.util.JDOMExternalizable;
22 import com.intellij.openapi.util.SimpleModificationTracker;
23 import com.intellij.openapi.util.registry.Registry;
24 import com.intellij.openapi.vfs.VirtualFile;
25 import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
26 import com.intellij.util.ThrowableRunnable;
27 import gnu.trove.THashMap;
28 import kotlin.NotImplementedError;
29 import org.jdom.Element;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32 import org.jetbrains.annotations.TestOnly;
33 import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
34
35 import java.util.*;
36
37 public class ModuleRootManagerImpl extends ModuleRootManagerEx implements Disposable {
38   protected static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.ModuleRootManagerImpl");
39
40   private final Module myModule;
41   private final ProjectRootManagerImpl myProjectRootManager;
42   private final VirtualFilePointerManager myFilePointerManager;
43   protected RootModelImpl myRootModel;
44   private boolean myIsDisposed;
45   private boolean myLoaded;
46   private final OrderRootsCache myOrderRootsCache;
47   private final Map<RootModelImpl, Throwable> myModelCreations = new THashMap<>();
48
49   protected final SimpleModificationTracker myModificationTracker = new SimpleModificationTracker();
50
51   public ModuleRootManagerImpl(@NotNull Module module,
52                                @NotNull ProjectRootManagerImpl projectRootManager,
53                                @NotNull VirtualFilePointerManager filePointerManager) {
54     myModule = module;
55     myProjectRootManager = projectRootManager;
56     myFilePointerManager = filePointerManager;
57
58     myRootModel = new RootModelImpl(this, projectRootManager, filePointerManager);
59     myOrderRootsCache = new OrderRootsCache(module);
60   }
61
62   @Override
63   @NotNull
64   public Module getModule() {
65     return myModule;
66   }
67
68   @Override
69   @NotNull
70   public ModuleFileIndex getFileIndex() {
71     return ModuleServiceManager.getService(myModule, ModuleFileIndex.class);
72   }
73
74   @Override
75   public void dispose() {
76     myRootModel.dispose();
77     myIsDisposed = true;
78
79     if (Disposer.isDebugMode()) {
80       List<Map.Entry<RootModelImpl, Throwable>> entries;
81       synchronized (myModelCreations) {
82         entries = new ArrayList<>(myModelCreations.entrySet());
83       }
84       for (final Map.Entry<RootModelImpl, Throwable> entry : entries) {
85         LOG.warn("\n" +
86                  "***********************************************************************************************\n" +
87                  "***                        R O O T   M O D E L   N O T   D I S P O S E D                    ***\n" +
88                  "***********************************************************************************************\n" +
89                  "Created at:", entry.getValue());
90         entry.getKey().dispose();
91       }
92     }
93   }
94
95   @Override
96   @NotNull
97   public ModifiableRootModel getModifiableModel() {
98     return getModifiableModel(new RootConfigurationAccessor());
99   }
100
101   @Override
102   @NotNull
103   public ModifiableRootModel getModifiableModel(@NotNull RootConfigurationAccessor accessor) {
104     ApplicationManager.getApplication().assertReadAccessAllowed();
105     final RootModelImpl model = new RootModelImpl(myRootModel, this, true, accessor, myFilePointerManager, myProjectRootManager) {
106       @Override
107       public void dispose() {
108         super.dispose();
109         if (Disposer.isDebugMode()) {
110           synchronized (myModelCreations) {
111             myModelCreations.remove(this);
112           }
113         }
114       }
115     };
116     if (Disposer.isDebugMode()) {
117       synchronized (myModelCreations) {
118         myModelCreations.put(model, new Throwable());
119       }
120     }
121     return model;
122   }
123
124   @Override
125   @TestOnly
126   public long getModificationCountForTests() {
127     throw new NotImplementedError("Make sense only for persistent root manager");
128   }
129
130   void makeRootsChange(@NotNull Runnable runnable) {
131     ProjectRootManagerEx projectRootManagerEx = (ProjectRootManagerEx)ProjectRootManager.getInstance(myModule.getProject());
132     // IMPORTANT: should be the first listener!
133     projectRootManagerEx.makeRootsChange(runnable, false, myModule.isLoaded());
134   }
135
136   public RootModelImpl getRootModel() {
137     return myRootModel;
138   }
139
140   @NotNull
141   @Override
142   public ContentEntry[] getContentEntries() {
143     return myRootModel.getContentEntries();
144   }
145
146   @Override
147   @NotNull
148   public OrderEntry[] getOrderEntries() {
149     return myRootModel.getOrderEntries();
150   }
151
152   @Override
153   public Sdk getSdk() {
154     return myRootModel.getSdk();
155   }
156
157   @Override
158   public boolean isSdkInherited() {
159     return myRootModel.isSdkInherited();
160   }
161
162   void commitModel(RootModelImpl rootModel) {
163     ApplicationManager.getApplication().assertWriteAccessAllowed();
164     LOG.assertTrue(rootModel.myModuleRootManager == this);
165     LOG.assertTrue(!myIsDisposed);
166
167     boolean changed = rootModel.isChanged();
168
169     final Project project = myModule.getProject();
170     final ModifiableModuleModel moduleModel = ModuleManager.getInstance(project).getModifiableModel();
171     ModifiableModelCommitter.multiCommit(Collections.singletonList(rootModel), moduleModel);
172
173     if (changed) {
174       stateChanged();
175     }
176   }
177
178   static void doCommit(RootModelImpl rootModel) {
179     ModuleRootManagerImpl rootManager = (ModuleRootManagerImpl)getInstance(rootModel.getModule());
180     LOG.assertTrue(!rootManager.myIsDisposed);
181     rootModel.docommit();
182     rootModel.dispose();
183
184     try {
185       rootManager.stateChanged();
186     }
187     catch (Exception e) {
188       LOG.error(e);
189     }
190   }
191
192   @Override
193   @NotNull
194   public Module[] getDependencies() {
195     return myRootModel.getModuleDependencies();
196   }
197
198   @NotNull
199   @Override
200   public Module[] getDependencies(boolean includeTests) {
201     return myRootModel.getModuleDependencies(includeTests);
202   }
203
204   @NotNull
205   @Override
206   public Module[] getModuleDependencies() {
207     return myRootModel.getModuleDependencies();
208   }
209
210   @NotNull
211   @Override
212   public Module[] getModuleDependencies(boolean includeTests) {
213     return myRootModel.getModuleDependencies(includeTests);
214   }
215
216   @Override
217   public boolean isDependsOn(@NotNull Module module) {
218     return myRootModel.findModuleOrderEntry(module) != null;
219   }
220
221   @Override
222   @NotNull
223   public String[] getDependencyModuleNames() {
224     return myRootModel.getDependencyModuleNames();
225   }
226
227   @Override
228   public <T> T getModuleExtension(@NotNull final Class<T> klass) {
229     return myRootModel.getModuleExtension(klass);
230   }
231
232   @Override
233   public <R> R processOrder(@NotNull RootPolicy<R> policy, R initialValue) {
234     LOG.assertTrue(!myIsDisposed);
235     return myRootModel.processOrder(policy, initialValue);
236   }
237
238   @NotNull
239   @Override
240   public OrderEnumerator orderEntries() {
241     return new ModuleOrderEnumerator(myRootModel, myOrderRootsCache);
242   }
243
244   public static OrderRootsEnumerator getCachingEnumeratorForType(@NotNull OrderRootType type, @NotNull Module module) {
245     return getEnumeratorForType(type, module).usingCache();
246   }
247
248   @NotNull
249   private static OrderRootsEnumerator getEnumeratorForType(@NotNull OrderRootType type, @NotNull Module module) {
250     OrderEnumerator base = OrderEnumerator.orderEntries(module);
251     if (type == OrderRootType.CLASSES) {
252       return base.exportedOnly().withoutModuleSourceEntries().recursively().classes();
253     }
254     if (type == OrderRootType.SOURCES) {
255       return base.exportedOnly().recursively().sources();
256     }
257     return base.roots(type);
258   }
259
260   @Override
261   @NotNull
262   public VirtualFile[] getContentRoots() {
263     LOG.assertTrue(!myIsDisposed);
264     return myRootModel.getContentRoots();
265   }
266
267   @Override
268   @NotNull
269   public String[] getContentRootUrls() {
270     LOG.assertTrue(!myIsDisposed);
271     return myRootModel.getContentRootUrls();
272   }
273
274   @Override
275   @NotNull
276   public String[] getExcludeRootUrls() {
277     LOG.assertTrue(!myIsDisposed);
278     return myRootModel.getExcludeRootUrls();
279   }
280
281   @Override
282   @NotNull
283   public VirtualFile[] getExcludeRoots() {
284     LOG.assertTrue(!myIsDisposed);
285     return myRootModel.getExcludeRoots();
286   }
287
288   @Override
289   @NotNull
290   public String[] getSourceRootUrls() {
291     return getSourceRootUrls(true);
292   }
293
294   @NotNull
295   @Override
296   public String[] getSourceRootUrls(boolean includingTests) {
297     LOG.assertTrue(!myIsDisposed);
298     return myRootModel.getSourceRootUrls(includingTests);
299   }
300
301   @Override
302   @NotNull
303   public VirtualFile[] getSourceRoots() {
304     return getSourceRoots(true);
305   }
306
307   @Override
308   @NotNull
309   public VirtualFile[] getSourceRoots(final boolean includingTests) {
310     LOG.assertTrue(!myIsDisposed);
311     return myRootModel.getSourceRoots(includingTests);
312   }
313
314   @NotNull
315   @Override
316   public List<VirtualFile> getSourceRoots(@NotNull JpsModuleSourceRootType<?> rootType) {
317     return myRootModel.getSourceRoots(rootType);
318   }
319
320   @NotNull
321   @Override
322   public List<VirtualFile> getSourceRoots(@NotNull Set<? extends JpsModuleSourceRootType<?>> rootTypes) {
323     return myRootModel.getSourceRoots(rootTypes);
324   }
325
326   @Override
327   public void dropCaches() {
328     myOrderRootsCache.clearCache();
329   }
330
331   public ModuleRootManagerState getState() {
332     if (Registry.is("store.track.module.root.manager.changes", false)) {
333       LOG.error("getState, module " + myModule.getName());
334     }
335     return new ModuleRootManagerState(myRootModel);
336   }
337
338   public void loadState(@NotNull ModuleRootManagerState object) {
339     loadState(object, myLoaded || myModule.isLoaded());
340     myLoaded = true;
341   }
342
343   protected void loadState(ModuleRootManagerState object, boolean throwEvent) {
344     ThrowableRunnable<RuntimeException> r = () -> {
345       final RootModelImpl newModel = new RootModelImpl(object.getRootModelElement(), this, myProjectRootManager, myFilePointerManager, throwEvent);
346
347       if (throwEvent) {
348         makeRootsChange(() -> doCommit(newModel));
349       }
350       else {
351         myRootModel.dispose();
352         myRootModel = newModel;
353       }
354
355       assert !myRootModel.isOrderEntryDisposed();
356     };
357     try {
358       if (throwEvent) WriteAction.run(r);
359       else ReadAction.run(r);
360     }
361     catch (InvalidDataException e) {
362       LOG.error(e);
363     }
364   }
365
366   public void stateChanged() {
367     if (Registry.is("store.track.module.root.manager.changes", false)) {
368       LOG.error("ModelRootManager state changed");
369     }
370     myModificationTracker.incModificationCount();
371   }
372
373   @Override
374   @Nullable
375   public ProjectModelExternalSource getExternalSource() {
376     return ExternalProjectSystemRegistry.getInstance().getExternalSource(myModule);
377   }
378
379   public static class ModuleRootManagerState implements JDOMExternalizable {
380     private RootModelImpl myRootModel;
381     private Element myRootModelElement;
382
383     public ModuleRootManagerState() {
384     }
385
386     public ModuleRootManagerState(RootModelImpl rootModel) {
387       myRootModel = rootModel;
388     }
389
390     @Override
391     public void readExternal(Element element) {
392       myRootModelElement = element;
393     }
394
395     @Override
396     public void writeExternal(Element element) {
397       myRootModel.writeExternal(element);
398     }
399
400     public Element getRootModelElement() {
401       return myRootModelElement;
402     }
403   }
404 }