| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.fileEditor; |
| |
| import com.intellij.openapi.actionSystem.DataConstants; |
| import com.intellij.openapi.actionSystem.DataContext; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.Result; |
| import com.intellij.openapi.application.WriteAction; |
| import com.intellij.openapi.application.impl.ApplicationImpl; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.components.impl.ComponentManagerImpl; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.actionSystem.EditorActionManager; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessExtension; |
| import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessProvider; |
| import com.intellij.openapi.module.EmptyModuleType; |
| import com.intellij.openapi.module.ModifiableModuleModel; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleManager; |
| import com.intellij.openapi.roots.ContentEntry; |
| import com.intellij.openapi.roots.ModuleRootManager; |
| import com.intellij.openapi.roots.ProjectFileIndex; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.WritingAccessProvider; |
| import com.intellij.testFramework.PlatformTestCase; |
| import com.intellij.testFramework.PlatformTestUtil; |
| import com.intellij.testFramework.PsiTestUtil; |
| import com.intellij.ui.EditorNotifications; |
| import com.intellij.ui.EditorNotificationsImpl; |
| import com.intellij.util.NullableFunction; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public class NonProjectFileAccessTest extends HeavyFileEditorManagerTestCase { |
| @Override |
| public void setUp() throws Exception { |
| PlatformTestCase.initPlatformLangPrefix(); |
| super.setUp(); |
| EditorNotifications notifications = new EditorNotificationsImpl(getProject()); |
| ((ComponentManagerImpl)getProject()).registerComponentInstance(EditorNotifications.class, notifications); |
| NonProjectFileWritingAccessProvider.enableChecksInTests(getProject(), true); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| NonProjectFileWritingAccessProvider.setCustomUnlocker(null); |
| NonProjectFileWritingAccessProvider.enableChecksInTests(getProject(), false); |
| super.tearDown(); |
| } |
| |
| @Override |
| protected boolean runInDispatchThread() { |
| return true; |
| } |
| |
| public void testBasicAccessCheck() throws Exception { |
| VirtualFile projectFile = createProjectFile(); |
| typeAndCheck(projectFile, true); |
| typeAndCheck(projectFile, true); // still allowed |
| |
| VirtualFile nonProjectFile = createNonProjectFile(); |
| typeAndCheck(nonProjectFile, false); |
| typeAndCheck(nonProjectFile, false);// still not allowed |
| |
| typeAndCheck(nonProjectFile, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, true); |
| typeAndCheck(nonProjectFile, null, true); // still allowed after previous Unlock |
| } |
| |
| public void testAccessToProjectSystemFiles() throws Exception { |
| saveProject(); |
| VirtualFile fileUnderProjectDir = new WriteAction<VirtualFile>() { |
| @Override |
| protected void run(@NotNull Result<VirtualFile> result) throws Throwable { |
| result.setResult(getProject().getBaseDir().createChildData(this, "fileUnderProjectDir.txt")); |
| } |
| }.execute().getResultObject(); |
| |
| assertFalse(ProjectFileIndex.SERVICE.getInstance(getProject()).isInContent(fileUnderProjectDir)); |
| |
| typeAndCheck(getProject().getProjectFile(), true); |
| typeAndCheck(getProject().getWorkspaceFile(), true); |
| typeAndCheck(fileUnderProjectDir, false); |
| } |
| |
| public void testAccessToModuleSystemFiles() throws Exception { |
| final Module moduleWithoutContentRoot = new WriteCommandAction<Module>(getProject()) { |
| @Override |
| protected void run(@NotNull Result<Module> result) throws Throwable { |
| String moduleName; |
| ModifiableModuleModel moduleModel = ModuleManager.getInstance(getProject()).getModifiableModel(); |
| try { |
| VirtualFile moduleDir = getProject().getBaseDir().createChildDirectory(this, "moduleWithoutContentRoot"); |
| moduleName = moduleModel.newModule(moduleDir.getPath() + "/moduleWithoutContentRoot.iml", EmptyModuleType.EMPTY_MODULE).getName(); |
| moduleModel.commit(); |
| } |
| catch (Throwable t) { |
| moduleModel.dispose(); |
| throw t; |
| } |
| |
| result.setResult(ModuleManager.getInstance(getProject()).findModuleByName(moduleName)); |
| } |
| }.execute().getResultObject(); |
| saveProject(); |
| |
| VirtualFile fileUnderModuleDir = new WriteAction<VirtualFile>() { |
| @Override |
| protected void run(@NotNull Result<VirtualFile> result) throws Throwable { |
| result.setResult(moduleWithoutContentRoot.getModuleFile().getParent().createChildData(this, "fileUnderModuleDir.txt")); |
| } |
| }.execute().getResultObject(); |
| |
| assertFalse(ProjectFileIndex.SERVICE.getInstance(getProject()).isInContent(fileUnderModuleDir)); |
| |
| typeAndCheck(moduleWithoutContentRoot.getModuleFile(), true); |
| typeAndCheck(myModule.getModuleFile(), true); |
| typeAndCheck(fileUnderModuleDir, false); |
| } |
| |
| private void saveProject() { |
| ApplicationImpl app = (ApplicationImpl)ApplicationManager.getApplication(); |
| boolean save = app.isDoNotSave(); |
| app.doNotSave(false); |
| try { |
| getProject().save(); |
| } |
| finally { |
| app.doNotSave(save); |
| } |
| } |
| |
| public void testAllowEditingInOneFileOnly() throws Exception { |
| VirtualFile nonProjectFile1 = createNonProjectFile(); |
| VirtualFile nonProjectFile2 = createNonProjectFile(); |
| |
| typeAndCheck(nonProjectFile1, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, true); |
| typeAndCheck(nonProjectFile2, null, false); |
| |
| typeAndCheck(nonProjectFile1, null, true); |
| typeAndCheck(nonProjectFile2, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, true); |
| |
| // let's check both files one more time to make sure unlock option doesn't have eny unexpected effect |
| typeAndCheck(nonProjectFile1, null, true); |
| typeAndCheck(nonProjectFile2, null, true); |
| } |
| |
| public void testAllowEditingInAllFiles() throws Exception { |
| VirtualFile nonProjectFile1 = createNonProjectFile(); |
| VirtualFile nonProjectFile2 = createNonProjectFile(); |
| VirtualFile nonProjectFile3 = createNonProjectFile(); |
| |
| typeAndCheck(nonProjectFile1, false); |
| typeAndCheck(nonProjectFile2, false); |
| typeAndCheck(nonProjectFile3, false); |
| |
| typeAndCheck(nonProjectFile1, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK_ALL, true); |
| // affects other files |
| typeAndCheck(nonProjectFile2, true); |
| typeAndCheck(nonProjectFile3, true); |
| } |
| |
| public void testCheckingOtherWriteAccessProvidersOnUnlock() throws Exception { |
| final VirtualFile nonProjectFile1 = createNonProjectFile(); |
| final VirtualFile nonProjectFile2 = createNonProjectFile(); |
| |
| final Set<VirtualFile> requested = registerWriteAccessProvider(nonProjectFile1); |
| |
| typeAndCheck(nonProjectFile1, false); |
| assertSameElements(requested); // not called since non-project file access is denied |
| |
| typeAndCheck(nonProjectFile1, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, false); |
| assertSameElements(requested, nonProjectFile1); |
| |
| typeAndCheck(nonProjectFile1, false); // leave file locked if other provides denied access |
| requested.clear(); |
| |
| typeAndCheck(nonProjectFile2, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK, true); |
| assertSameElements(requested, nonProjectFile2); |
| } |
| |
| public void testCheckingOtherWriteAccessProvidersOnUnlockAll() throws Exception { |
| final VirtualFile nonProjectFile1 = createNonProjectFile(); |
| final VirtualFile nonProjectFile2 = createNonProjectFile(); |
| |
| registerWriteAccessProvider(nonProjectFile1); |
| |
| typeAndCheck(nonProjectFile1, NonProjectFileWritingAccessProvider.UnlockOption.UNLOCK_ALL, |
| false); // can't write since denied by another write-access provider |
| typeAndCheck(nonProjectFile2, true); |
| } |
| |
| public void testClearingInfoForDeletedFiles() throws Exception { |
| final VirtualFile nonProjectFile1 = createNonProjectFile(); |
| final VirtualFile nonProjectFile2 = createNonProjectFile(); |
| |
| typeAndCheck(nonProjectFile1, false); |
| typeAndCheck(nonProjectFile2, false); |
| |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile1)); |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile2)); |
| |
| new WriteAction<Object>() { |
| @Override |
| protected void run(@NotNull Result<Object> result) throws Throwable { |
| nonProjectFile1.delete(this); |
| } |
| }.execute(); |
| |
| assertNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile1)); |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile2)); |
| } |
| |
| public void testUpdatingNotificationsOnRootChanges() throws Exception { |
| VirtualFile nonProjectFile1 = createNonProjectFile(); |
| VirtualFile nonProjectFile2 = createNonProjectFile(); |
| |
| typeAndCheck(nonProjectFile1, false); |
| typeAndCheck(nonProjectFile2, false); |
| |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile1)); |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile2)); |
| |
| ContentEntry contextRoot = PsiTestUtil.addContentRoot(myModule, nonProjectFile2.getParent()); |
| |
| // removing notification panel for newly added files |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile1)); |
| assertNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile2)); |
| |
| PsiTestUtil.removeContentEntry(myModule, contextRoot.getFile()); |
| |
| // do not add notification panel until access is requested |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile1)); |
| assertNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile2)); |
| |
| // but files are still not writable |
| typeAndCheck(nonProjectFile1, false); |
| typeAndCheck(nonProjectFile2, false); |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile1)); |
| assertNotNull(NonProjectFileWritingAccessProvider.getAccessStatus(getProject(), nonProjectFile2)); |
| } |
| |
| public void testCheckingExtensions() throws Exception { |
| VirtualFile nonProjectFile1 = createNonProjectFile(); |
| VirtualFile nonProjectFile2 = createNonProjectFile(); |
| |
| typeAndCheck(nonProjectFile1, false); |
| typeAndCheck(nonProjectFile2, false); |
| |
| List<VirtualFile> allowed = new ArrayList<VirtualFile>(); |
| registerAccessCheckExtension(allowed); |
| |
| typeAndCheck(nonProjectFile1, false); |
| typeAndCheck(nonProjectFile2, false); |
| |
| allowed.add(nonProjectFile1); |
| typeAndCheck(nonProjectFile1, true); |
| typeAndCheck(nonProjectFile2, false); |
| |
| allowed.clear(); |
| allowed.add(nonProjectFile2); |
| typeAndCheck(nonProjectFile1, false); |
| typeAndCheck(nonProjectFile2, true); |
| |
| allowed.clear(); |
| typeAndCheck(nonProjectFile1, false); |
| typeAndCheck(nonProjectFile2, false); |
| } |
| |
| private Set<VirtualFile> registerWriteAccessProvider(final VirtualFile... filesToDeny) { |
| final Set<VirtualFile> requested = new LinkedHashSet<VirtualFile>(); |
| PlatformTestUtil.registerExtension(Extensions.getArea(getProject()), WritingAccessProvider.EP_NAME, new WritingAccessProvider() { |
| @NotNull |
| @Override |
| public Collection<VirtualFile> requestWriting(VirtualFile... files) { |
| Collections.addAll(requested, files); |
| HashSet<VirtualFile> denied = new HashSet<VirtualFile>(Arrays.asList(filesToDeny)); |
| denied.retainAll(Arrays.asList(files)); |
| return denied; |
| } |
| |
| @Override |
| public boolean isPotentiallyWritable(@NotNull VirtualFile file) { |
| return true; |
| } |
| }, myTestRootDisposable); |
| return requested; |
| } |
| |
| private void registerAccessCheckExtension(final Collection<VirtualFile> filesToAllow) { |
| PlatformTestUtil.registerExtension(Extensions.getArea(getProject()), NonProjectFileWritingAccessExtension.EP_NAME, |
| new NonProjectFileWritingAccessExtension() { |
| @Override |
| public boolean isWritable(@NotNull VirtualFile file) { |
| return filesToAllow.contains(file); |
| } |
| }, |
| myTestRootDisposable); |
| } |
| |
| @NotNull |
| private VirtualFile createProjectFile() { |
| return new WriteAction<VirtualFile>() { |
| @Override |
| protected void run(@NotNull Result<VirtualFile> result) throws Throwable { |
| result.setResult(ModuleRootManager.getInstance(myModule).getContentRoots()[0].createChildData(this, "projectFile.txt")); |
| } |
| }.execute().getResultObject(); |
| } |
| |
| @NotNull |
| private VirtualFile createNonProjectFile() { |
| return new WriteAction<VirtualFile>() { |
| @Override |
| protected void run(@NotNull Result<VirtualFile> result) throws Throwable { |
| VirtualFile tmp = LocalFileSystem.getInstance().findFileByIoFile(FileUtil.createTempDirectory("tmp", null)); |
| result.setResult(tmp.createChildData(this, "nonProjectFile.txt")); |
| } |
| }.execute().getResultObject(); |
| } |
| |
| private void typeAndCheck(VirtualFile file, boolean fileHasBeenChanged) { |
| typeAndCheck(file, null, fileHasBeenChanged); |
| } |
| |
| private void typeAndCheck(VirtualFile file, |
| @Nullable final NonProjectFileWritingAccessProvider.UnlockOption option, |
| boolean fileHasBeenChanged) { |
| Editor editor = getEditor(file); |
| |
| NullableFunction<List<VirtualFile>, NonProjectFileWritingAccessProvider.UnlockOption> unlocker = |
| new NullableFunction<List<VirtualFile>, NonProjectFileWritingAccessProvider.UnlockOption>() { |
| @Nullable |
| @Override |
| public NonProjectFileWritingAccessProvider.UnlockOption fun(List<VirtualFile> files) { |
| return option; |
| } |
| }; |
| NonProjectFileWritingAccessProvider.setCustomUnlocker(unlocker); |
| |
| String before = editor.getDocument().getText(); |
| typeInChar(editor, 'a'); |
| |
| if (fileHasBeenChanged) { |
| assertEquals("Text should be changed", 'a' + before, editor.getDocument().getText()); |
| } |
| else { |
| assertEquals("Text should not be changed", before, editor.getDocument().getText()); |
| } |
| } |
| |
| private Editor getEditor(VirtualFile file) { |
| return FileEditorManager.getInstance(getProject()).openTextEditor(new OpenFileDescriptor(getProject(), file, 0), false); |
| } |
| |
| protected void typeInChar(Editor e, char c) { |
| getActionManager().getTypedAction().actionPerformed(e, c, createDataContextFor(e)); |
| } |
| |
| private DataContext createDataContextFor(final Editor editor) { |
| return new DataContext() { |
| @Override |
| public Object getData(String dataId) { |
| if (dataId.equals(DataConstants.EDITOR)) return editor; |
| if (dataId.equals(DataConstants.PROJECT)) return getProject(); |
| return null; |
| } |
| }; |
| } |
| |
| private static EditorActionManager getActionManager() { |
| return EditorActionManager.getInstance(); |
| } |
| } |