2 * Copyright 2000-2011 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.vfs.newvfs.impl;
18 import com.intellij.openapi.application.Application;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.PathManager;
21 import com.intellij.openapi.application.impl.ApplicationImpl;
22 import com.intellij.openapi.module.Module;
23 import com.intellij.openapi.module.ModuleManager;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.project.ProjectManager;
26 import com.intellij.openapi.roots.ModuleRootManager;
27 import com.intellij.openapi.roots.OrderEntry;
28 import com.intellij.openapi.roots.OrderRootType;
29 import com.intellij.openapi.roots.ProjectRootManager;
30 import com.intellij.openapi.util.Condition;
31 import com.intellij.openapi.util.Pair;
32 import com.intellij.openapi.util.io.FileUtil;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.openapi.vfs.JarFileSystem;
35 import com.intellij.openapi.vfs.LocalFileSystem;
36 import com.intellij.openapi.vfs.VfsUtil;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
39 import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem;
40 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
41 import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
42 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
43 import com.intellij.util.ArrayUtil;
44 import com.intellij.util.PathUtil;
45 import com.intellij.util.SystemProperties;
46 import com.intellij.util.containers.ContainerUtil;
47 import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
48 import gnu.trove.THashMap;
49 import gnu.trove.THashSet;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52 import org.jetbrains.annotations.TestOnly;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.io.OutputStream;
58 import java.net.URISyntaxException;
65 public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
66 private static final VirtualFileSystemEntry NULL_VIRTUAL_FILE = new VirtualFileImpl("*?;%NULL", null, -42);
68 private final NewVirtualFileSystem myFS;
71 protected Object myChildren; // Either HashMap<String, VFile> or VFile[]
73 public VirtualDirectoryImpl(@NotNull String name, final VirtualDirectoryImpl parent, @NotNull NewVirtualFileSystem fs, final int id) {
74 super(name, parent, id);
80 public NewVirtualFileSystem getFileSystem() {
85 private VirtualFileSystemEntry findChild(@NotNull String name,
86 final boolean createIfNotFound,
87 boolean ensureCanonicalName,
88 NewVirtualFileSystem delegate) {
89 final VirtualFileSystemEntry result = doFindChild(name, createIfNotFound, ensureCanonicalName, delegate);
90 if (result == NULL_VIRTUAL_FILE) {
91 return createIfNotFound ? createAndFindChildWithEventFire(name) : null;
96 Map<String, VirtualFileSystemEntry> map = asMap();
98 map.put(name, NULL_VIRTUAL_FILE);
107 private VirtualFileSystemEntry doFindChild(@NotNull String name,
108 final boolean createIfNotFound,
109 boolean ensureCanonicalName,
110 NewVirtualFileSystem delegate) {
111 if (name.length() == 0) {
115 final VirtualFileSystemEntry[] array;
116 final Map<String, VirtualFileSystemEntry> map;
117 final VirtualFileSystemEntry file;
118 synchronized (this) {
122 file = map.get(name);
130 final boolean ignoreCase = !getFileSystem().isCaseSensitive();
131 for (VirtualFileSystemEntry vf : array) {
132 if (vf.nameMatches(name, ignoreCase)) return vf;
134 return createIfNotFound ? createAndFindChildWithEventFire(name) : null;
137 if (file != null) return file;
139 if (ensureCanonicalName) {
140 VirtualFile fake = new FakeVirtualFile(this, name);
141 name = delegate.getCanonicallyCasedName(fake);
142 if (name.length() == 0) return null;
145 synchronized (this) {
146 // do not extract getId outside the synchronized block since it will cause a concurrency problem.
147 int id = PersistentFS.getId(this, name, delegate);
149 // maybe another doFindChild() sneaked in the middle
150 VirtualFileSystemEntry lastTry = map.get(name);
151 if (lastTry != null) return lastTry;
153 final String shorty = new String(name);
154 VirtualFileSystemEntry child = createChild(shorty, id); // So we don't hold whole char[] buffer of a lengthy path
155 map.put(shorty, child);
164 public VirtualFileSystemEntry createChild(String name, int id) {
165 final VirtualFileSystemEntry child;
166 final NewVirtualFileSystem fs = getFileSystem();
167 if (PersistentFS.isDirectory(id)) {
168 child = /*PersistentFS.isSymLink(id) ? new SymlinkDirectory(name, this, fs, id) :*/ new VirtualDirectoryImpl(name, this, fs, id);
171 child = new VirtualFileImpl(name, this, id);
172 assertAccessInTests(child);
175 if (fs.markNewFilesAsDirty()) {
183 private static final boolean IS_UNDER_TEAMCITY = System.getProperty("bootstrap.testcases") != null;
184 private static final boolean IS_UNIT_TESTS = ApplicationManager.getApplication().isUnitTestMode();
185 private static final Collection<String> additionalRoots = new THashSet<String>();
187 public static void allowToAccess(@NotNull String root) { additionalRoots.add(FileUtil.toSystemIndependentName(root)); }
189 private static void assertAccessInTests(VirtualFileSystemEntry child) {
190 if (IS_UNIT_TESTS && IS_UNDER_TEAMCITY && ApplicationManager.getApplication() instanceof ApplicationImpl && ((ApplicationImpl)ApplicationManager.getApplication()).isComponentsCreated()) {
191 NewVirtualFileSystem fileSystem = child.getFileSystem();
192 if (fileSystem != LocalFileSystem.getInstance() && fileSystem != JarFileSystem.getInstance()) {
195 // root' children are loaded always
196 if (child.getParent() == null || child.getParent().getParent() == null) return;
198 Set<String> allowed = allowedRoots();
199 boolean isUnder = allowed == null;
201 for (String root : allowed) {
202 String childPath = child.getPath();
203 if (child.getFileSystem() == JarFileSystem.getInstance()) {
204 VirtualFile local = JarFileSystem.getInstance().getVirtualFileForJar(child);
205 childPath = local.getPath();
207 if (FileUtil.startsWith(childPath, root)) {
211 if (root.startsWith(JarFileSystem.PROTOCOL_PREFIX)) {
212 String rootLocalPath = FileUtil.toSystemIndependentName(PathUtil.toPresentableUrl(root));
213 isUnder = FileUtil.startsWith(childPath, rootLocalPath);
220 if (!allowed.isEmpty()) {
221 assert false : "File accessed outside allowed roots: " + child +";\n Allowed roots: "+new ArrayList(allowed);
227 // null means we were unable to get roots, so do not check access
228 private static Set<String> allowedRoots() {
229 if (insideGettingRoots) return null;
230 Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
231 if (openProjects.length == 0) return null;
232 final Set<String> allowed = new THashSet<String>();
233 String homePath = PathManager.getHomePath();
234 allowed.add(FileUtil.toSystemIndependentName(homePath));
236 URL outUrl = Application.class.getResource("/");
237 String output = new File(outUrl.toURI()).getParentFile().getParentFile().getPath();
238 allowed.add(FileUtil.toSystemIndependentName(output));
240 catch (URISyntaxException ignored) {
242 String javaHome = SystemProperties.getJavaHome();
243 allowed.add(FileUtil.toSystemIndependentName(javaHome));
244 String tempDirectorySpecific = new File(FileUtil.getTempDirectory()).getParent();
245 allowed.add(FileUtil.toSystemIndependentName(tempDirectorySpecific));
246 String tempDirectory = System.getProperty("java.io.tmpdir");
247 allowed.add(FileUtil.toSystemIndependentName(tempDirectory));
248 String userHome = SystemProperties.getUserHome();
249 allowed.add(FileUtil.toSystemIndependentName(userHome));
250 for (Project project : openProjects) {
251 if (!project.isInitialized()) {
252 return null; // all is allowed
254 for (VirtualFile root : ProjectRootManager.getInstance(project).getContentRoots()) {
255 allowed.add(root.getPath());
257 for (VirtualFile root : getAllRoots(project)) {
258 allowed.add(StringUtil.trimEnd(root.getPath(), JarFileSystem.JAR_SEPARATOR));
260 String location = project.getLocation();
261 allowed.add(FileUtil.toSystemIndependentName(location));
264 //for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
265 // allowed.add(FileUtil.toSystemIndependentName(sdk.getHomePath()));
267 for (String root : additionalRoots) {
273 private static boolean insideGettingRoots;
274 private static VirtualFile[] getAllRoots(Project project) {
275 insideGettingRoots = true;
276 Set<VirtualFile> roots = new THashSet<VirtualFile>();
277 final Module[] modules = ModuleManager.getInstance(project).getModules();
278 for (Module module : modules) {
279 final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
280 final OrderEntry[] orderEntries = moduleRootManager.getOrderEntries();
281 for (OrderEntry entry : orderEntries) {
283 files = entry.getFiles(OrderRootType.CLASSES);
284 ContainerUtil.addAll(roots, files);
285 files = entry.getFiles(OrderRootType.SOURCES);
286 ContainerUtil.addAll(roots, files);
287 files = entry.getFiles(OrderRootType.CLASSES_AND_OUTPUT);
288 ContainerUtil.addAll(roots, files);
291 insideGettingRoots = false;
292 return VfsUtil.toVirtualFileArray(roots);
297 private VirtualFileSystemEntry createAndFindChildWithEventFire(@NotNull String name) {
298 final NewVirtualFileSystem delegate = getFileSystem();
299 VirtualFile fake = new FakeVirtualFile(this, name);
300 if (delegate.exists(fake)) {
301 final String realName = delegate.getCanonicallyCasedName(fake);
302 VFileCreateEvent event = new VFileCreateEvent(null, this, realName, delegate.isDirectory(fake), true);
303 RefreshQueue.getInstance().processSingleEvent(event);
304 return findChild(realName);
313 public NewVirtualFile refreshAndFindChild(@NotNull String name) {
314 return findChild(name, true, true, getFileSystem());
319 public synchronized NewVirtualFile findChildIfCached(@NotNull String name) {
320 final VirtualFileSystemEntry[] a = asArray();
322 final boolean ignoreCase = !getFileSystem().isCaseSensitive();
323 for (VirtualFileSystemEntry file : a) {
324 if (file.nameMatches(name, ignoreCase)) return file;
330 final Map<String, VirtualFileSystemEntry> map = asMap();
332 final VirtualFileSystemEntry file = map.get(name);
333 return file != NULL_VIRTUAL_FILE ? file : null;
341 public Iterable<VirtualFile> iterInDbChildren() {
342 return ContainerUtil.iterate(getInDbChildren(), new Condition<VirtualFile>() {
344 public boolean value(VirtualFile file) {
345 return file != NULL_VIRTUAL_FILE;
351 private synchronized Collection<VirtualFile> getInDbChildren() {
352 if (myChildren instanceof VirtualFileSystemEntry[]) {
353 return Arrays.asList((VirtualFile[])myChildren);
356 if (!ourPersistence.wereChildrenAccessed(this)) {
357 return Collections.emptyList();
360 if (ourPersistence.areChildrenLoaded(this)) {
361 return Arrays.asList(getChildren());
364 final String[] names = PersistentFS.listPersisted(this);
365 NewVirtualFileSystem delegate = PersistentFS.replaceWithNativeFS(getFileSystem());
366 for (String name : names) {
367 findChild(name, false, false, delegate);
370 // important: should return a copy here for safe iterations
371 return new ArrayList<VirtualFile>(ensureAsMap().values());
376 public synchronized VirtualFile[] getChildren() {
377 if (myChildren instanceof VirtualFileSystemEntry[]) {
378 return (VirtualFileSystemEntry[])myChildren;
381 Pair<String[],int[]> pair = PersistentFS.listAll(this);
382 final int[] childrenIds = pair.second;
383 VirtualFileSystemEntry[] children;
384 if (childrenIds.length == 0) {
385 children = EMPTY_ARRAY;
388 children = new VirtualFileSystemEntry[childrenIds.length];
389 String[] names = pair.first;
390 final Map<String, VirtualFileSystemEntry> map = asMap();
391 for (int i = 0; i < children.length; i++) {
392 final int childId = childrenIds[i];
393 final String name = names[i];
394 VirtualFileSystemEntry child = map != null ? map.get(name) : null;
396 children[i] = child != null && child != NULL_VIRTUAL_FILE ? child : createChild(name, childId);
401 myChildren = children;
409 public VirtualFileSystemEntry findChild(@NotNull final String name) {
410 return findChild(name, false, true, getFileSystem());
415 public NewVirtualFile findChildById(int id) {
416 final NewVirtualFile loaded = findChildByIdIfCached(id);
417 if (loaded != null) {
421 String name = ourPersistence.getName(id);
422 return findChild(name, false, false, getFileSystem());
426 public NewVirtualFile findChildByIdIfCached(int id) {
427 final VirtualFile[] a = asArray();
429 for (VirtualFile file : a) {
430 NewVirtualFile withId = (NewVirtualFile)file;
431 if (withId.getId() == id) return withId;
436 synchronized (this) {
437 final Map<String, VirtualFileSystemEntry> map = asMap();
439 for (Map.Entry<String, VirtualFileSystemEntry> entry : map.entrySet()) {
440 VirtualFile file = entry.getValue();
441 if (file == NULL_VIRTUAL_FILE) continue;
442 NewVirtualFile withId = (NewVirtualFile)file;
443 if (withId.getId() == id) return withId;
451 private VirtualFileSystemEntry[] asArray() {
452 if (myChildren instanceof VirtualFileSystemEntry[]) return (VirtualFileSystemEntry[])myChildren;
457 private Map<String, VirtualFileSystemEntry> asMap() {
458 if (myChildren instanceof Map) {
459 //noinspection unchecked
460 return (Map<String, VirtualFileSystemEntry>)myChildren;
466 private Map<String, VirtualFileSystemEntry> ensureAsMap() {
467 Map<String, VirtualFileSystemEntry> map;
468 if (myChildren == null) {
473 //noinspection unchecked
474 map = (Map<String, VirtualFileSystemEntry>)myChildren;
480 public synchronized void addChild(@NotNull VirtualFileSystemEntry file) {
481 final VirtualFileSystemEntry[] a = asArray();
483 myChildren = ArrayUtil.append(a, file);
486 ensureAsMap().put(file.getName(), file);
490 public synchronized void removeChild(@NotNull VirtualFile file) {
491 final VirtualFileSystemEntry[] a = asArray();
493 myChildren = ArrayUtil.remove(a, file);
496 ensureAsMap().put(file.getName(), NULL_VIRTUAL_FILE);
500 public synchronized boolean allChildrenLoaded() {
501 return myChildren instanceof VirtualFileSystemEntry[];
505 public synchronized List<String> getSuspiciousNames() {
506 final Map<String, VirtualFileSystemEntry> map = asMap();
507 if (map == null) return Collections.emptyList();
509 List<String> names = new ArrayList<String>();
510 for (Map.Entry<String, VirtualFileSystemEntry> entry : map.entrySet()) {
511 if (entry.getValue() == NULL_VIRTUAL_FILE) {
512 names.add(entry.getKey());
520 public boolean isDirectory() {
526 public synchronized Collection<VirtualFile> getCachedChildren() {
527 final Map<String, VirtualFileSystemEntry> map = asMap();
529 Set<VirtualFile> files = new THashSet<VirtualFile>(map.values());
530 files.remove(NULL_VIRTUAL_FILE);
534 final VirtualFile[] a = asArray();
535 if (a != null) return Arrays.asList(a);
537 return Collections.emptyList();
541 private Map<String, VirtualFileSystemEntry> createMap() {
542 return getFileSystem().isCaseSensitive()
543 ? new THashMap<String, VirtualFileSystemEntry>()
544 : new THashMap<String, VirtualFileSystemEntry>(CaseInsensitiveStringHashingStrategy.INSTANCE);
548 public synchronized void cleanupCachedChildren(@NotNull Set<VirtualFile> survivors) {
549 if (survivors.contains(this)) {
550 for (VirtualFile file : getCachedChildren()) {
551 if (file instanceof VirtualDirectoryImpl) {
552 ((VirtualDirectoryImpl)file).cleanupCachedChildren(survivors);
562 public InputStream getInputStream() throws IOException {
563 throw new IOException("getInputStream() must not be called against a directory: " + getUrl());
568 public OutputStream getOutputStream(final Object requestor, final long newModificationStamp, final long newTimeStamp) throws IOException {
569 throw new IOException("getOutputStream() must not be called against a directory: " + getUrl());