REST API /file

IDEA-135828 Command-line option to bring WebStorm to front
This commit is contained in:
Vladimir Krivosheev
2015-02-06 19:16:33 +01:00
parent 168adc472d
commit 30ac169c57
14 changed files with 638 additions and 111 deletions
+3 -3
View File
@@ -2,12 +2,12 @@ var gulp = require('gulp')
var apidoc = require('gulp-apidoc')
var path = require('path')
var sources = path.resolve("../../platform/")
var sources = path.resolve("../../platform/built-in-server/src")
gulp.task('apidoc', function () {
var destDir = (process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) + "/idea-rest-api";
console.info("generating docs from " + sources + " to " + destDir)
apidoc.exec({src: sources, dest: destDir, debug: true})
console.info("Generating docs from " + sources + " to " + destDir)
apidoc.exec({src: sources, dest: destDir})
})
gulp.task('default', ['apidoc'])
+8
View File
@@ -0,0 +1,8 @@
<root>
<item name='org.junit.rules.TestWatcher void finished(org.junit.runner.Description) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.junit.rules.TestWatcher void starting(org.junit.runner.Description) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
@@ -21,5 +21,6 @@
<orderEntry type="module" module-name="testFramework" scope="TEST" />
<orderEntry type="module" module-name="built-in-server-api" exported="" />
<orderEntry type="module" module-name="vcs-api" />
<orderEntry type="library" scope="TEST" name="KotlinJavaRuntime" level="project" />
</component>
</module>
@@ -44,15 +44,15 @@ import java.util.List;
* @apiName diff
* @apiGroup Platform
*
* @apiParam (properties) {String} [fileType] The file type name of the contents (see <a href="https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/fileTypes/FileType.java">FileType.getName()</a>).
* @apiParam {String} [fileType] The file type name of the contents (see <a href="https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/openapi/fileTypes/FileType.java">FileType.getName()</a>).
* You can get registered file types using <a href="#api-Platform-about">/rest/about?registeredFileTypes</a> request.
* @apiParam (properties) {String} [windowTitle=Diff Service] The title of the diff window.
* @apiParam (properties) {Boolean} [focused=true] Whether to focus project window.
* @apiParam {String} [windowTitle=Diff Service] The title of the diff window.
* @apiParam {Boolean} [focused=true] Whether to focus project window.
*
* @apiParam (properties) {Object[]{2..}} contents The list of the contents to diff.
* @apiParam (properties) {String} [contents.title] The title of the content.
* @apiParam (properties) {String} [contents.fileType] The file type name of the content.
* @apiParam (properties) {String} contents.content The data of the content.
* @apiParam {Object[]{2..}} contents The list of the contents to diff.
* @apiParam {String} [contents.title] The title of the content.
* @apiParam {String} [contents.fileType] The file type name of the content.
* @apiParam {String} contents.content The data of the content.
*
* @apiUse DiffRequestExample
*/
@@ -107,7 +107,7 @@ final class DiffHttpService extends RestService {
return "Empty request";
}
Project project = guessProject();
Project project = getLastFocusedOrOpenedProject();
if (project == null) {
// Argument for @NotNull parameter 'project' of com/intellij/openapi/components/ServiceManager.getService must not be null
project = ProjectManager.getInstance().getDefaultProject();
@@ -0,0 +1,322 @@
/*
* Copyright 2000-2015 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 org.jetbrains.ide;
import com.google.gson.Gson;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.StringUtilRt;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsRoot;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.ManagingFS;
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import com.intellij.openapi.vfs.newvfs.RefreshSession;
import com.intellij.ui.AppUIUtil;
import com.intellij.util.Consumer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.builtInWebServer.WebServerPathToFileManager;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.Promise;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @api {get} /file Open file
* @apiName file
* @apiGroup Platform
*
* @apiParam {String} file The path of the file. Relative (to project base dir, VCS root, module source or content root) or absolute.
* @apiParam {Integer} [line] The line number of the file (1-based).
* @apiParam {Integer} [column] The column number of the file (1-based).
* @apiParam {Boolean} [focused=true] Whether to focus project window.
*
* @apiExample {curl} Absolute path
* curl http://localhost:63342/api/file//absolute/path/to/file.kt
*
* @apiExample {curl} Relative path
* curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt
*
* @apiExample {curl} With line and column
* curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt:100:34
*
* @apiExample {curl} Query parameters
* curl http://localhost:63342/api/file?file=path/to/file.kt&line=100&column=34
*/
class OpenFileHttpService extends RestService {
private static final RuntimeException NOT_FOUND = Promise.createError("not found");
private static final Pattern LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$");
private volatile long refreshSessionId = 0;
private final ConcurrentLinkedQueue<OpenFileTask> requests = new ConcurrentLinkedQueue<OpenFileTask>();
@NotNull
@Override
protected String getServiceName() {
return "file";
}
@Override
protected boolean isMethodSupported(@NotNull HttpMethod method) {
return method == HttpMethod.GET || method == HttpMethod.POST;
}
@Nullable
@Override
public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
final boolean keepAlive = HttpHeaders.isKeepAlive(request);
final Channel channel = context.channel();
String file;
int line;
int column;
if (request.method() == HttpMethod.POST) {
OpenFileRequest jsonRequest = new Gson().fromJson(createJsonReader(request), OpenFileRequest.class);
file = jsonRequest.file;
line = jsonRequest.line;
column = jsonRequest.column;
}
else {
file = StringUtil.nullize(getStringParameter("file", urlDecoder), true);
line = getIntParameter("line", urlDecoder);
column = getIntParameter("column", urlDecoder);
}
int prefixLength = 1 + PREFIX.length() + 1 + getServiceName().length() + 1;
String path = urlDecoder.path();
if (path.length() > prefixLength) {
Matcher matcher = LINE_AND_COLUMN.matcher(path).region(prefixLength, path.length());
LOG.assertTrue(matcher.matches());
if (file == null) {
file = matcher.group(1).trim();
}
if (line == -1) {
line = StringUtilRt.parseInt(matcher.group(2), 1);
}
if (column == -1) {
column = StringUtilRt.parseInt(matcher.group(3), 1);
}
}
if (file == null) {
sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel);
return null;
}
openFile(file, line, column, getBooleanParameter("focused", urlDecoder))
.done(new Consumer<Void>() {
@Override
public void consume(Void aVoid) {
sendStatus(HttpResponseStatus.OK, keepAlive, channel);
}
})
.rejected(new Consumer<Throwable>() {
@Override
public void consume(Throwable throwable) {
if (throwable == NOT_FOUND) {
sendStatus(HttpResponseStatus.NOT_FOUND, keepAlive, channel);
}
else {
// todo send error
sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel);
LOG.error(throwable);
}
}
});
return null;
}
private static void navigate(@Nullable Project project, @NotNull VirtualFile file, int line, int column, boolean focused) {
if (project == null) {
project = getLastFocusedOrOpenedProject();
if (project == null) {
project = ProjectManager.getInstance().getDefaultProject();
}
}
new OpenFileDescriptor(project, file, line, column).navigate(true);
if (focused) {
com.intellij.ide.impl.ProjectUtil.focusProjectWindow(project, true);
}
}
@NotNull
Promise<Void> openFile(@NotNull String path, int line, final int column, final boolean focused) {
path = FileUtil.expandUserHome(path);
final File file = new File(FileUtil.toSystemDependentName(path));
if (file.isAbsolute()) {
return openAbsolutePath(file, line, column, focused);
}
// we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
RefreshQueue queue = RefreshQueue.getInstance();
queue.cancelSession(refreshSessionId);
OpenFileTask request = new OpenFileTask(FileUtil.toCanonicalPath(FileUtil.toSystemIndependentName(path), '/'), line, column);
requests.offer(request);
RefreshSession session = queue.createSession(true, true, new Runnable() {
@Override
public void run() {
OpenFileTask request;
while ((request = requests.poll()) != null) {
try {
if (openRelativePath(request.path, request.line, request.column, focused)) {
request.promise.setResult(null);
}
else {
request.promise.setError(NOT_FOUND);
}
}
catch (Throwable e) {
request.promise.setError(e);
}
}
}
}, ModalityState.NON_MODAL);
session.addAllFiles(ManagingFS.getInstance().getLocalRoots());
refreshSessionId = session.getId();
session.launch();
return request.promise;
}
// path must be normalized
private static boolean openRelativePath(@NotNull final String path, final int line, final int column, final boolean focused) {
VirtualFile virtualFile = null;
Project project = null;
Project[] projects = ProjectManager.getInstance().getOpenProjects();
for (Project openedProject : projects) {
VirtualFile openedProjectBaseDir = openedProject.getBaseDir();
if (openedProjectBaseDir != null) {
virtualFile = openedProjectBaseDir.findFileByRelativePath(path);
}
if (virtualFile == null) {
virtualFile = WebServerPathToFileManager.getInstance(openedProject).get(path);
}
if (virtualFile != null) {
project = openedProject;
break;
}
}
if (virtualFile == null) {
for (Project openedProject : projects) {
for (VcsRoot vcsRoot : ProjectLevelVcsManager.getInstance(openedProject).getAllVcsRoots()) {
VirtualFile root = vcsRoot.getPath();
if (root != null) {
virtualFile = root.findFileByRelativePath(path);
if (virtualFile != null) {
project = openedProject;
break;
}
}
}
}
}
if (virtualFile == null) {
return false;
}
final Project finalProject = project;
final VirtualFile finalVirtualFile = virtualFile;
AppUIUtil.invokeLaterIfProjectAlive(project, new Runnable() {
@Override
public void run() {
navigate(finalProject, finalVirtualFile, line, column, focused);
}
});
return true;
}
@NotNull
private static Promise<Void> openAbsolutePath(@NotNull final File file, final int line, final int column, final boolean focused) {
if (!file.exists()) {
return Promise.reject(NOT_FOUND);
}
final AsyncPromise<Void> promise = new AsyncPromise<Void>();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
try {
VirtualFile virtualFile;
AccessToken token = WriteAction.start();
try {
virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
}
finally {
token.finish();
}
if (virtualFile == null) {
promise.setError(NOT_FOUND);
}
else {
navigate(ProjectUtil.guessProjectForContentFile(virtualFile), virtualFile, line, column, focused);
promise.setResult(null);
}
}
catch (Throwable e) {
promise.setError(e);
}
}
});
return promise;
}
private static final class OpenFileTask {
final String path;
final int line;
final int column;
final AsyncPromise<Void> promise = new AsyncPromise<Void>();
OpenFileTask(@NotNull String path, int line, int column) {
this.path = path;
this.line = line;
this.column = column;
}
}
private static class OpenFileRequest {
public String file;
public int line;
public int column;
public boolean focused;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* Copyright 2000-2015 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.
@@ -15,30 +15,11 @@
*/
package org.jetbrains.ide;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileEditorProvider;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.fileEditor.ex.FileEditorProviderManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectLocator;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsRoot;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.Promise;
import java.io.File;
/**
* @author mike
*/
public class OpenFileXmlRpcHandler {
class OpenFileXmlRpcHandler {
private static final Logger LOG = Logger.getInstance(OpenFileXmlRpcHandler.class);
// XML-RPC interface method - keep the signature intact
@@ -55,75 +36,7 @@ public class OpenFileXmlRpcHandler {
return doOpen(path, line, column);
}
private static boolean doOpen(final String path, final int line, final int column) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
Pair<VirtualFile, Project> data = new File(path).isAbsolute() ? findByAbsolutePath(path) : findByRelativePath(path);
if (data == null) return;
FileEditorProvider[] providers = FileEditorProviderManager.getInstance().getProviders(data.second, data.first);
if (providers.length == 0) return;
OpenFileDescriptor descriptor = new OpenFileDescriptor(data.second, data.first, line, column);
FileEditorManager.getInstance(data.second).openTextEditor(descriptor, true);
}
});
return true;
private static boolean doOpen(@NotNull String path, int line, int column) {
return HttpRequestHandler.EP_NAME.findExtension(OpenFileHttpService.class).openFile(path, line, column, false).getState() != Promise.State.REJECTED;
}
@Nullable
private static Pair<VirtualFile, Project> findByAbsolutePath(String path) {
File file = new File(FileUtil.toSystemDependentName(path));
if (file.exists()) {
VirtualFile vFile = findVirtualFile(file);
if (vFile != null) {
Project project = ProjectLocator.getInstance().guessProjectForFile(vFile);
if (project != null) {
return Pair.create(vFile, project);
}
}
}
return null;
}
@Nullable
private static Pair<VirtualFile, Project> findByRelativePath(String path) {
Project[] projects = ProjectManager.getInstance().getOpenProjects();
String localPath = FileUtil.toSystemDependentName(path);
for (Project project : projects) {
File file = new File(project.getBasePath(), localPath);
if (file.exists()) {
VirtualFile vFile = findVirtualFile(file);
return vFile != null ? Pair.create(vFile, project) : null;
}
}
for (Project project : projects) {
for (VcsRoot vcsRoot : ProjectLevelVcsManager.getInstance(project).getAllVcsRoots()) {
VirtualFile root = vcsRoot.getPath();
if (root != null) {
File file = new File(FileUtil.toSystemDependentName(root.getPath()), localPath);
if (file.exists()) {
VirtualFile vFile = findVirtualFile(file);
return vFile != null ? Pair.create(vFile, project) : null;
}
}
}
}
return null;
}
@Nullable
private static VirtualFile findVirtualFile(final File file) {
return ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
@Override
public VirtualFile compute() {
return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
}
});
}
}
}
@@ -36,8 +36,8 @@ import java.io.IOException;
* @apiGroup Platform
* @apiDescription Checkout a repository from source control and then open an IDEA project from it.
*
* @apiParam (properties) {Object} vcs The map of the VCS.
* @apiParam (properties) {String} project The path to project to be opened.
* @apiParam {Object} vcs The map of the VCS.
* @apiParam {String} project The path to project to be opened.
*
* @apiUse OpenProjectSetRequestExample
* @apiUse OpenProjectSetRequestExampleMulti
@@ -22,6 +22,8 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.StringUtilRt;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
@@ -29,6 +31,7 @@ import com.intellij.util.ExceptionUtil;
import com.intellij.util.containers.ContainerUtil;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import org.jetbrains.annotations.NotNull;
@@ -50,7 +53,7 @@ import java.util.List;
*/
public abstract class RestService extends HttpRequestHandler {
protected static final Logger LOG = Logger.getInstance(RestService.class);
private static final String PREFIX = "api";
public static final String PREFIX = "api";
@Override
public final boolean isSupported(@NotNull FullHttpRequest request) {
@@ -142,7 +145,7 @@ public abstract class RestService extends HttpRequestHandler {
}
@Nullable
protected static Project guessProject() {
protected static Project getLastFocusedOrOpenedProject() {
IdeFrame lastFocusedFrame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
Project project = lastFocusedFrame == null ? null : lastFocusedFrame.getProject();
if (project == null) {
@@ -153,7 +156,18 @@ public abstract class RestService extends HttpRequestHandler {
}
protected static void sendOk(@NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
Responses.send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK), context.channel(), request);
sendStatus(HttpResponseStatus.OK, HttpHeaders.isKeepAlive(request), context.channel());
}
protected static void sendStatus(@NotNull HttpResponseStatus status, boolean keepAlive, @NotNull Channel channel) {
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
HttpHeaders.setContentLength(response, 0);
Responses.addCommonHeaders(response);
Responses.addNoCache(response);
if (keepAlive) {
HttpHeaders.setKeepAlive(response, true);
}
Responses.send(response, channel, !keepAlive);
}
protected static void send(@NotNull BufferExposingByteArrayOutputStream byteOut, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
@@ -162,6 +176,15 @@ public abstract class RestService extends HttpRequestHandler {
Responses.send(response, context.channel(), request);
}
@Nullable
protected static String getStringParameter(@NotNull String name, @NotNull QueryStringDecoder urlDecoder) {
return ContainerUtil.getLastItem(urlDecoder.parameters().get(name));
}
protected static int getIntParameter(@NotNull String name, @NotNull QueryStringDecoder urlDecoder) {
return StringUtilRt.parseInt(StringUtil.nullize(ContainerUtil.getLastItem(urlDecoder.parameters().get(name)), true), -1);
}
protected static boolean getBooleanParameter(@NotNull String name, @NotNull QueryStringDecoder urlDecoder) {
List<String> values = urlDecoder.parameters().get(name);
if (ContainerUtil.isEmpty(values)) {
@@ -109,7 +109,7 @@ public final class Responses {
send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(content, charset)), channel, request);
}
private static void send(@NotNull HttpResponse response, @NotNull Channel channel, boolean close) {
public static void send(@NotNull HttpResponse response, @NotNull Channel channel, boolean close) {
if (!channel.isActive()) {
return;
}
@@ -0,0 +1,35 @@
package org.jetbrains.ide
import com.intellij.testFramework.PlatformTestCase
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
import org.junit.rules.ExternalResource
import javax.swing.SwingUtilities
import com.intellij.openapi.application.WriteAction
fun invokeAndWaitIfNeed(runnable: () -> Unit) {
if (SwingUtilities.isEventDispatchThread()) runnable() else SwingUtilities.invokeAndWait(runnable)
}
inline fun runWriteAction(runnable: () -> Unit) {
val token = WriteAction.start()
try {
runnable()
}
finally {
token.finish()
}
}
public class FixtureRule() : ExternalResource() {
val projectFixture = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder().getFixture()
override fun before() {
PlatformTestCase.initPlatformLangPrefix()
invokeAndWaitIfNeed { projectFixture.setUp() }
}
override fun after() {
invokeAndWaitIfNeed { projectFixture.tearDown() }
}
}
@@ -0,0 +1,108 @@
package org.jetbrains.ide
import io.netty.handler.codec.http.HttpResponseStatus
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.hamcrest.CoreMatchers.equalTo
import org.jetbrains.ide.TestManager.TestDescriptor
import org.junit.Assert.assertThat
import java.net.URL
import java.net.HttpURLConnection
import com.google.gson.stream.JsonWriter
import java.io.OutputStreamWriter
import com.intellij.openapi.vfs.CharsetToolkit
import java.io.BufferedOutputStream
public class RestApiTest {
private val fixtureManager = FixtureRule()
private val manager = TestManager(fixtureManager.projectFixture)
private val _chain = RuleChain
.outerRule(fixtureManager)
.around(manager)
Rule
public fun getChain(): RuleChain = _chain
Test
TestDescriptor(filePath = "", status = 400)
public fun fileEmptyRequest() {
doTest()
}
Test
TestDescriptor(filePath = "foo.txt", relativeToProject = true, status = 200)
public fun relativeToProject() {
doTest()
}
Test
TestDescriptor(filePath = "foo.txt", relativeToProject = true, line = 1, status = 200)
public fun relativeToProjectWithLine() {
doTest()
}
Test
TestDescriptor(filePath = "foo.txt", relativeToProject = true, line = 1, column = 13, status = 200)
public fun relativeToProjectWithLineAndColumn() {
doTest()
}
Test
TestDescriptor(filePath = "fileInExcludedDir.txt", excluded = true, status = 200)
public fun inExcludedDir() {
doTest()
}
Test
TestDescriptor(filePath = "bar/42/foo.txt", doNotCreate = true, status = 404)
public fun relativeNonExistent() {
doTest()
}
Test
TestDescriptor(filePath = "_tmp_", doNotCreate = true, status = 404)
public fun absoluteNonExistent() {
doTest()
}
Test
TestDescriptor(filePath = "_tmp_", status = 200)
public fun absolute() {
doTest()
}
private fun doTest() {
val serviceUrl = "http://localhost:" + BuiltInServerManager.getInstance().getPort() + "/api/file"
var url = serviceUrl + (if (manager.filePath == null) "" else ("/" + manager.filePath))
val line = manager.annotation?.line ?: -1
if (line != -1) {
url += ":$line"
}
val column = manager.annotation?.column ?: -1
if (column != -1) {
url += ":$column"
}
var connection = URL(url).openConnection() as HttpURLConnection
val expectedStatus = HttpResponseStatus.valueOf(manager.annotation?.status ?: 200)
assertThat(HttpResponseStatus.valueOf(connection.getResponseCode()), equalTo(expectedStatus))
connection = URL("$serviceUrl?file=${manager.filePath ?: ""}&line=$line&column=$column").openConnection() as HttpURLConnection
assertThat(HttpResponseStatus.valueOf(connection.getResponseCode()), equalTo(expectedStatus))
connection = URL("$serviceUrl").openConnection() as HttpURLConnection
connection.setRequestMethod("POST")
connection.setDoOutput(true)
val writer = JsonWriter(OutputStreamWriter(BufferedOutputStream(connection.getOutputStream()), CharsetToolkit.UTF8_CHARSET))
writer.beginObject()
writer.name("file").value(manager.filePath)
writer.name("line").value(line)
writer.name("column").value(column)
writer.endObject()
writer.close()
assertThat(HttpResponseStatus.valueOf(connection.getResponseCode()), equalTo(expectedStatus))
}
}
@@ -0,0 +1,116 @@
package org.jetbrains.ide
import com.intellij.openapi.vfs.VirtualFile
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import com.intellij.openapi.util.text.StringUtil
import com.intellij.testFramework.fixtures.IdeaProjectTestFixture
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.util.io.FileUtilRt
import java.io.File
class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() {
class object {
val EXCLUDED_DIR_NAME = "excludedDir"
}
public var annotation: TestDescriptor? = null
public var filePath: String? = null
private var fileToDelete: VirtualFile? = null
private var ioFileToDelete: File? = null
Retention(RetentionPolicy.RUNTIME)
Target(ElementType.METHOD)
annotation public class TestDescriptor(public val filePath: String,
public val line: Int = -1,
public val column: Int = -1,
public val relativeToProject: Boolean = false,
public val excluded: Boolean = false,
public val doNotCreate: Boolean = false,
public val status: Int = 200)
override fun starting(description: Description) {
annotation = description.getAnnotation<TestDescriptor>(javaClass<TestDescriptor>())
if (annotation == null) {
return
}
filePath = StringUtil.nullize(annotation!!.filePath)
if (filePath == null) {
return
}
if (filePath!! == "_tmp_") {
if (annotation!!.doNotCreate) {
filePath = FileUtilRt.generateRandomTemporaryPath().getPath()
}
else {
ioFileToDelete = FileUtilRt.createTempFile("testFile", ".txt")
filePath = ioFileToDelete!!.getPath()
}
return
}
if (annotation!!.doNotCreate) {
return
}
invokeAndWaitIfNeed {
val normalizedFilePath = FileUtilRt.toSystemIndependentName(filePath)
if (annotation!!.relativeToProject) {
val root = projectFixture.getProject().getBaseDir()
runWriteAction {
fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath)
}
}
else {
if (annotation!!.excluded) {
ModuleRootModificationUtil.updateModel(projectFixture.getModule()) { model ->
val contentEntry = model.getContentEntries()[0]
val contentRoot = contentEntry.getFile()!!
runWriteAction {
contentRoot.findChild(EXCLUDED_DIR_NAME)?.delete(this@TestManager)
fileToDelete = contentRoot.createChildDirectory(this@TestManager, EXCLUDED_DIR_NAME)
fileToDelete!!.createChildData(this@TestManager, normalizedFilePath)
}
contentEntry.addExcludeFolder(fileToDelete!!)
}
filePath = "$EXCLUDED_DIR_NAME/$filePath"
}
else {
val root = ModuleRootManager.getInstance(projectFixture.getModule()).getSourceRoots()[0]
runWriteAction {
fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath)
}
}
}
}
}
override fun finished(description: Description?) {
if (annotation!!.excluded) {
ModuleRootModificationUtil.updateModel(projectFixture.getModule()) { model -> model.getContentEntries()[0].removeExcludeFolder(EXCLUDED_DIR_NAME) }
}
if (fileToDelete != null) {
invokeAndWaitIfNeed { runWriteAction { fileToDelete?.delete(this@TestManager) } }
fileToDelete = null
}
if (ioFileToDelete != null && !FileUtilRt.delete(ioFileToDelete)) {
ioFileToDelete!!.deleteOnExit()
}
annotation = null
filePath = null
}
}
@@ -23,6 +23,7 @@
<httpRequestHandler implementation="org.jetbrains.ide.XmlRpcServerImpl$XmlRpcRequestHandler"/>
<httpRequestHandler implementation="org.jetbrains.ide.ProjectSetRequestHandler"/>
<httpRequestHandler implementation="org.jetbrains.ide.DiffHttpService"/>
<httpRequestHandler implementation="org.jetbrains.ide.OpenFileHttpService"/>
<httpRequestHandler implementation="org.jetbrains.ide.AboutHttpService"/>
<httpRequestHandler implementation="org.jetbrains.builtInWebServer.BuiltInWebServer"/>
</extensions>