blob: 06594d2352bf8cf10c71cc2af772215aba2109af [file] [log] [blame]
/*
* 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();
}
}