diff --git a/features/org.jkiss.dbeaver.core.feature/feature.xml b/features/org.jkiss.dbeaver.core.feature/feature.xml
index 712ce2ae52e3e685727b7dad75cca7a276677749..3956c06977cada49f26d627d4958be89701ec03f 100644
--- a/features/org.jkiss.dbeaver.core.feature/feature.xml
+++ b/features/org.jkiss.dbeaver.core.feature/feature.xml
@@ -41,6 +41,7 @@
+
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/.classpath b/plugins/org.jkiss.dbeaver.runtime.ide.core/.classpath
new file mode 100644
index 0000000000000000000000000000000000000000..43b986286a9e5fd1828793fa4b2353d02ab8625f
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/.project b/plugins/org.jkiss.dbeaver.runtime.ide.core/.project
new file mode 100644
index 0000000000000000000000000000000000000000..a39a6160b6f795a5a4dc08140446473ff44ade9a
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/.project
@@ -0,0 +1,28 @@
+
+
+ org.jkiss.dbeaver.runtime.ide.core
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/META-INF/MANIFEST.MF b/plugins/org.jkiss.dbeaver.runtime.ide.core/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000000000000000000000000000000000..4551a9d7d90b35beb345363691eabafc40cc46a8
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/META-INF/MANIFEST.MF
@@ -0,0 +1,11 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Vendor: %Bundle-Vendor
+Bundle-Name: %Bundle-Name
+Bundle-SymbolicName: org.jkiss.dbeaver.runtime.ide.core
+Bundle-Version: 4.2.2
+Bundle-Release-Date: 20171002
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
+ org.eclipse.core.resources;visibility:=reexport
+Export-Package: org.jkiss.dbeaver.runtime.ide.core
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/OSGI-INF/l10n/bundle.properties b/plugins/org.jkiss.dbeaver.runtime.ide.core/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..57e5cd3639772fa4c90629442860f57720b425e5
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,3 @@
+#Properties file for org.jkiss.dbeaver.runtime.ide.core
+Bundle-Vendor = JKISS
+Bundle-Name = DBeaver Runtime IDE Core
\ No newline at end of file
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/build.properties b/plugins/org.jkiss.dbeaver.runtime.ide.core/build.properties
new file mode 100644
index 0000000000000000000000000000000000000000..67e6565494e9b9a38f3a24b6a89447a31c62e91c
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = target/classes/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/pom.xml b/plugins/org.jkiss.dbeaver.runtime.ide.core/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fe84b0fdcc5d3afbe081d5504395ee90fbcac96c
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/pom.xml
@@ -0,0 +1,14 @@
+
+
+ 4.0.0
+
+ org.jkiss.dbeaver
+ dbeaver
+ 1.0.0
+ ../../
+
+ org.jkiss.dbeaver.runtime.ide.core
+ 4.2.2
+ eclipse-plugin
+
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/ide/core/IdeCore.java b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/ide/core/IdeCore.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b1607b49d05c2852aafa9531862bcbff615600e
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/ide/core/IdeCore.java
@@ -0,0 +1,18 @@
+package org.jkiss.dbeaver.runtime.ide.core;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+public class IdeCore {
+
+ public static final String BUNDLE_SYMBOLIC_NAME = "org.jkiss.dbeaver.runtime.ide.core"; //$NON-NLS-1$
+
+ public static IStatus createError(String message) {
+ return new Status(IStatus.ERROR, BUNDLE_SYMBOLIC_NAME, message);
+ }
+
+ public static IStatus createError(String message, Throwable t) {
+ return new Status(IStatus.ERROR, BUNDLE_SYMBOLIC_NAME, message, t);
+ }
+
+}
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/ide/core/WorkspaceResources.java b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/ide/core/WorkspaceResources.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf6f89d6b7bc8e71f4fbeb1b4cf07544287992b0
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/ide/core/WorkspaceResources.java
@@ -0,0 +1,44 @@
+package org.jkiss.dbeaver.runtime.ide.core;
+
+import java.net.URI;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.jkiss.dbeaver.runtime.internal.ide.core.CreateLinkedFileRunnable;
+import org.jkiss.dbeaver.runtime.internal.ide.core.CreateLinkedFolderRunnable;
+
+public class WorkspaceResources {
+
+ public static IStatus linkFile(IFile file, URI location, IProgressMonitor monitor) {
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ try {
+ workspace.run(new CreateLinkedFileRunnable(file, location), monitor);
+ } catch (CoreException e) {
+ return e.getStatus();
+ } catch (Throwable e) {
+ String message = CreateLinkedFileRunnable.composeErrorMessage(file, location);
+ return IdeCore.createError(message, e);
+ }
+ return Status.OK_STATUS;
+ }
+
+ public static IStatus linkFolder(IFolder folder, URI location, IProgressMonitor monitor) {
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ try {
+ workspace.run(new CreateLinkedFolderRunnable(folder, location), monitor);
+ } catch (CoreException e) {
+ return e.getStatus();
+ } catch (Throwable e) {
+ String message = CreateLinkedFolderRunnable.composeErrorMessage(folder, location);
+ return IdeCore.createError(message, e);
+ }
+ return Status.OK_STATUS;
+ }
+
+}
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/CreateLinkedFileRunnable.java b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/CreateLinkedFileRunnable.java
new file mode 100644
index 0000000000000000000000000000000000000000..487968e7bfd2972030ee0d3f38060f5ef1cb0ea9
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/CreateLinkedFileRunnable.java
@@ -0,0 +1,46 @@
+package org.jkiss.dbeaver.runtime.internal.ide.core;
+
+import java.net.URI;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.ICoreRunnable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.osgi.util.NLS;
+import org.jkiss.dbeaver.runtime.ide.core.IdeCore;
+
+public class CreateLinkedFileRunnable implements ICoreRunnable {
+
+ private final IFile file;
+ private final URI location;
+ private final int flags;
+
+ public CreateLinkedFileRunnable(IFile file, URI location)
+ {
+ this.file = file;
+ this.location = location;
+ this.flags = IResource.NONE;
+ }
+
+
+
+ @Override
+ public void run(IProgressMonitor monitor) throws CoreException
+ {
+ if (file == null || location == null) {
+ String message = composeErrorMessage(file, location);
+ IStatus error = IdeCore.createError(message);
+ throw new CoreException(error);
+ }
+ file.createLink(location, flags, monitor);
+ }
+
+ public static String composeErrorMessage(IFile file, URI location)
+ {
+ String message = NLS.bind(IdeCoreMessages.CreateLinkedFileRunnable_e_unable_to_link, file, location);
+ return message;
+ }
+
+}
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/CreateLinkedFolderRunnable.java b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/CreateLinkedFolderRunnable.java
new file mode 100644
index 0000000000000000000000000000000000000000..cba0de3faab8f03814e95d73701d673e1090f0dd
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/CreateLinkedFolderRunnable.java
@@ -0,0 +1,48 @@
+package org.jkiss.dbeaver.runtime.internal.ide.core;
+
+import java.net.URI;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.ICoreRunnable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.osgi.util.NLS;
+import org.jkiss.dbeaver.runtime.ide.core.IdeCore;
+
+public class CreateLinkedFolderRunnable implements ICoreRunnable {
+
+ private final IFolder folder;
+ private final URI location;
+ private final int flags;
+
+ public CreateLinkedFolderRunnable(IFolder folder, URI location)
+ {
+ this.folder = folder;
+ this.location = location;
+ this.flags = IResource.NONE;
+ }
+
+
+
+ @Override
+ public void run(IProgressMonitor monitor) throws CoreException
+ {
+ if (folder == null || location == null) {
+ String message = composeErrorMessage(folder, location);
+ IStatus error = IdeCore.createError(message);
+ throw new CoreException(error);
+ }
+ folder.createLink(location, flags, monitor);
+ }
+
+
+
+ public static String composeErrorMessage(IFolder folder, URI location)
+ {
+ String message = NLS.bind(IdeCoreMessages.CreateLinkedFolderRunnable_e_unable_to_link, folder, location);
+ return message;
+ }
+
+}
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/IdeCoreMessages.java b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/IdeCoreMessages.java
new file mode 100644
index 0000000000000000000000000000000000000000..4993d8c70e325ee467195aa81ea03b951c2f2faf
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/IdeCoreMessages.java
@@ -0,0 +1,20 @@
+package org.jkiss.dbeaver.runtime.internal.ide.core;
+
+import org.eclipse.osgi.util.NLS;
+
+public class IdeCoreMessages extends NLS {
+
+ private static final String BUNDLE_NAME = "org.jkiss.dbeaver.runtime.internal.ide.core.ide_core_messages"; //$NON-NLS-1$
+
+ public static String CreateLinkedFileRunnable_e_unable_to_link;
+ public static String CreateLinkedFolderRunnable_e_unable_to_link;
+
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, IdeCoreMessages.class);
+ }
+
+ private IdeCoreMessages()
+ {
+ }
+}
diff --git a/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/ide_core_messages.properties b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/ide_core_messages.properties
new file mode 100644
index 0000000000000000000000000000000000000000..e5230e66e780c85151e5ba45755dcf1cd3ffe51f
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.runtime.ide.core/src/org/jkiss/dbeaver/runtime/internal/ide/core/ide_core_messages.properties
@@ -0,0 +1,2 @@
+CreateLinkedFileRunnable_e_unable_to_link=Unable to link file {0} with location {1}
+CreateLinkedFolderRunnable_e_unable_to_link=Unable to link folder {0} with location {1}
diff --git a/pom.xml b/pom.xml
index 16a003b6a8d899242f69bcc461e0a33374d1f195..f97d28ff3a65b5c8b1b4a71ff6b31d39f6594662 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
modules/org.jkiss.utils
modules/org.jkiss.wmi
+ plugins/org.jkiss.dbeaver.runtime.ide.core
plugins/org.jkiss.dbeaver.core
plugins/org.jkiss.dbeaver.ui
plugins/org.jkiss.dbeaver.core.application
@@ -70,6 +71,8 @@
product/updateSite
product/standalone
+
+ tests/org.jkiss.dbeaver.runtime.ide.core.tests
diff --git a/tests/org.jkiss.dbeaver.runtime.ide.core.tests/.classpath b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/.classpath
new file mode 100644
index 0000000000000000000000000000000000000000..43b986286a9e5fd1828793fa4b2353d02ab8625f
--- /dev/null
+++ b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/org.jkiss.dbeaver.runtime.ide.core.tests/.project b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/.project
new file mode 100644
index 0000000000000000000000000000000000000000..02341ead98f0e46933f089d13cbbb4d7a8823a3c
--- /dev/null
+++ b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/.project
@@ -0,0 +1,28 @@
+
+
+ org.jkiss.dbeaver.runtime.ide.core.tests
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/tests/org.jkiss.dbeaver.runtime.ide.core.tests/META-INF/MANIFEST.MF b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000000000000000000000000000000000..c59d04270b0874981bc2756701267df58610749a
--- /dev/null
+++ b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name
+Bundle-SymbolicName: org.jkiss.dbeaver.runtime.ide.core.tests
+Bundle-Version: 4.2.2
+Bundle-Vendor: %Bundle-Vendor
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: org.junit,
+ org.jkiss.dbeaver.runtime.ide.core
diff --git a/tests/org.jkiss.dbeaver.runtime.ide.core.tests/OSGI-INF/l10n/bundle.properties b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..7039f1821572fa2501e805febe8d00c22e2f892a
--- /dev/null
+++ b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,3 @@
+#Properties file for org.jkiss.dbeaver.runtime.ide.core.tests
+Bundle-Vendor = JKISS
+Bundle-Name = DBeaver Runtime IDE Core Tests
\ No newline at end of file
diff --git a/tests/org.jkiss.dbeaver.runtime.ide.core.tests/build.properties b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/build.properties
new file mode 100644
index 0000000000000000000000000000000000000000..67e6565494e9b9a38f3a24b6a89447a31c62e91c
--- /dev/null
+++ b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = target/classes/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/
diff --git a/tests/org.jkiss.dbeaver.runtime.ide.core.tests/pom.xml b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3e250b75a63e9509d0ecadfbeb5d54b8241ae7e1
--- /dev/null
+++ b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/pom.xml
@@ -0,0 +1,14 @@
+
+
+ 4.0.0
+
+ org.jkiss.dbeaver
+ dbeaver
+ 1.0.0
+ ../../
+
+ org.jkiss.dbeaver.runtime.ide.core.tests
+ 4.2.2
+ eclipse-test-plugin
+
diff --git a/tests/org.jkiss.dbeaver.runtime.ide.core.tests/src/org/jkiss/dbeaver/runtime/ide/core/WorkspaceResourcesTest.java b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/src/org/jkiss/dbeaver/runtime/ide/core/WorkspaceResourcesTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..37b5266d4ea304016ddd8d4c6a2ca69ff2f6f213
--- /dev/null
+++ b/tests/org.jkiss.dbeaver.runtime.ide.core.tests/src/org/jkiss/dbeaver/runtime/ide/core/WorkspaceResourcesTest.java
@@ -0,0 +1,98 @@
+package org.jkiss.dbeaver.runtime.ide.core;
+
+import java.io.ByteArrayInputStream;
+import java.net.URI;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+@SuppressWarnings("nls")
+public class WorkspaceResourcesTest {
+
+ private static IProject project;
+ private static IFolder folder;
+ private static IFile file;
+ private static URI folderLocation;
+ private static URI fileLocation;
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Before
+ public void setUp() throws Exception
+ {
+ Assert.assertTrue(Platform.isRunning());
+
+ project = ResourcesPlugin.getWorkspace().getRoot().getProject("some_project");
+ try {
+ project.create(null);
+ project.open(null);
+ folder = project.getFolder("some_folder");
+ folder.create(true, true, null);
+ file = folder.getFile("some_file");
+ file.create(new ByteArrayInputStream("some_content".getBytes()), IResource.NONE, null);
+ } catch (CoreException e) {
+ System.out.println(e);
+ }
+ folderLocation = tempFolder.newFolder().toURI();
+ fileLocation = tempFolder.newFile().toURI();
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ if (project.exists()) {
+ project.delete(true, true, null);
+ }
+ }
+
+ @Test
+ public void testLinkFileNegative()
+ {
+ Assert.assertFalse(WorkspaceResources.linkFile(null, fileLocation, null).isOK());
+ Assert.assertFalse(WorkspaceResources.linkFile(file, null, null).isOK());
+ Assert.assertFalse(WorkspaceResources.linkFile(file, fileLocation, null).isOK());
+ }
+
+ @Test
+ public void testLinkFilePositive()
+ {
+ IFile another = folder.getFile("another");
+ IStatus linkFile = WorkspaceResources.linkFile(another, fileLocation, null);
+ Assert.assertTrue(linkFile.isOK());
+ Assert.assertTrue(another.isLinked());
+ URI locationURI = another.getLocationURI();
+ Assert.assertTrue(fileLocation.equals(locationURI));
+ }
+
+ @Test
+ public void testLinkFolderNegative()
+ {
+ Assert.assertFalse(WorkspaceResources.linkFolder(null, folderLocation, null).isOK());
+ Assert.assertFalse(WorkspaceResources.linkFolder(folder, null, null).isOK());
+ Assert.assertFalse(WorkspaceResources.linkFolder(folder, folderLocation, null).isOK());
+ }
+
+ @Test
+ public void testLinkFolderPositive()
+ {
+ IFolder another = folder.getFolder("another");
+ IStatus linkFolder = WorkspaceResources.linkFolder(another, folderLocation, null);
+ Assert.assertTrue(linkFolder.isOK());
+ Assert.assertTrue(another.isLinked());
+ URI locationURI = another.getLocationURI();
+ Assert.assertTrue(folderLocation.equals(locationURI));
+ }
+}