IDEA-129305 Settings: remove >> symbols in lists for setting groups
[idea/community.git] / python / src / com / jetbrains / python / psi / impl / PythonLanguageLevelPusher.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.jetbrains.python.psi.impl;
17
18 import com.intellij.facet.Facet;
19 import com.intellij.facet.FacetManager;
20 import com.intellij.openapi.application.Application;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.fileTypes.FileTypeManager;
23 import com.intellij.openapi.module.Module;
24 import com.intellij.openapi.module.ModuleManager;
25 import com.intellij.openapi.module.ModuleType;
26 import com.intellij.openapi.module.ModuleUtilCore;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.projectRoots.Sdk;
29 import com.intellij.openapi.roots.*;
30 import com.intellij.openapi.roots.impl.FilePropertyPusher;
31 import com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater;
32 import com.intellij.openapi.util.Key;
33 import com.intellij.openapi.vfs.VfsUtilCore;
34 import com.intellij.openapi.vfs.VirtualFile;
35 import com.intellij.openapi.vfs.VirtualFileVisitor;
36 import com.intellij.openapi.vfs.newvfs.FileAttribute;
37 import com.intellij.psi.SingleRootFileViewProvider;
38 import com.intellij.util.FileContentUtil;
39 import com.intellij.util.containers.WeakHashMap;
40 import com.intellij.util.io.DataInputOutputUtil;
41 import com.intellij.util.messages.MessageBus;
42 import com.jetbrains.python.PythonFileType;
43 import com.jetbrains.python.PythonModuleTypeBase;
44 import com.jetbrains.python.facet.PythonFacetSettings;
45 import com.jetbrains.python.psi.LanguageLevel;
46 import com.jetbrains.python.sdk.PythonSdkType;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import java.io.DataInputStream;
51 import java.io.DataOutputStream;
52 import java.io.IOException;
53 import java.util.*;
54
55 /**
56  * @author yole
57  */
58 public class PythonLanguageLevelPusher implements FilePropertyPusher<LanguageLevel> {
59   private final Map<Module, Sdk> myModuleSdks = new WeakHashMap<Module, Sdk>();
60
61   public static void pushLanguageLevel(final Project project) {
62     PushedFilePropertiesUpdater.getInstance(project).pushAll(new PythonLanguageLevelPusher());
63   }
64
65   public void initExtra(@NotNull Project project, @NotNull MessageBus bus, @NotNull Engine languageLevelUpdater) {
66     final Module[] modules = ModuleManager.getInstance(project).getModules();
67     Set<Sdk> usedSdks = new HashSet<Sdk>();
68     for (Module module : modules) {
69       if (isPythonModule(module)) {
70         final Sdk sdk = PythonSdkType.findPythonSdk(module);
71         myModuleSdks.put(module, sdk);
72         if (sdk != null && !usedSdks.contains(sdk)) {
73           usedSdks.add(sdk);
74           updateSdkLanguageLevel(project, sdk);
75         }
76       }
77     }
78   }
79
80   @NotNull
81   public Key<LanguageLevel> getFileDataKey() {
82     return LanguageLevel.KEY;
83   }
84
85   public boolean pushDirectoriesOnly() {
86     return true;
87   }
88
89   @NotNull
90   public LanguageLevel getDefaultValue() {
91     return LanguageLevel.getDefault();
92   }
93
94   public LanguageLevel getImmediateValue(@NotNull Project project, @Nullable VirtualFile file) {
95     return getFileLanguageLevel(project, file);
96   }
97
98   public static LanguageLevel getFileLanguageLevel(Project project, VirtualFile file) {
99     if (ApplicationManager.getApplication().isUnitTestMode() && LanguageLevel.FORCE_LANGUAGE_LEVEL != null) {
100       return LanguageLevel.FORCE_LANGUAGE_LEVEL;
101     }
102     if (file == null) return null;
103
104     final Module module = ModuleUtilCore.findModuleForFile(file, project);
105     if (module != null) {
106       final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
107       return PythonSdkType.getLanguageLevelForSdk(sdk);
108     }
109     final Sdk sdk = findSdk(project, file);
110     if (sdk != null) {
111       return PythonSdkType.getLanguageLevelForSdk(sdk);
112     }
113     return null;
114   }
115
116   @Nullable
117   private static Sdk findSdk(Project project, VirtualFile file) {
118     if (file != null) {
119       final List<OrderEntry> orderEntries = ProjectRootManager.getInstance(project).getFileIndex().getOrderEntriesForFile(file);
120       for (OrderEntry orderEntry : orderEntries) {
121         if (orderEntry instanceof JdkOrderEntry) {
122           return ((JdkOrderEntry)orderEntry).getJdk();
123         }
124       }
125     }
126     return null;
127   }
128
129   public LanguageLevel getImmediateValue(@NotNull Module module) {
130     if (ApplicationManager.getApplication().isUnitTestMode() && LanguageLevel.FORCE_LANGUAGE_LEVEL != null) {
131       return LanguageLevel.FORCE_LANGUAGE_LEVEL;
132     }
133
134     final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
135     return PythonSdkType.getLanguageLevelForSdk(sdk);
136   }
137
138   public boolean acceptsFile(@NotNull VirtualFile file) {
139     return false;
140   }
141
142   @Override
143   public boolean acceptsDirectory(@NotNull VirtualFile file, @NotNull Project project) {
144     return true;
145   }
146
147   private static final FileAttribute PERSISTENCE = new FileAttribute("python_language_level_persistence", 2, true);
148
149   public void persistAttribute(@NotNull Project project, @NotNull VirtualFile fileOrDir, @NotNull LanguageLevel level) throws IOException {
150     final DataInputStream iStream = PERSISTENCE.readAttribute(fileOrDir);
151     if (iStream != null) {
152       try {
153         final int oldLevelOrdinal = DataInputOutputUtil.readINT(iStream);
154         if (oldLevelOrdinal == level.ordinal()) return;
155       }
156       finally {
157         iStream.close();
158       }
159     }
160
161     final DataOutputStream oStream = PERSISTENCE.writeAttribute(fileOrDir);
162     DataInputOutputUtil.writeINT(oStream, level.ordinal());
163     oStream.close();
164
165     for (VirtualFile child : fileOrDir.getChildren()) {
166       if (!child.isDirectory() && PythonFileType.INSTANCE.equals(child.getFileType())) {
167         PushedFilePropertiesUpdater.getInstance(project).filePropertiesChanged(child);
168       }
169     }
170   }
171
172   public void afterRootsChanged(@NotNull final Project project) {
173     Set<Sdk> updatedSdks = new HashSet<Sdk>();
174     final Module[] modules = ModuleManager.getInstance(project).getModules();
175     boolean needReparseOpenFiles = false;
176     for (Module module : modules) {
177       if (isPythonModule(module)) {
178         Sdk newSdk = PythonSdkType.findPythonSdk(module);
179         if (myModuleSdks.containsKey(module)) {
180           Sdk oldSdk = myModuleSdks.get(module);
181           if ((newSdk != null || oldSdk != null) && newSdk != oldSdk) {
182             needReparseOpenFiles = true;
183           }
184         }
185         myModuleSdks.put(module, newSdk);
186         if (newSdk != null && !updatedSdks.contains(newSdk)) {
187           updatedSdks.add(newSdk);
188           updateSdkLanguageLevel(project, newSdk);
189         }
190       }
191     }
192     if (needReparseOpenFiles) {
193       FileContentUtil.reparseFiles(project, Collections.<VirtualFile>emptyList(), true);
194     }
195   }
196
197   private static boolean isPythonModule(@NotNull final Module module) {
198     final ModuleType moduleType = ModuleType.get(module);
199     if (moduleType instanceof PythonModuleTypeBase) return true;
200     final Facet[] allFacets = FacetManager.getInstance(module).getAllFacets();
201     for (Facet facet : allFacets) {
202       if (facet.getConfiguration() instanceof PythonFacetSettings) {
203         return true;
204       }
205     }
206     return false;
207   }
208
209   private void updateSdkLanguageLevel(final Project project, final Sdk sdk) {
210     final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk);
211     final VirtualFile[] files = sdk.getRootProvider().getFiles(OrderRootType.CLASSES);
212     final Application application = ApplicationManager.getApplication();
213     application.executeOnPooledThread(new Runnable() {
214       @Override
215       public void run() {
216         application.runReadAction(new Runnable() {
217           @Override
218           public void run() {
219             if (project != null && project.isDisposed()) {
220               return;
221             }
222             for (VirtualFile file : files) {
223               if (file.isValid()) {
224                 VirtualFile parent = file.getParent();
225                 boolean suppressSizeLimit = false;
226                 if (parent != null && parent.getName().equals(PythonSdkType.SKELETON_DIR_NAME)) {
227                   suppressSizeLimit = true;
228                 }
229                 markRecursively(project, file, languageLevel, suppressSizeLimit);
230               }
231             }
232           }
233         });
234       }
235     });
236   }
237
238   private void markRecursively(final Project project,
239                                @NotNull final VirtualFile file,
240                                final LanguageLevel languageLevel,
241                                final boolean suppressSizeLimit) {
242     final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
243     VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
244       @Override
245       public boolean visitFile(@NotNull VirtualFile file) {
246         if (fileTypeManager.isFileIgnored(file)) {
247           return false;
248         }
249         if (file.isDirectory()) {
250           PushedFilePropertiesUpdater.getInstance(project).findAndUpdateValue(file, PythonLanguageLevelPusher.this, languageLevel);
251         }
252         if (suppressSizeLimit) {
253           SingleRootFileViewProvider.doNotCheckFileSizeLimit(file);
254         }
255         return true;
256       }
257     });
258   }
259
260   public static void setForcedLanguageLevel(final Project project, @Nullable LanguageLevel languageLevel) {
261     LanguageLevel.FORCE_LANGUAGE_LEVEL = languageLevel;
262     pushLanguageLevel(project);
263   }
264
265   public void flushLanguageLevelCache() {
266     myModuleSdks.clear();
267   }
268 }