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