Compare commits

..

72 Commits

Author SHA1 Message Date
Pereki 8a1d515179 chore: complete refactor 2026-03-20 21:02:17 +01:00
Ruben 56a59089a0 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	Clients/VisualStudioCode/package.json
2026-02-17 14:08:26 +01:00
Vic Nightfall 1a0596ca71 Version bump 2025-09-28 14:20:59 +02:00
Ruben 3687d4bf6a fix: bump version 2025-09-26 09:33:15 +02:00
RubenKraft a9bbe834cc Update README.md 2025-09-24 14:43:07 +00:00
Ruben 4fbf9133a5 fix: bump version 2025-09-23 18:38:31 +02:00
Ruben 972d9014ba fix: bump version 2025-09-23 18:38:01 +02:00
Ruben 4f4404d366 fix: cleanup and fixes 2025-09-23 18:37:37 +02:00
Ruben 2dfbef6d7f fix: except VerifyError in Catch Clause 2025-09-23 18:26:58 +02:00
Ruben 7e6e968b0f feat: do not show 100 times the insert 2025-09-22 18:22:05 +02:00
Ruben dded453ba3 feat: update jar and Version 2025-09-22 15:42:25 +02:00
Ruben 254edf25af feat: update jar and Version 2025-09-19 19:20:16 +02:00
RubenKraft fc628f7547 Update README.md 2025-09-18 16:05:45 +00:00
Ruben 33d5fbb01d feat: update jar and Version 2025-09-18 17:55:45 +02:00
Ruben 6332daf2ad feat: update Compiler Jar 2025-09-18 17:54:17 +02:00
Ruben 01616ee985 feat: update vscode client 2025-09-17 19:11:55 +02:00
Ruben 29f0e6a05a Merge remote-tracking branch 'origin/main'
# Conflicts:
#	Clients/VisualStudioCode/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar
#	Clients/VisualStudioCode/package.json
2025-09-17 19:08:53 +02:00
Ruben ed26f869c3 feat #40: only insert Type as normal name without package reference as it is implicitly imported 2025-09-17 14:29:46 +02:00
Ruben ee0b51868c feat: move compilation into compiler 2025-09-17 11:32:46 +02:00
Ruben 98b32fd418 feat #40: remove Diagnostic if it has been selected 2025-09-17 11:08:07 +02:00
Ruben 7bc07d9ac1 feat: update vscode client 2025-09-15 18:51:24 +02:00
Ruben 558fcb5ebb feat: initially check syntax when opening document 2025-09-15 13:50:20 +02:00
Ruben 1e4541b705 feat: update 2025-09-11 12:53:41 +02:00
Ruben 205b243d32 feat: compile multiple Files 2025-09-10 19:30:40 +02:00
Ruben dda95006ef feat: refactor and fix diagnostics for parsererrors 2025-09-10 18:26:37 +02:00
Ruben 1efec1d854 feat: refactor and add command for restarting 2025-09-10 17:17:40 +02:00
Ruben 74d4f7018b feat: reset caches 2025-09-10 16:34:48 +02:00
Ruben f54004b21a feat: reset cache when opening new file 2025-09-10 14:24:15 +02:00
Ruben 1b84b77677 feat: fix characters 2025-09-09 21:09:58 +02:00
Ruben 6b40ce7e64 feat: add fixed Inserts 2025-09-09 20:27:19 +02:00
Ruben 3fe253ae63 feat: only calculate wenn a TPH is used and therefore the AST will not represent the ResultSet 2025-09-09 19:04:42 +02:00
Ruben 9d42e829e2 feat: add output 2025-09-09 18:41:35 +02:00
Ruben a88e34431f feat: update 2025-09-09 16:45:33 +02:00
Ruben a53b79f91d feat: update 2025-09-08 22:00:04 +02:00
Ruben e119d42148 feat: also reset TPH Names when getting normal Interface Response 2025-08-12 16:22:42 +02:00
Ruben 1e125407f1 feat: finally positions are handled correctly 2025-08-07 16:56:49 +02:00
Ruben 579d736efc feat: finally positions are handled correctly 2025-08-07 16:55:23 +02:00
Ruben c5760210fe feat: add Compiler jar to git 2025-08-07 16:33:18 +02:00
Ruben 2e6be4079a feat: positionAdjustment 2025-08-07 16:18:26 +02:00
Ruben bb9be2c319 feat: infere Lambdas in Methods 2025-07-12 11:55:59 +02:00
Ruben 57c82542f4 refactor: add Method instead of calculating directly 2025-07-12 11:35:43 +02:00
Ruben cb54606a93 feat: add lambda inference for Field Variables 2025-07-12 11:15:34 +02:00
Ruben 31ed1f16b6 feat: import all generics when one is selected 2025-07-11 21:20:04 +02:00
Ruben 5569b6d0db feat: add Picture and Description to VSC Client 2025-07-09 16:15:47 +02:00
Ruben 2a87b85dc1 fix: display ParserErrors 2025-07-09 15:47:21 +02:00
Ruben 11fb843efe docs: add Doc for installation of LSP Client for Intellij 2025-07-09 15:25:15 +02:00
Ruben 701caab9cd fix: fix generic Display and enable them 2025-07-08 20:45:49 +02:00
Ruben c2cbd0aa92 fix: reduction of ResultSet now also bases on last saved File 2025-07-08 19:54:35 +02:00
Ruben e1b000b976 fix: fix position adjustment 2025-07-08 19:28:06 +02:00
RubenKraft 7de2b2764a Merge pull request 'refactor' (#37) from refactor into main
Reviewed-on: #37
2025-07-07 18:38:09 +00:00
Ruben 27282721bc refactor: type Algorithm 2025-07-07 20:36:13 +02:00
Ruben 144cb84c2c refactor: type Algorithm 2025-07-07 20:19:58 +02:00
Ruben ae5f9a1771 refactor: add Change Handler 2025-07-07 17:36:00 +02:00
Ruben c8fa373972 refactor: rename package 2025-07-07 16:28:17 +02:00
Ruben f1f0fb8bf3 refactor: Handle Save with Services 2025-07-07 16:27:31 +02:00
Ruben d6dd414aba #10: add initial support for Intellij 2025-07-06 21:27:21 +02:00
Ruben 2b6fc2fc91 feat: add Constructor to get inferred 2025-06-10 15:47:58 +02:00
Ruben 762b86be85 feat: add support for fields 2025-06-10 15:39:51 +02:00
RubenKraft 38166cf6f2 Merge pull request 'reduce-result-set' (#36) from reduce-result-set into main
Reviewed-on: #36
2025-06-05 14:55:53 +00:00
Ruben c012471a22 feat: reduce Resultset when selecting a Type 2025-06-04 17:19:19 +02:00
Ruben 0091c9ef71 feat: reduce Resultset when selecting a Type 2025-06-02 17:58:49 +02:00
Ruben f6ed883ddb feat: add boolean for enabling and disabling generics 2025-06-02 14:06:28 +02:00
Ruben ae3b5e80d0 feat: add originalName Attribute 2025-06-02 13:38:08 +02:00
Ruben 4226623abc feat: add variable to store current Result and add Method to recalculate 2025-06-02 13:28:46 +02:00
Ruben 637478b1c3 feat: activate recompilation 2025-06-02 13:07:57 +02:00
Ruben 24c5cd823f feat: remove Diagnostic if it has been selected 2025-05-14 15:28:39 +02:00
Ruben 26eedc5b32 feat: remove Typehint if it has been selected 2025-05-14 15:18:19 +02:00
Ruben c21f09496e feat: fix position of Hints when new Text is added to the same line 2025-05-14 14:27:49 +02:00
Ruben 39ae1d66bc feat: add compiler jar to LSP in libs Folder 2025-05-13 19:15:41 +02:00
Ruben 6ee0a74b22 feat: change Name and update jar 2025-05-12 19:51:15 +02:00
Ruben ca6fa89e3f Merge remote-tracking branch 'origin/main' 2025-05-12 19:46:28 +02:00
Ruben 8cde997c15 feat: deactivate display if one Variable is beeing inserted 2025-05-12 19:46:17 +02:00
54 changed files with 4507 additions and 1507 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
-28
View File
@@ -1,28 +0,0 @@
plugins {
id 'java'
id 'org.jetbrains.intellij.platform' version '2.2.1'
}
group = 'org.example'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
maven { url "https://jitpack.io" }
intellijPlatform {
defaultRepositories()
}
}
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation "com.github.Ballerina-Platform.lsp4intellij:lsp4intellij:master-SNAPSHOT"
}
test {
useJUnitPlatform()
}
+41
View File
@@ -0,0 +1,41 @@
plugins {
id("org.jetbrains.intellij") version "1.17.3"
kotlin("jvm") version "1.9.0"
}
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation(kotlin("stdlib"))
implementation("com.github.ballerina-platform:lsp4intellij:0.9.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "17"
}
}
intellij {
version.set("2023.3")
}
tasks {
patchPluginXml {
sinceBuild.set("233")
untilBuild.set("233.*")
}
}
+13
View File
@@ -0,0 +1,13 @@
# Installation des Intellij Language Clients
Um den Language Client für Intellij installieren zu können, kann die Erweiterung
[LSP4IJ](https://plugins.jetbrains.com/plugin/23257-lsp4ij) installiert werden.
## Installation
1. Installiere [LSP4IJ](https://plugins.jetbrains.com/plugin/23257-lsp4ij)
2. Öffne den Language Server Tab
3. Füge einen neuen Language-Server hinzu
4. Vergebe einen Namen und füge folgenden Command hinzu: `java -jar "<path/to/LanguageServer.jar>`
5. Füge unter Mappings -> File name patterns *.jav und Language ID java_tx hinzu
6. öffne eine Datei mit Dateiendung .jav
7. Fertig
@@ -1,17 +0,0 @@
package org.example;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
public static void main(String[] args) {
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
// to see how IntelliJ IDEA suggests fixing it.
System.out.printf("Hello and welcome!");
for (int i = 1; i <= 5; i++) {
//TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.
System.out.println("i = " + i);
}
}
}
@@ -0,0 +1,14 @@
package kotlin.com.example.lsp;
import com.intellij.openapi.application.PreloadingActivity;
import com.intellij.openapi.progress.ProgressIndicator;
import org.wso2.lsp4intellij.IntellijLanguageClient;
import org.wso2.lsp4intellij.client.languageserver.serverdefinition.RawCommandServerDefinition;
public class PreloadActivityTX extends PreloadingActivity {
@Override
public void preload(ProgressIndicator indicator) {
String[] command = new String[]{"java", "-jar", "/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar"};
IntellijLanguageClient.addServerDefinition(new RawCommandServerDefinition("jav", command));
}
}
@@ -13,7 +13,7 @@ see also jetbrains documentation: https://plugins.jetbrains.com/docs/intellij/pl
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<!-- register a preloading activity. You need to init IntellijLanguageClient with your config, see readme --> <!-- register a preloading activity. You need to init IntellijLanguageClient with your config, see readme -->
<preloadingActivity implementation="your.plugin.MyPreloadingActivity" id="your.plugin.MyPreloadingActivity"/> <preloadingActivity implementation="kotlin.com.example.lsp.PreloadActivityTX" id="kotlin.com.example.lsp.PreloadActivityTX"/>
<!-- register intellijLanguageClient as a Service OR as a plugin component (see readme)... --> <!-- register intellijLanguageClient as a Service OR as a plugin component (see readme)... -->
<applicationService serviceImplementation="org.wso2.lsp4intellij.IntellijLanguageClient"/> <applicationService serviceImplementation="org.wso2.lsp4intellij.IntellijLanguageClient"/>
+4
View File
@@ -9,3 +9,7 @@ vsc-extension-quickstart.md
**/*.map **/*.map
**/*.ts **/*.ts
**/.vscode-test.* **/.vscode-test.*
!JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar
!JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar
+2 -70
View File
@@ -1,71 +1,3 @@
# lspclient README # Java-TX Language Server
This is the README for your extension "lspclient". After writing up a brief description, we recommend including the following sections. This is the official Plugin for Java-TX.
## Features
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
For example if there is an image subfolder under your extension project workspace:
\!\[feature X\]\(images/feature-x.png\)
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
## Requirements
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
## Extension Settings
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
For example:
This extension contributes the following settings:
* `myExtension.enable`: Enable/disable this extension.
* `myExtension.thing`: Set to `blah` to do something.
## Known Issues
Calling out known issues can help limit users opening duplicate issues against your extension.
## Release Notes
Users appreciate release notes as you update your extension.
### 1.0.0
Initial release of ...
### 1.0.1
Fixed issue #.
### 1.1.0
Added features X, Y, and Z.
---
## Following extension guidelines
Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension.
* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
## Working with Markdown
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
## For more information
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
**Enjoy!**
Binary file not shown.

After

Width:  |  Height:  |  Size: 902 KiB

File diff suppressed because it is too large Load Diff
+8 -6
View File
@@ -1,8 +1,9 @@
{ {
"name": "lspclient", "icon": "TX.png",
"displayName": "LSPClient", "name": "java-tx-language-extension",
"description": "", "displayName": "Java-TX Language Extension",
"version": "0.0.1", "description": "The Language Extension for Java-TX with Typehints and Syntax Checks",
"version": "0.0.16",
"engines": { "engines": {
"vscode": "^1.94.0" "vscode": "^1.94.0"
}, },
@@ -16,8 +17,8 @@
"contributes": { "contributes": {
"commands": [ "commands": [
{ {
"command": "lspclient.helloWorld", "command": "tx.restartLanguageServer",
"title": "Hello World" "title": "TX: Restart Language Server"
} }
] ]
}, },
@@ -41,6 +42,7 @@
"typescript": "^5.6.2" "typescript": "^5.6.2"
}, },
"dependencies": { "dependencies": {
"@vscode/vsce": "^3.6.2",
"vscode-languageclient": "^9.0.1" "vscode-languageclient": "^9.0.1"
} }
} }
+60 -67
View File
@@ -1,77 +1,70 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { workspace, ExtensionContext } from 'vscode';
import { import {
LanguageClient, LanguageClient,
LanguageClientOptions, LanguageClientOptions,
ServerOptions, ServerOptions
TransportKind
} from 'vscode-languageclient/node'; } from 'vscode-languageclient/node';
let client: LanguageClient | undefined;
function createClient(context: vscode.ExtensionContext): LanguageClient {
const extensionPath = context.extensionPath;
const jarPath = extensionPath + '/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar';
const serverOptions: ServerOptions = {
run: { command: 'java', args: ['-Xss10m', '-jar', jarPath] },
debug: { command: 'java', args: ['-Xss10m', '-jar', jarPath] }
};
// This method is called when your extension is activated const clientOptions: LanguageClientOptions = {
// Your extension is activated the very first time the command is executed documentSelector: [{ scheme: 'file', pattern: '**/*.jav' }],
export function activate(context: vscode.ExtensionContext) { synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.jav')
},
revealOutputChannelOn: 4
};
return new LanguageClient(
'javaTxLanguageServer',
const workspaceFolder = context.extensionPath; 'Java-TX Language Server',
const serverOptions: ServerOptions = { serverOptions,
run: { clientOptions
command: 'java', );
args: ['-jar', workspaceFolder + "/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar"], // Absolute Pfadangabe
},
debug: {
command: 'java',
args: ['-jar', workspaceFolder + "/JavaTXLanguageServer-1.0-SNAPSHOT-jar-with-dependencies.jar"], // Absolute Pfadangabe für Debug
}
};
console.log("SERVER CREATED.")
console.log(workspaceFolder)
// Clientoptionen: definiere, welche Dateitypen der Client unterstützt
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'java' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.jav')
},
revealOutputChannelOn: 4
};
// Language Client erstellen und starten
const client = new LanguageClient(
'javaLanguageServer', // ID des Clients
'Java Language Server', // Name des Clients
serverOptions,
clientOptions
);
// Client starten
client.start().then(() => {
console.log("Language Client erfolgreich gestartet");
}).catch(error => {
console.error("Fehler beim Starten des Language Clients:", error);
});
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "tx" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
const disposable = vscode.commands.registerCommand('lspclient.helloWorld', () => {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from TX!');
});
context.subscriptions.push(disposable);
} }
// This method is called when your extension is deactivated export async function activate(context: vscode.ExtensionContext) {
export function deactivate() { } client = createClient(context);
try {
await client.start();
console.log('Java-TX Language Server started successfully.');
} catch (err) {
console.error('Failed to start Language Client:', err);
}
const restart = vscode.commands.registerCommand('tx.restartLanguageServer', async () => {
if (!client) {
vscode.window.showWarningMessage('Language Client is not initialized.');
return;
}
try {
await client.stop();
client.dispose();
} catch (e) {
console.error('Error stopping Language Client:', e);
}
client = createClient(context);
try {
await client.start();
vscode.window.showInformationMessage('Java-TX Language Server restarted.');
} catch (e) {
console.error('Error restarting Language Client:', e);
vscode.window.showErrorMessage('Failed to restart Language Server.');
}
});
context.subscriptions.push(restart);
}
export function deactivate(): Thenable<void> | undefined {
return client?.stop();
}
+12
View File
@@ -20,6 +20,11 @@
<artifactId>antlr4</artifactId> <artifactId>antlr4</artifactId>
<version>4.11.1</version> <version>4.11.1</version>
</dependency> </dependency>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
<version>4.12</version>
</dependency>
<dependency> <dependency>
<groupId>log4j</groupId> <groupId>log4j</groupId>
<artifactId>log4j</artifactId> <artifactId>log4j</artifactId>
@@ -75,6 +80,12 @@
<version>0.1</version> <version>0.1</version>
</dependency> </dependency>
</dependencies> </dependencies>
<properties>
<!-- Setzt Java 21 als globale Version -->
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<build> <build>
<plugins> <plugins>
@@ -96,6 +107,7 @@
<configuration> <configuration>
<source>21</source> <source>21</source>
<target>21</target> <target>21</target>
<release>21</release>
</configuration> </configuration>
</plugin> </plugin>
@@ -1,81 +1,49 @@
package de.dhbw; package de.dhbw;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.TextDocumentService; import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService; import org.eclipse.lsp4j.services.WorkspaceService;
import org.eclipse.lsp4j.services.LanguageServer;
import java.net.URI;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/**
*
* Configuration of the Language Server
*
* */
public class JavaTXLanguageServer implements LanguageServer {
private static final Logger logger = LogManager.getLogger(JavaTXLanguageServer.class);
private LanguageClient client;
public void connect(LanguageClient client) { public class JavaTXLanguageServer implements LanguageServer {
this.client = client;
textDocumentService.setClient(client);
}
private final JavaTXTextDocumentService textDocumentService = new JavaTXTextDocumentService(); private final JavaTXTextDocumentService textDocumentService = new JavaTXTextDocumentService();
private final JavaTXWorkspaceService workspaceService = new JavaTXWorkspaceService(); private final JavaTXWorkspaceService workspaceService = new JavaTXWorkspaceService();
@Override public void connect(LanguageClient client) {
public void setTrace(SetTraceParams params) { textDocumentService.setClient(client);
} }
/**
* Configure the Features of the LanguageServer
* */
@Override @Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) { public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
ServerCapabilities capabilities = new ServerCapabilities(); ServerCapabilities capabilities = new ServerCapabilities();
capabilities.setDocumentFormattingProvider(true); capabilities.setDocumentFormattingProvider(true);
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full); capabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
capabilities.setHoverProvider(false);
capabilities.setInlayHintProvider(true); capabilities.setInlayHintProvider(true);
capabilities.setCodeActionProvider(true); capabilities.setCodeActionProvider(true);
capabilities.setTextDocumentSync(TextDocumentSyncKind.Full);
capabilities.setCompletionProvider(new CompletionOptions(true, List.of())); capabilities.setCompletionProvider(new CompletionOptions(true, List.of()));
capabilities.setWorkspaceSymbolProvider(false);
if(params.getWorkspaceFolders() != null && !params.getWorkspaceFolders().isEmpty()) { if (params.getWorkspaceFolders() != null && !params.getWorkspaceFolders().isEmpty()) {
textDocumentService.setFileRoot(params.getWorkspaceFolders()); textDocumentService.setFileRoot(params.getWorkspaceFolders());
} }
return CompletableFuture.supplyAsync(() -> new InitializeResult(capabilities)); return CompletableFuture.completedFuture(new InitializeResult(capabilities));
} }
/**
* @return the TextDocumentService
* */
@Override @Override
public TextDocumentService getTextDocumentService() { public TextDocumentService getTextDocumentService() {
return textDocumentService; return textDocumentService;
} }
/**
* @return the WorkspaceService
* */
@Override @Override
public WorkspaceService getWorkspaceService() { public WorkspaceService getWorkspaceService() {
return workspaceService; return workspaceService;
} }
/**
* @return the Client
* */
public LanguageClient getClient() {
return client;
}
@Override @Override
public CompletableFuture<Object> shutdown() { public CompletableFuture<Object> shutdown() {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
@@ -1,30 +1,15 @@
package de.dhbw; package de.dhbw;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.launch.LSPLauncher; import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClient;
/**
*
* Start the JavaTX language Server and use System In and System Out for Communication
*
* */
public class JavaTXLanguageServerLauncher { public class JavaTXLanguageServerLauncher {
private static final Logger logger = LogManager.getLogger(JavaTXLanguageServerLauncher.class); public static void main(String[] args) throws Exception {
JavaTXLanguageServer server = new JavaTXLanguageServer();
public static void main(String[] args) { var launcher = LSPLauncher.createServerLauncher(server, System.in, System.out);
try { LanguageClient client = launcher.getRemoteProxy();
JavaTXLanguageServer server = new JavaTXLanguageServer(); server.connect(client);
var launcher = LSPLauncher.createServerLauncher(server, System.in, System.out); launcher.startListening().get();
LanguageClient client = launcher.getRemoteProxy();
server.connect(client);
launcher.startListening().get();
} catch (Exception e) {
e.printStackTrace();
}
} }
} }
@@ -1,565 +1,144 @@
package de.dhbw; package de.dhbw;
import com.google.common.base.Stopwatch; import de.dhbw.handler.ChangeHandler;
import de.dhbw.handler.CodeActionHandler;
import de.dhbw.handler.FormattingHandler;
import de.dhbw.handler.SaveHandler;
import de.dhbw.helper.CodeSnippetOptions; import de.dhbw.helper.CodeSnippetOptions;
import de.dhbw.helper.ConversionHelper;
import de.dhbw.helper.TextHelper; import de.dhbw.helper.TextHelper;
import de.dhbw.helper.TypeResolver; import de.dhbw.helper.TypeResolver;
import de.dhbw.model.LSPVariable;
import de.dhbw.model.SnippetWithName; import de.dhbw.model.SnippetWithName;
import de.dhbw.model.Type; import de.dhbw.service.CacheService;
import de.dhbwstuttgart.languageServerInterface.LanguageServerInterface; import de.dhbw.service.ClientService;
import de.dhbwstuttgart.languageServerInterface.ParserInterface; import de.dhbw.service.DocumentStore;
import de.dhbwstuttgart.languageServerInterface.model.ParserError; import de.dhbw.service.LogService;
import org.apache.log4j.LogManager; import de.dhbw.service.ParserService;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService; import org.eclipse.lsp4j.services.TextDocumentService;
import java.io.File;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.util.ArrayList;
import java.util.*; import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
/** public class JavaTXTextDocumentService implements TextDocumentService {
* Handles Actions in Documents, such as Autocompletion, Change-Events and Syntax-Checks
*/
public class JavaTXTextDocumentService implements org.eclipse.lsp4j.services.TextDocumentService {
private final FormattingHandler formattingHandler;
private final SaveHandler saveHandler;
private final ChangeHandler changeHandler;
private final CodeActionHandler codeActionHandler;
private final ParserService parserService;
private final DocumentStore documentStore;
private final LogService logService;
private final ClientService clientService;
private final CacheService cacheService;
private final CodeSnippetOptions codeSnippetOptions;
private final TypeResolver typeResolver;
private static final Logger logger = LogManager.getLogger(JavaTXTextDocumentService.class); private LanguageClient client;
LanguageClient client;
HashMap<String, List<InlayHint>> globalInlayHintMap = new HashMap<>(); public JavaTXTextDocumentService() {
Boolean currentlyCalculating = false; TextHelper textHelper = new TextHelper();
HashMap<String, List<Diagnostic>> globalDiagnosticsMap = new HashMap<>(); this.cacheService = new CacheService();
HashMap<String, String> textDocuments = new HashMap<>(); this.clientService = new ClientService(null);
CodeSnippetOptions codeSnippetOptions = new CodeSnippetOptions(); this.typeResolver = new TypeResolver();
TextHelper textHelper = new TextHelper(); this.documentStore = new DocumentStore();
TypeResolver typeResolver = new TypeResolver(); this.conversionHelper = new ConversionHelper(textHelper, documentStore);
Path fileRoot = null; this.logService = new LogService(clientService);
this.formattingHandler = new FormattingHandler(documentStore);
this.parserService = new ParserService(conversionHelper);
this.codeActionHandler = new CodeActionHandler(documentStore, typeResolver, logService, conversionHelper);
this.saveHandler = new SaveHandler(typeResolver, documentStore, logService, cacheService, conversionHelper, clientService, parserService);
this.changeHandler = new ChangeHandler(documentStore, parserService, conversionHelper, clientService, typeResolver, cacheService, logService);
this.codeSnippetOptions = new CodeSnippetOptions();
}
private final ConversionHelper conversionHelper;
public void setClient(LanguageClient client) { public void setClient(LanguageClient client) {
this.client = client; this.client = client;
clientService.setClient(client);
} }
public void setFileRoot(List<WorkspaceFolder> root) { public void setFileRoot(List<WorkspaceFolder> root) {
if (root != null && !root.isEmpty()) {
//TODO: Nicht nur das erste Element nehmen sondern alle beachten Path.of(URI.create(root.getFirst().getUri()));
fileRoot = Path.of(URI.create(root.get(0).getUri())); }
} }
/**
* Handles Completion Events.
* In this case you can select codeSnippets.
*
* @param params the completion Context
* @return the Auto-Completion Items that will be displayed
*/
@Override @Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) { public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams params) {
List<CompletionItem> completions = new ArrayList<>(); List<CompletionItem> completions = new ArrayList<>();
for (SnippetWithName snippet : codeSnippetOptions.getSnippets()) {
CompletionItem item = new CompletionItem(snippet.name());
for (SnippetWithName elem : codeSnippetOptions.getSnippets()) {
CompletionItem item = new CompletionItem(elem.getName());
item.setKind(CompletionItemKind.Snippet); item.setKind(CompletionItemKind.Snippet);
item.setInsertText(elem.getSnippet()); item.setInsertText(snippet.snippet());
item.setInsertTextFormat(InsertTextFormat.Snippet); item.setInsertTextFormat(InsertTextFormat.Snippet);
completions.add(item); completions.add(item);
} }
return CompletableFuture.completedFuture(Either.forLeft(completions)); return CompletableFuture.completedFuture(Either.forLeft(completions));
} }
/**
* Handles didOpen-Event.
* Puts the Text-Document in the text-Document Hashmap
*
* @param params the Context of the newly opened Document
*/
@Override @Override
public void didOpen(DidOpenTextDocumentParams params) { public void didOpen(DidOpenTextDocumentParams params) {
textDocuments.put(params.getTextDocument().getUri(), params.getTextDocument().getText()); cacheService.reset();
typeResolver.reset();
String uri = params.getTextDocument().getUri();
String text = params.getTextDocument().getText();
List<Diagnostic> syntaxErrors = parserService.getDiagnosticsOfErrors(text, uri);
if (!syntaxErrors.isEmpty()) {
clientService.publishDiagnostics(uri, syntaxErrors);
}
client.refreshDiagnostics();
client.refreshInlayHints();
documentStore.saveFile(uri, text);
} }
/**
* Handles didChange-Event.
* updates textDocument-State on Server and run Syntax-Check. If an Error is found it will get displayed as a Diagnostic.
*
* @param params the Context of the Changed Document, including the incremental change as well as the whole Document
*/
@Override @Override
public void didChange(DidChangeTextDocumentParams params) { public void didChange(DidChangeTextDocumentParams params) {
log("[didChange] Client triggered didChange Event.", MessageType.Info); logService.log("[didChange] Client triggered didChange event.", MessageType.Info);
cacheService.reset();
AtomicReference<String> summedUp = new AtomicReference<>(""); String uri = params.getTextDocument().getUri();
params.getContentChanges().forEach(el -> summedUp.set(summedUp.get() + el.getText())); String fileContent = documentStore.getFileContent(uri);
textDocuments.put(params.getTextDocument().getUri(), summedUp.get()); documentStore.saveFile(uri, fileContent);
changeHandler.didChange(params);
String input = summedUp.get();
ParserInterface parserInterface = new ParserInterface();
List<ParserError> parserErrors = parserInterface.getParseErrors(input);
List<Diagnostic> diagnostics = parserErrors.stream().map(el -> {
Range errorRange = new Range(
new Position(el.getLine() - 1, el.getCharPositionInLine()), // Startposition
new Position(el.getLine() - 1, el.getEndCharPosition()) // Endposition
);
return new Diagnostic(
errorRange,
el.getMsg(),
DiagnosticSeverity.Error,
"JavaTX Language Server"
);
}).toList();
PublishDiagnosticsParams diagnosticsParams = new PublishDiagnosticsParams(params.getTextDocument().getUri(), diagnostics);
client.publishDiagnostics(diagnosticsParams);
} }
/**
* Handles a Formatting-Event
*
* @param params the Context of the Formatting
*/
@Override @Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) { public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
log("[formatting] Client requested formatting.", MessageType.Info); logService.log("[formatting] Client requested formatting.", MessageType.Info);
return CompletableFuture.completedFuture(formattingHandler.handleFormat(params));
List<TextEdit> edits = new ArrayList<>();
String[] lines = textDocuments.get(params.getTextDocument().getUri()).split("\n");
StringBuilder formattedText = new StringBuilder();
for (String line : lines) {
formattedText.append(line.stripTrailing()).append("\n");
}
TextEdit edit = new TextEdit();
edit.setRange(new Range(new Position(0, 0), new Position(lines.length, 0)));
edit.setNewText(formattedText.toString().trim());
edits.add(edit);
return CompletableFuture.completedFuture(edits);
} }
@Override @Override
public void didClose(DidCloseTextDocumentParams params) { public void didClose(DidCloseTextDocumentParams params) {
} }
private void log(String message, MessageType type) {
client.logMessage(new MessageParams(type, message));
switch (type) {
case Error -> logger.error(message);
case Warning -> logger.warn(message);
case Info -> logger.info(message);
default -> logger.debug(message);
}
}
private InlayHint getInlayHint(LSPVariable variable) {
InlayHint inlayHint = new InlayHint();
String typeDisplay = "";
for (Type type : variable.getPossibleTypes()) {
typeDisplay += " | " + type.getType().replaceAll("GTV ", "");
}
inlayHint.setLabel(typeDisplay.length() > 2 ? typeDisplay.substring(2) : typeDisplay);
inlayHint.setPosition(new Position(variable.getLine() - 1, variable.getCharPosition()));
inlayHint.setKind(InlayHintKind.Parameter);
inlayHint.setPaddingRight(true);
inlayHint.setPaddingRight(true);
return inlayHint;
}
private Diagnostic getDiagnostic(LSPVariable variable, String fileUri, Type type) {
Range errorRange = new Range(
new Position(variable.getLine() - 1, variable.getCharPosition()), // Startposition
new Position(variable.getLine() - 1, textHelper.getEndingCharOfStartingChar(variable.getLine() - 1, variable.getCharPosition(), textDocuments.get(fileUri))) // Endposition
);
Diagnostic diagnostic = new Diagnostic(
errorRange,
//TODO: REMOVE! Temporary Fix because GTV, like TPH can be thrown away in the TypeResolver
type.getType().replaceAll("GTV ", ""),
DiagnosticSeverity.Hint,
"JavaTX Language Server"
);
diagnostic.setCode(type.isGeneric() ? "GENERIC" : "TYPE");
return diagnostic;
}
private void updateGlobalMaps(List<Diagnostic> diagnostics, List<InlayHint> typeHint, String uri) {
globalDiagnosticsMap.put(uri, diagnostics);
globalInlayHintMap.put(uri, typeHint);
}
private void updateClient(LanguageClient client) {
client.refreshInlayHints();
client.refreshDiagnostics();
}
private void startLoading(String taskName, String title, LanguageClient client) {
Either<String, Integer> token = Either.forLeft(taskName);
client.createProgress(new WorkDoneProgressCreateParams(token));
WorkDoneProgressBegin begin = new WorkDoneProgressBegin();
begin.setTitle(title);
begin.setCancellable(false);
begin.setPercentage(0);
ProgressParams beginParams = new ProgressParams(token, Either.forLeft(begin));
client.notifyProgress(beginParams);
}
private void stopLoading(String taskName, String title, LanguageClient client) {
Either<String, Integer> token = Either.forLeft(taskName);
WorkDoneProgressEnd end = new WorkDoneProgressEnd();
end.setMessage(title);
ProgressParams endParams = new ProgressParams(token, Either.forLeft(end));
client.notifyProgress(endParams);
}
@Override @Override
public void didSave(DidSaveTextDocumentParams didSaveTextDocumentParams) { public void didSave(DidSaveTextDocumentParams params) {
log("[didSave] Client triggered didSave-Event.", MessageType.Info); logService.log("[didSave] Client triggered didSave event.");
clientService.startProgress("compile-task", "Inferring types...");
startLoading("compile-task", "Inferring types...", client); saveHandler.handleSave(params);
clientService.stopProgress("compile-task", "Types successfully inferred");
LanguageServerInterface languageServerInterface = new LanguageServerInterface(); clientService.refreshClient();
if (!currentlyCalculating) {
currentlyCalculating = true;
ArrayList<Diagnostic> diagnostics = new ArrayList<>();
var sWatch = Stopwatch.createUnstarted();
sWatch.start();
try {
if (textDocuments.get(didSaveTextDocumentParams.getTextDocument().getUri()) == null) {
log("[didSave] Input of Text Document is null in TextDocument-Hashmap.", MessageType.Error);
}
ArrayList<LSPVariable> typesOfMethodAndParameters = typeResolver.infereInput(textDocuments.get(didSaveTextDocumentParams.getTextDocument().getUri()), didSaveTextDocumentParams.getTextDocument().getUri());
List<InlayHint> typeHint = new ArrayList<>();
for (var variable : typesOfMethodAndParameters) {
InlayHint inlayHint = getInlayHint(variable);
typeHint.add(inlayHint);
//Diagnostics of Types
for (var type : variable.getPossibleTypes()) {
Diagnostic diagnostic = getDiagnostic(variable, didSaveTextDocumentParams.getTextDocument().getUri(), type);
diagnostics.add(diagnostic);
}
}
updateGlobalMaps(diagnostics, typeHint, didSaveTextDocumentParams.getTextDocument().getUri());
PublishDiagnosticsParams diagnosticsParams = new PublishDiagnosticsParams(didSaveTextDocumentParams.getTextDocument().getUri(), diagnostics);
client.publishDiagnostics(diagnosticsParams);
} catch (Exception e) {
log("[didSave] Error trying to get Inlay-Hints and Diagnostics for Client: " + e.getMessage(), MessageType.Error);
} finally {
currentlyCalculating = false;
sWatch.stop();
log("[didSave] Finished Calculating in [" + sWatch.elapsed().toSeconds() + "s]", MessageType.Info);
}
}
stopLoading("compile-task", "Types successfully inferred", client);
updateClient(client);
ArrayList<File> files = new ArrayList<>();
try (Stream<Path> stream = Files.walk(Paths.get(fileRoot.toUri()))) {
stream.filter(Files::isRegularFile)
.forEach(el -> files.add(el.toFile()));
} catch (IOException e) {
}
} }
@Override @Override
public CompletableFuture<List<InlayHint>> inlayHint(InlayHintParams params) { public CompletableFuture<List<InlayHint>> inlayHint(InlayHintParams params) {
log("[inlayHint] The Client requested Inlay-Hints.", MessageType.Info); String uri = params.getTextDocument().getUri();
return CompletableFuture.supplyAsync(() -> globalInlayHintMap.get(params.getTextDocument().getUri()) == null ? Collections.emptyList() : globalInlayHintMap.get(params.getTextDocument().getUri())); List<InlayHint> hints = cacheService.getInlayHintsByUri().get(uri);
} return CompletableFuture.completedFuture(hints != null ? hints : Collections.emptyList());
@Override
public void willSave(WillSaveTextDocumentParams params) {
TextDocumentService.super.willSave(params);
}
@Override
public CompletableFuture<List<TextEdit>> willSaveWaitUntil(WillSaveTextDocumentParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<DocumentLink>> documentLink(DocumentLinkParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<DocumentLink> documentLinkResolve(DocumentLink params) {
return CompletableFuture.completedFuture(new DocumentLink());
}
@Override
public CompletableFuture<List<ColorInformation>> documentColor(DocumentColorParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<ColorPresentation>> colorPresentation(ColorPresentationParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<CallHierarchyItem>> prepareCallHierarchy(CallHierarchyPrepareParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<CallHierarchyIncomingCall>> callHierarchyIncomingCalls(CallHierarchyIncomingCallsParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<CallHierarchyOutgoingCall>> callHierarchyOutgoingCalls(CallHierarchyOutgoingCallsParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<SelectionRange>> selectionRange(SelectionRangeParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
return TextDocumentService.super.semanticTokensFull(params);
}
@Override
public CompletableFuture<Either<SemanticTokens, SemanticTokensDelta>> semanticTokensFullDelta(SemanticTokensDeltaParams params) {
return CompletableFuture.completedFuture(Either.forLeft(new SemanticTokens()));
}
@Override
public CompletableFuture<SemanticTokens> semanticTokensRange(SemanticTokensRangeParams params) {
return CompletableFuture.completedFuture(new SemanticTokens());
}
@Override
public CompletableFuture<List<Moniker>> moniker(MonikerParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem unresolved) {
return CompletableFuture.completedFuture(new CompletionItem());
}
@Override
public CompletableFuture<Hover> hover(HoverParams params) {
return CompletableFuture.completedFuture(new Hover());
}
@Override
public CompletableFuture<SignatureHelp> signatureHelp(SignatureHelpParams params) {
return CompletableFuture.completedFuture(new SignatureHelp());
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> declaration(DeclarationParams params) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(DefinitionParams params) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> typeDefinition(TypeDefinitionParams params) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> implementation(ImplementationParams params) {
return CompletableFuture.completedFuture(Either.forLeft(Collections.emptyList()));
}
@Override
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(DocumentHighlightParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> documentSymbol(DocumentSymbolParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
} }
@Override @Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) { public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
log("[codeAction] Client requested Insert at Line [" + params.getRange().getStart().getLine() + "] and from Char [" + params.getRange().getStart().getCharacter() + "] to [" + params.getRange().getEnd().getCharacter() + "].", MessageType.Info); logService.log("[codeAction] Client requested code action.", MessageType.Info);
List<Either<Command, CodeAction>> actions = new ArrayList<>(); return CompletableFuture.supplyAsync(() -> codeActionHandler.handleCodeAction(params));
List<Diagnostic> diagnosticInCurrentDocument = params.getContext().getDiagnostics();
Range requestedRange = params.getRange();
//All Diagnostics that are in range of the hover -> All Diagnostics of the selected Variable and thus all Types of the Variable
List<Diagnostic> diagnosticsOverlappingHover = diagnosticInCurrentDocument.stream()
.filter(diagnostic -> rangesOverlap(diagnostic.getRange(), requestedRange)).toList();
Range rangeOfInsert = params.getRange();
String documentUri = params.getTextDocument().getUri();
for (Diagnostic typeDiagnostic : diagnosticsOverlappingHover) {
try {
String typeWithReplacedVariable = typeDiagnostic.getMessage() +
" " +
textHelper.getTextOfChars(
textDocuments.get(params.getTextDocument().getUri()),
rangeOfInsert.getStart().getLine(),
rangeOfInsert.getStart().getCharacter(),
rangeOfInsert.getEnd().getCharacter()
);
ArrayList<TextEdit> listOfChanges = new ArrayList<>();
if (typeDiagnostic.getCode().getLeft().equals("GENERIC")) {
for (var diagnostic : globalDiagnosticsMap.get(params.getTextDocument().getUri())) {
if (diagnostic.getMessage().contains(typeDiagnostic.getMessage()) || typeDiagnostic.getMessage().contains(diagnostic.getMessage())) {
String genericType = diagnostic.getMessage() +
" " +
textHelper.getTextOfChars(
textDocuments.get(params.getTextDocument().getUri()),
diagnostic.getRange().getStart().getLine(),
diagnostic.getRange().getStart().getCharacter(),
diagnostic.getRange().getEnd().getCharacter()
);
listOfChanges.add(new TextEdit(diagnostic.getRange(), genericType));
}
}
} else {
listOfChanges.add(new TextEdit(rangeOfInsert, typeWithReplacedVariable));
}
var isTypeImported = false;
if (!typeDiagnostic.getCode().getLeft().equalsIgnoreCase("generic")) {
isTypeImported = typeResolver.isTypeImported(textDocuments.get(params.getTextDocument().getUri()), typeDiagnostic.getMessage());
}
if (!isTypeImported && !typeDiagnostic.getMessage().equals("void") && !typeDiagnostic.getCode().getLeft().equals("GENERIC")) {
Range importRange = new Range(new Position(0, 0), new Position(0, 0));
var typeWithoutGenerics = typeDiagnostic.getMessage().contains("<") ? typeDiagnostic.getMessage().substring(0, typeDiagnostic.getMessage().indexOf('<')) : typeDiagnostic.getMessage();
listOfChanges.add(new TextEdit(importRange, "import " + typeWithoutGenerics + ";\n"));
}
Map<String, List<TextEdit>> changes = new HashMap<>();
changes.put(documentUri, listOfChanges);
WorkspaceEdit edit = new WorkspaceEdit();
edit.setChanges(changes);
CodeAction action = new CodeAction("Insert " + typeDiagnostic.getMessage());
action.setKind(CodeActionKind.QuickFix);
action.setEdit(edit);
actions.add(Either.forRight(action));
} catch (Exception e) {
log("Error creating Actions, returning empty List. The Error was: " + e.getMessage(), MessageType.Error);
}
}
return CompletableFuture.completedFuture(actions);
} }
}
private boolean rangesOverlap(Range range1, Range range2) {
int start1 = range1.getStart().getLine() * 1000 + range1.getStart().getCharacter();
int end1 = range1.getEnd().getLine() * 1000 + range1.getEnd().getCharacter();
int start2 = range2.getStart().getLine() * 1000 + range2.getStart().getCharacter();
int end2 = range2.getEnd().getLine() * 1000 + range2.getEnd().getCharacter();
return start1 <= end2 && start2 <= end1;
}
@Override
public CompletableFuture<CodeAction> resolveCodeAction(CodeAction unresolved) {
return CompletableFuture.completedFuture(new CodeAction());
}
@Override
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<CodeLens> resolveCodeLens(CodeLens unresolved) {
return CompletableFuture.completedFuture(new CodeLens());
}
@Override
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams params) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
return CompletableFuture.completedFuture(new WorkspaceEdit());
}
@Override
public CompletableFuture<LinkedEditingRanges> linkedEditingRange(LinkedEditingRangeParams params) {
return CompletableFuture.completedFuture(new LinkedEditingRanges());
}
}
@@ -1,30 +1,16 @@
package de.dhbw; package de.dhbw;
import org.apache.log4j.LogManager; import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.apache.log4j.Logger; import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.WorkspaceService; import org.eclipse.lsp4j.services.WorkspaceService;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
*
* Handles Actions in Workspace
*
* */
public class JavaTXWorkspaceService implements WorkspaceService { public class JavaTXWorkspaceService implements WorkspaceService {
private static final Logger logger = LogManager.getLogger(JavaTXWorkspaceService.class);
@Override @Override
public void didChangeConfiguration(DidChangeConfigurationParams didChangeConfigurationParams) { public void didChangeConfiguration(DidChangeConfigurationParams params) {
} }
@Override @Override
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
} }
} }
@@ -0,0 +1,95 @@
package de.dhbw.handler;
import com.google.common.base.Stopwatch;
import de.dhbw.helper.ConversionHelper;
import de.dhbw.helper.TypeResolver;
import de.dhbw.model.DiagnosticsAndTypehints;
import de.dhbw.model.LSPVariable;
import de.dhbw.service.CacheService;
import de.dhbw.service.ClientService;
import de.dhbw.service.DocumentStore;
import de.dhbw.service.LogService;
import de.dhbw.service.ParserService;
import org.eclipse.lsp4j.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.net.URI;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class ChangeHandler {
private final DocumentStore documentStore;
private final ParserService parserService;
private final ConversionHelper conversionHelper;
private final ClientService clientService;
private final TypeResolver typeResolver;
private final CacheService cacheService;
private final LogService logService;
public ChangeHandler(DocumentStore documentStore, ParserService parserService,
ConversionHelper conversionHelper, ClientService clientService,
TypeResolver typeResolver, CacheService cacheService, LogService logService) {
this.documentStore = documentStore;
this.parserService = parserService;
this.conversionHelper = conversionHelper;
this.clientService = clientService;
this.typeResolver = typeResolver;
this.cacheService = cacheService;
this.logService = logService;
}
public void didChange(DidChangeTextDocumentParams params) {
String uri = params.getTextDocument().getUri();
StringBuilder content = new StringBuilder();
params.getContentChanges().forEach(change -> content.append(change.getText()));
String input = content.toString();
documentStore.saveFile(uri, input);
List<Diagnostic> syntaxErrors = parserService.getDiagnosticsOfErrors(input, uri);
if (!syntaxErrors.isEmpty()) {
clientService.publishDiagnostics(uri, syntaxErrors);
return;
}
clientService.refreshClient();
inferAndPublish(uri, input);
}
private void inferAndPublish(String uri, String input) {
Stopwatch stopwatch = Stopwatch.createStarted();
File tempFile = null;
try {
File tempDir = Path.of(new URI(uri)).getParent().toFile();
tempFile = File.createTempFile("jtx_temp", ".tmp", tempDir);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
writer.write(input);
}
List<LSPVariable> variables = typeResolver.inferTypes(tempFile.toURI().toString(), input);
cacheService.setVariables(variables);
DiagnosticsAndTypehints result = conversionHelper.variablesToDiagnosticsAndTypehints(variables, input);
cacheService.updateGlobalMaps(result.diagnostics(), result.inlayHints(), uri);
clientService.publishDiagnostics(uri, new ArrayList<>(result.diagnostics()));
} catch (Exception | VerifyError e) {
logService.log("[didChange] Error: " + e.getMessage(), MessageType.Error);
clientService.showMessage(MessageType.Error, e.getMessage() != null ? e.getMessage() : "Unknown error");
cacheService.updateGlobalMaps(List.of(), List.of(), uri);
} finally {
if (tempFile != null) {
tempFile.delete();
}
stopwatch.stop();
logService.log("[didChange] Completed in " + stopwatch.elapsed().toSeconds() + "s", MessageType.Info);
}
}
}
@@ -0,0 +1,126 @@
package de.dhbw.handler;
import de.dhbw.helper.ConversionHelper;
import de.dhbw.helper.TypeResolver;
import de.dhbw.service.DocumentStore;
import de.dhbw.service.LogService;
import de.dhbw.model.PlaceholderVariable;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CodeActionHandler {
private final DocumentStore documentStore;
private final TypeResolver typeResolver;
private final LogService logService;
private final ConversionHelper conversionHelper;
public CodeActionHandler(DocumentStore documentStore, TypeResolver typeResolver,
LogService logService, ConversionHelper conversionHelper) {
this.documentStore = documentStore;
this.typeResolver = typeResolver;
this.logService = logService;
this.conversionHelper = conversionHelper;
}
public List<Either<Command, CodeAction>> handleCodeAction(CodeActionParams params) {
if (typeResolver.getInserts() == null) {
return List.of();
}
String uri = params.getTextDocument().getUri();
Range range = params.getRange();
Map<String, List<PlaceholderVariable>> overlapping = getOverlapping(
typeResolver.getInserts(),
range.getStart().getLine() + 1,
range.getStart().getCharacter(),
range.getEnd().getCharacter()
);
List<Either<Command, CodeAction>> actions = new ArrayList<>();
List<String> addedTitles = new ArrayList<>();
for (List<PlaceholderVariable> placeholders : overlapping.values()) {
for (PlaceholderVariable placeholder : placeholders) {
try {
String currentContent = documentStore.getFileContent(uri);
String inserted = placeholder.insert(currentContent);
String title = "Insert " + conversionHelper.cleanType(placeholder.getInsertString());
if (addedTitles.contains(title)) {
continue;
}
TextEdit edit = new TextEdit(wholeDocumentRange(currentContent), inserted);
WorkspaceEdit workspaceEdit = new WorkspaceEdit();
workspaceEdit.setChanges(Map.of(uri, List.of(edit)));
CodeAction action = new CodeAction(title);
action.setKind(CodeActionKind.QuickFix);
action.setEdit(workspaceEdit);
actions.add(Either.forRight(action));
addedTitles.add(title);
} catch (Exception e) {
logService.log("Error creating code action: " + e.getMessage(), MessageType.Error);
}
}
}
return actions;
}
static Range wholeDocumentRange(String text) {
if (text == null || text.isEmpty()) {
return new Range(new Position(0, 0), new Position(0, 0));
}
int lastLine = 0;
int lastBreak = -1;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '\n') {
lastLine++;
lastBreak = i;
} else if (c == '\r') {
lastLine++;
if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
lastBreak = i + 1;
i++;
} else {
lastBreak = i;
}
}
}
int endChar = text.length() - (lastBreak + 1);
return new Range(new Position(0, 0), new Position(lastLine, endChar));
}
private static <V> Map<String, V> getOverlapping(Map<String, V> map, int line,
int startChar, int endChar) {
int s = Math.min(startChar, endChar);
int e = Math.max(startChar, endChar);
return map.entrySet().stream()
.filter(entry -> {
String[] parts = entry.getKey().split("\\s+");
if (parts.length != 2) return false;
try {
int kLine = Integer.parseInt(parts[0]);
int kChar = Integer.parseInt(parts[1]);
return kLine == line && kChar >= s && kChar <= e;
} catch (NumberFormatException ex) {
return false;
}
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}
@@ -0,0 +1,34 @@
package de.dhbw.handler;
import de.dhbw.service.DocumentStore;
import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import java.util.List;
public class FormattingHandler {
private final DocumentStore documentStore;
public FormattingHandler(DocumentStore documentStore) {
this.documentStore = documentStore;
}
public List<TextEdit> handleFormat(DocumentFormattingParams params) {
String content = documentStore.getFileContent(params.getTextDocument().getUri());
String[] lines = content.split("\n");
StringBuilder formatted = new StringBuilder();
for (String line : lines) {
formatted.append(line.stripTrailing()).append("\n");
}
TextEdit edit = new TextEdit(
new Range(new Position(0, 0), new Position(lines.length, 0)),
formatted.toString().stripTrailing()
);
return List.of(edit);
}
}
@@ -0,0 +1,80 @@
package de.dhbw.handler;
import com.google.common.base.Stopwatch;
import de.dhbw.model.DiagnosticsAndTypehints;
import de.dhbw.model.LSPVariable;
import de.dhbw.service.CacheService;
import de.dhbw.service.ClientService;
import de.dhbw.service.DocumentStore;
import de.dhbw.service.LogService;
import de.dhbw.service.ParserService;
import de.dhbw.helper.ConversionHelper;
import de.dhbw.helper.TypeResolver;
import org.eclipse.lsp4j.*;
import java.util.ArrayList;
import java.util.List;
public class SaveHandler {
private final TypeResolver typeResolver;
private final DocumentStore documentStore;
private final LogService logService;
private final CacheService cacheService;
private final ConversionHelper conversionHelper;
private final ClientService clientService;
private final ParserService parserService;
public SaveHandler(TypeResolver typeResolver, DocumentStore documentStore, LogService logService,
CacheService cacheService, ConversionHelper conversionHelper,
ClientService clientService, ParserService parserService) {
this.typeResolver = typeResolver;
this.documentStore = documentStore;
this.logService = logService;
this.cacheService = cacheService;
this.conversionHelper = conversionHelper;
this.clientService = clientService;
this.parserService = parserService;
}
public void handleSave(DidSaveTextDocumentParams params) {
cacheService.reset();
typeResolver.reset();
Stopwatch stopwatch = Stopwatch.createStarted();
String uri = params.getTextDocument().getUri();
try {
String fileContent = documentStore.getFileContent(uri);
List<Diagnostic> syntaxErrors = parserService.getDiagnosticsOfErrors(fileContent, uri);
if (!syntaxErrors.isEmpty()) {
clientService.publishDiagnostics(uri, syntaxErrors);
return;
}
clientService.refreshClient();
cacheService.getLastSavedFiles().put(uri, fileContent);
if (fileContent == null) {
logService.log("[didSave] File content is null.", MessageType.Error);
return;
}
List<LSPVariable> variables = typeResolver.inferTypes(uri, fileContent);
cacheService.setVariables(variables);
DiagnosticsAndTypehints result = conversionHelper.variablesToDiagnosticsAndTypehints(variables, fileContent);
cacheService.updateGlobalMaps(result.diagnostics(), result.inlayHints(), uri);
clientService.publishDiagnostics(uri, new ArrayList<>(result.diagnostics()));
} catch (Exception | VerifyError e) {
logService.log("[didSave] Error: " + e.getMessage(), MessageType.Error);
clientService.showMessage(MessageType.Error, e.getMessage() != null ? e.getMessage() : "Unknown error");
cacheService.updateGlobalMaps(List.of(), List.of(), uri);
} finally {
stopwatch.stop();
logService.log("[didSave] Completed in " + stopwatch.elapsed().toSeconds() + "s", MessageType.Info);
}
}
}
@@ -0,0 +1,90 @@
package de.dhbw.helper;
import de.dhbw.model.PlaceholderPoint;
import de.dhbw.model.PlaceholderType;
import de.dhbw.model.PlaceholderVariable;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.target.generate.GenerateGenerics;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.target.generate.GenericsResultSet;
import de.dhbwstuttgart.typeinference.result.ResolvedType;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import org.antlr.v4.runtime.Token;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
public class ASTTransformationHelper {
private ASTTransformationHelper() {}
public static Set<PlaceholderVariable> createTypeInsertPoints(SourceFile source, ResultSet results,
GenericsResult generics) {
return new PlaceholderPlacer().getTypeInserts(source, results, generics);
}
public static PlaceholderVariable createInsertPoints(RefTypeOrTPHOrWildcardOrGeneric type, Token offset,
ClassOrInterface cl, Method m, ResultSet resultSet,
GenericsResultSet constraints,
GenericsResultSet classConstraints) {
ResolvedType resolvedType = resultSet.resolveType(type);
PlaceholderPoint insertPoint = new PlaceholderPoint(
offset,
new PlaceholderToInsertString(resolvedType.resolvedType, constraints, classConstraints).insert,
PlaceholderType.NORMAL_INSERT
);
Set<PlaceholderPoint> genericInserts = createGenericInserts(constraints, classConstraints, cl, m, resultSet, offset);
return new PlaceholderVariable(insertPoint, genericInserts, resolvedType.getResultPair());
}
private static synchronized Set<PlaceholderPoint> createGenericInserts(GenericsResultSet methodConstraints,
GenericsResultSet classConstraints,
ClassOrInterface cl, Method m,
ResultSet resultSet, Token mOffset) {
Set<PlaceholderPoint> result = createGenericClassInserts(classConstraints, cl);
if (m != null) {
Token offset = m.getOffset() != null ? m.getOffset() : mOffset;
result.addAll(createMethodConstraints(methodConstraints, offset));
}
return result;
}
private static Set<PlaceholderPoint> createMethodConstraints(GenericsResultSet constraints, Token offset) {
if (constraints == null || constraints.size() == 0) {
return new HashSet<>();
}
String insert = buildGenericString(constraints);
Set<PlaceholderPoint> result = new HashSet<>();
result.add(new PlaceholderPoint(offset, insert, PlaceholderType.GENERIC_METHOD_INSERT));
return result;
}
private static Set<PlaceholderPoint> createGenericClassInserts(GenericsResultSet classConstraints,
ClassOrInterface cl) {
if (classConstraints == null || classConstraints.size() == 0) {
return new HashSet<>();
}
String insert = buildGenericString(classConstraints);
Set<PlaceholderPoint> result = new HashSet<>();
result.add(new PlaceholderPoint(cl.getGenerics().getOffset(), insert, PlaceholderType.GENERIC_CLASS_INSERT));
return result;
}
private static String buildGenericString(GenericsResultSet constraints) {
StringJoiner joiner = new StringJoiner(", ", " <", ">");
for (var constraint : constraints) {
if (constraint instanceof GenerateGenerics.PairEQ peq) {
joiner.add(peq.left.resolve().getName());
} else if (constraint instanceof GenerateGenerics.PairLT psm) {
joiner.add(psm.left.resolve().getName() + " extends " + psm.right.resolve().getName());
}
}
return joiner.toString();
}
}
@@ -2,34 +2,17 @@ package de.dhbw.helper;
import de.dhbw.model.SnippetWithName; import de.dhbw.model.SnippetWithName;
import java.util.ArrayList; import java.util.List;
/**
* Helper-Class containing all Snippets
*
* */
public class CodeSnippetOptions { public class CodeSnippetOptions {
private ArrayList<SnippetWithName> snippets = new ArrayList<>();
public CodeSnippetOptions() { private static final List<SnippetWithName> SNIPPETS = List.of(
snippets.add(getMainSnippet()); new SnippetWithName("main", "public main(args){\n ${1:}\n}\n"),
snippets.add(getForLoopSnippet()); new SnippetWithName("forLoop", "for(i = 0; i < ${1:listSize}; i++){\n\n}"),
snippets.add(getForEachSnippet()); new SnippetWithName("forEachLoop", "for(el : ${1:listSize}){\n\n}")
} );
public SnippetWithName getMainSnippet(){ public List<SnippetWithName> getSnippets() {
return new SnippetWithName("main", "public main(args){\n ${1:}\n}\n"); return SNIPPETS;
}
public SnippetWithName getForLoopSnippet(){
return new SnippetWithName("forLoop", "for(i = 0; i < ${1:listSize}; i++){\n\n}");
}
public SnippetWithName getForEachSnippet(){
return new SnippetWithName("forEachLoop", "for(el : ${1:listSize}){\n\n}");
}
public ArrayList<SnippetWithName> getSnippets() {
return snippets;
} }
} }
@@ -0,0 +1,82 @@
package de.dhbw.helper;
import de.dhbw.model.DiagnosticsAndTypehints;
import de.dhbw.service.DocumentStore;
import de.dhbw.model.LSPVariable;
import de.dhbw.model.Type;
import de.dhbwstuttgart.languageServerInterface.model.ParserError;
import org.eclipse.lsp4j.*;
import java.util.ArrayList;
import java.util.List;
public class ConversionHelper {
private final TextHelper textHelper;
private final DocumentStore documentStore;
public ConversionHelper(TextHelper textHelper, DocumentStore documentStore) {
this.textHelper = textHelper;
this.documentStore = documentStore;
}
public String cleanType(String type) {
return type.replace("java.lang.", "").replace("java.util.", "");
}
public DiagnosticsAndTypehints variablesToDiagnosticsAndTypehints(List<LSPVariable> variables, String input) {
List<InlayHint> hints = new ArrayList<>();
List<Diagnostic> diagnostics = new ArrayList<>();
for (LSPVariable variable : variables) {
hints.add(createInlayHint(variable));
for (Type type : variable.getPossibleTypes()) {
diagnostics.add(createDiagnostic(variable, input, type));
}
}
return new DiagnosticsAndTypehints(diagnostics, hints);
}
public List<Diagnostic> parseErrorsToDiagnostics(List<ParserError> parserErrors) {
return parserErrors.stream().map(error -> new Diagnostic(
new Range(
new Position(error.getLine() - 1, error.getCharPositionInLine()),
new Position(error.getLine() - 1, error.getEndCharPosition())
),
error.getMsg(),
DiagnosticSeverity.Error,
"JavaTX Language Server"
)).toList();
}
private InlayHint createInlayHint(LSPVariable variable) {
String label = variable.getPossibleTypes().stream()
.map(type -> cleanType(type.type().replaceAll("GTV ", "")))
.reduce((a, b) -> a + " | " + b)
.orElse("");
InlayHint hint = new InlayHint();
hint.setLabel(label);
hint.setPosition(new Position(variable.getLine() - 1, variable.getCharPosition()));
hint.setKind(InlayHintKind.Parameter);
hint.setPaddingRight(true);
return hint;
}
private Diagnostic createDiagnostic(LSPVariable variable, String input, Type type) {
Range range = new Range(
new Position(variable.getLine() - 1, variable.getCharPosition()),
new Position(variable.getLine() - 1,
textHelper.getEndingCharOfStartingChar(variable.getLine() - 1, variable.getCharPosition(), input))
);
Diagnostic diagnostic = new Diagnostic(
range,
cleanType(type.type().replaceAll("GTV ", "")),
DiagnosticSeverity.Hint,
"JavaTX Language Server"
);
diagnostic.setCode(type.generic() ? "GENERIC" : "TYPE");
return diagnostic;
}
}
@@ -0,0 +1,31 @@
package de.dhbw.helper;
import de.dhbw.model.PlaceholderVariable;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import java.util.HashSet;
import java.util.Set;
public class PlaceholderPlacer extends AbstractASTWalker {
private final Set<PlaceholderVariable> inserts = new HashSet<>();
private ResultSet withResults;
private GenericsResult genericsResult;
public Set<PlaceholderVariable> getTypeInserts(SourceFile sourceFile, ResultSet withResults,
GenericsResult genericsResult) {
this.withResults = withResults;
this.genericsResult = genericsResult;
sourceFile.accept(this);
inserts.forEach(PlaceholderVariable::reducePackage);
return inserts;
}
@Override
public void visit(ClassOrInterface classOrInterface) {
PlaceholderPlacerClass cl = new PlaceholderPlacerClass(classOrInterface, withResults, genericsResult);
inserts.addAll(cl.inserts);
}
}
@@ -0,0 +1,65 @@
package de.dhbw.helper;
import de.dhbw.model.PlaceholderVariable;
import de.dhbwstuttgart.syntaxtree.*;
import de.dhbwstuttgart.syntaxtree.statement.LambdaExpression;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.target.generate.GenericsResultSet;
import de.dhbwstuttgart.typeinference.result.ResultSet;
import java.util.HashSet;
import java.util.Set;
class PlaceholderPlacerClass extends AbstractASTWalker{
protected final ResultSet results;
private GenericsResult generatedGenerics;
protected final ClassOrInterface cl;
public final Set<PlaceholderVariable> inserts = new HashSet<>();
private Method method;
GenericsResultSet constraints;
GenericsResultSet classConstraints;
PlaceholderPlacerClass(ClassOrInterface forClass, ResultSet withResults, GenericsResult generatedGenerics){
this.cl = forClass;
this.method = null;
this.results = withResults;
this.generatedGenerics = generatedGenerics;
forClass.accept(this);
}
@Override
public void visit(Method method) {
this.method = method;
constraints = generatedGenerics.get(method);
classConstraints = generatedGenerics.get(cl);
if(method.getReturnType() instanceof TypePlaceholder)
inserts.add(ASTTransformationHelper.createInsertPoints(
method.getReturnType(), method.getReturnType().getOffset(), cl, method, results, constraints, classConstraints));
super.visit(method);
}
@Override
public void visit(Field field) {
if(field.getType() instanceof TypePlaceholder){
classConstraints = generatedGenerics.get(cl);
inserts.add(ASTTransformationHelper.createInsertPoints(
field.getType(), field.getType().getOffset(), cl, method, results, null, classConstraints));
}
super.visit(field);
}
@Override
public void visit(FormalParameter param) {
if(param.getType() instanceof TypePlaceholder)
inserts.add(ASTTransformationHelper.createInsertPoints(
param.getType(), param.getType().getOffset(), cl, method, results, constraints, classConstraints));
super.visit(param);
}
@Override
public void visit(LambdaExpression lambdaExpression) {
// Lambda expressions do not need type inserts
}
}
@@ -0,0 +1,77 @@
package de.dhbw.helper;
import de.dhbwstuttgart.syntaxtree.type.*;
import de.dhbwstuttgart.target.generate.GenericsResultSet;
import de.dhbwstuttgart.typeinference.result.*;
import java.util.Iterator;
public class PlaceholderToInsertString implements ResultSetVisitor{
public String insert = "";
private GenericsResultSet constraints;
private GenericsResultSet classConstraints;
public PlaceholderToInsertString(RefTypeOrTPHOrWildcardOrGeneric type, GenericsResultSet constraints, GenericsResultSet classConstraints){
this.constraints = constraints;
this.classConstraints = classConstraints;
type.accept(this);
}
@Override
public void visit(PairTPHsmallerTPH p) {
}
@Override
public void visit(PairTPHequalRefTypeOrWildcardType p) {
}
@Override
public void visit(PairTPHEqualTPH p) {
}
@Override
public void visit(RefType resolved) {
insert = resolved.getName().toString();
if(resolved.getParaList().size() > 0){
insert += "<";
Iterator<RefTypeOrTPHOrWildcardOrGeneric> iterator = resolved.getParaList().iterator();
while(iterator.hasNext()){
RefTypeOrTPHOrWildcardOrGeneric typeParam = iterator.next();
insert += new PlaceholderToInsertString(typeParam, constraints, classConstraints).insert;
if(iterator.hasNext())insert += ", ";
}
insert += ">";
}
}
@Override
public void visit(GenericRefType genericRefType) {
insert += genericRefType.getParsedName();
}
@Override
public void visit(SuperWildcardType superWildcardType) {
insert += "? super " + new PlaceholderToInsertString(superWildcardType.getInnerType(), constraints, classConstraints).insert;
}
@Override
public void visit(TypePlaceholder typePlaceholder) {
ResultPair<?, ?> resultPair = null;
if (constraints != null)
resultPair = constraints.getResultPairFor(typePlaceholder).orElse(null);
if (resultPair == null)
resultPair = classConstraints.getResultPairFor(typePlaceholder).orElse(null);
if (resultPair != null)
insert += ((TypePlaceholder)resultPair.getLeft()).getName();
}
@Override
public void visit(ExtendsWildcardType extendsWildcardType) {
insert += "? extends " + new PlaceholderToInsertString(extendsWildcardType.getInnerType(), constraints, classConstraints).insert;
}
}
@@ -1,87 +1,27 @@
package de.dhbw.helper; package de.dhbw.helper;
import org.apache.log4j.Logger; import java.util.Set;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TextHelper { public class TextHelper {
private static final Logger log = Logger.getLogger(TextHelper.class); private static final Set<String> WORD_DELIMITERS = Set.of("(", ")", " ", "{", "}", ";", ",");
public Integer getClassPositionForGeneric(Integer line, String input, Integer startChar){
log.info("Calculating Position of Class-Generic Variable at Line [" + line + "] and Char [" + startChar + "].");
public int getEndingCharOfStartingChar(int line, int startChar, String input) {
String[] lines = input.split("\n"); String[] lines = input.split("\n");
if (lines.length <= line) {
if(lines.length < line){ return startChar;
log.warn("Returning hardcoded Value because the requested Line [" + line + "] does not exist in Text Document.");
return startChar+3;
} }
String[] linesInChar = lines[line].split(""); String lineText = lines[line];
for (int i = startChar; i < lineText.length(); i++) {
if (WORD_DELIMITERS.contains(String.valueOf(lineText.charAt(i)))) {
var index = startChar;
var found = false;
for (int i = startChar; i < linesInChar.length; i++) {
if(linesInChar[i].contains("{")){
index = i;
found = true;
break;
}
}
if(!found){
index = linesInChar.length-1;
}
for(int j = index; j <= linesInChar.length; j--){
if(!linesInChar[j].isEmpty() && !linesInChar[j].equals(" ")){
return j;
}
}
return index;
}
public Integer getEndingCharOfStartingChar(Integer line, Integer startChar, String input){
log.info("Calculating ending-Position for Variable at Line [" + line + "] and Char [" + startChar + "].");
List<String> endingChars = List.of("(", ")", " ", "{", "}", ";", ",");
String[] lines = input.split("\n");
if(lines.length < line){
log.warn("Returning hardcoded Value because the requested Line [" + line + "] does not exist in Text Document.");
return startChar+3;
}
String[] linesInChar = lines[line].split("");
var index = startChar;
for (int i = startChar; i < linesInChar.length; i++) {
index++;
if(endingChars.contains(linesInChar[i])){
return i; return i;
} }
} }
return lineText.length();
return index-1;
} }
public String getTextOfChars(String textDocument, Integer line, Integer charStart, Integer charEnd){ public String getTextOfChars(String textDocument, int line, int charStart, int charEnd) {
String[] splittedText = textDocument.split("\n"); return textDocument.split("\n")[line].substring(charStart, charEnd);
return splittedText[line].substring(charStart, charEnd);
} }
} }
@@ -1,13 +1,11 @@
package de.dhbw.helper; package de.dhbw.helper;
import de.dhbw.model.LSPVariable;
import de.dhbw.model.*; import de.dhbw.model.PlaceholderVariable;
import de.dhbw.model.Type;
import de.dhbwstuttgart.languageServerInterface.LanguageServerInterface; import de.dhbwstuttgart.languageServerInterface.LanguageServerInterface;
import de.dhbwstuttgart.languageServerInterface.model.LanguageServerTransferObject; import de.dhbwstuttgart.languageServerInterface.model.LanguageServerTransferObject;
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.SourceFile; import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
import de.dhbwstuttgart.target.generate.GenericsResult; import de.dhbwstuttgart.target.generate.GenericsResult;
import de.dhbwstuttgart.typeinference.result.ResultSet; import de.dhbwstuttgart.typeinference.result.ResultSet;
import org.apache.log4j.LogManager; import org.apache.log4j.LogManager;
@@ -16,240 +14,98 @@ import org.apache.log4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
* Helper-Class for finding the Type of a selected Word
*/
public class TypeResolver { public class TypeResolver {
HashMap<Integer, LanguageServerTransferObject> dataCache = new HashMap<>();
private static final Logger logger = LogManager.getLogger(TypeResolver.class); private static final Logger logger = LogManager.getLogger(TypeResolver.class);
private List<GenericsResult> generatedGenerics;
private HashMap<String, List<PlaceholderVariable>> inserts;
private LanguageServerTransferObject current;
private List<ResultSet> currentResults = new ArrayList<>(); public HashMap<String, List<PlaceholderVariable>> getInserts() {
return inserts;
public List<ResultSet> getCurrentResults() {
return currentResults;
} }
public void setCurrentResults(List<ResultSet> currentResults) { public void reset() {
this.currentResults = currentResults; inserts = null;
generatedGenerics = null;
current = null;
} }
public void addToCurrentResults(List<ResultSet> additionalResults) { public List<LSPVariable> inferTypes(String path, String input) throws IOException, ClassNotFoundException, URISyntaxException {
this.currentResults.addAll(additionalResults); LanguageServerInterface lsi = new LanguageServerInterface();
} LanguageServerTransferObject transfer = lsi.getResultSetAndAbstractSyntax(path);
public TypeResolver() { SourceFile ast = transfer.getAst();
} List<ResultSet> resultSets = transfer.getResultSets();
generatedGenerics = transfer.getGeneratedGenerics().get(ast);
current = transfer;
Set<PlaceholderVariable> insertPoints = new HashSet<>();
private LanguageServerTransferObject getCacheOrCalculate(String input, String path) throws IOException, ClassNotFoundException, URISyntaxException { for (int i = 0; i < resultSets.size(); i++) {
logger.info("Trying to find cached Compiler-Answer for input with key [" + input.hashCode() + "]."); insertPoints.addAll(ASTTransformationHelper.createTypeInsertPoints(ast, resultSets.get(i), generatedGenerics.get(i)));
LanguageServerInterface languageServer = new LanguageServerInterface();
if (dataCache.containsKey(input.hashCode())) {
logger.info("Found Cache for the Input with key [" + input.hashCode() + "].");
return dataCache.get(input.hashCode());
} else {
logger.info("Could not find Cache for Input with key [" + input.hashCode() + "]. Using Compiler Interface and Saving the Answer in Cache.");
var transferObject = languageServer.getResultSetAndAbstractSyntax(path, input);
dataCache.put(input.hashCode(), transferObject);
return transferObject;
} }
inserts = groupInsertsByPosition(insertPoints);
List<LSPVariable> variables = createVariablesFromInserts(inserts);
deduplicateTypes(variables);
return variables;
}
public List<LSPVariable> inferTypesFromCurrent() throws IOException, ClassNotFoundException, URISyntaxException {
Set<PlaceholderVariable> insertPoints = new HashSet<>();
for (int i = 0; i < current.getResultSets().size(); i++) {
insertPoints.addAll(ASTTransformationHelper.createTypeInsertPoints(
current.getAst(), current.getResultSets().get(i), generatedGenerics.get(i)));
}
inserts = groupInsertsByPosition(insertPoints);
List<LSPVariable> variables = createVariablesFromInserts(inserts);
deduplicateTypes(variables);
return variables;
} }
public boolean isTypeImported(String input, String type) { public boolean isTypeImported(String input, String type) {
// try {
// var transferObject = getCacheOrCalculate(input);
// var abstractSyntax = transferObject.getAst();
//
// var isAlreadyImported = false;
// for (var importStatement : abstractSyntax.getImports()) {
// isAlreadyImported = !isAlreadyImported && importStatement.toString().equals(type);
// }
//
// return isAlreadyImported;
//
// } catch (Exception e) {
// return true;
// }
return true; return true;
} }
public ArrayList<Type> getAvailableTypes(List<ResultSet> resultSets, Method method) { private HashMap<String, List<PlaceholderVariable>> groupInsertsByPosition(Set<PlaceholderVariable> insertPoints) {
logger.info("Searching for resulting Types of Placeholder [" + method.getReturnType().toString() + "]."); HashMap<String, List<PlaceholderVariable>> grouped = new HashMap<>();
ArrayList<String> normalType = new ArrayList<>(); for (PlaceholderVariable insert : insertPoints) {
String key = insert.point.point.getLine() + " " + insert.point.point.getCharPositionInLine();
resultSets.forEach(conSet -> { grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(insert);
if (method.getReturnType().toString().toLowerCase().contains("tph ")) {
normalType.add(conSet.resolveType(method.getReturnType()).resolvedType.toString());
}
});
return new ArrayList<>(normalType.stream().filter(el -> !el.contains("TPH ")).map(el -> new Type(el, false)).toList());
}
public ArrayList<Type> getAvailableGenericTypes(List<GenericsResult> genericsResult, Method method) {
logger.info("Searching for resulting Types of Placeholder [" + method.getReturnType().toString() + "].");
ArrayList<String> genericTypes = new ArrayList<>();
genericsResult.forEach(conSet -> {
if (method.getReturnType().toString().toLowerCase().contains("tph ")) {
genericTypes.add(conSet.resolveTarget(method.getReturnType()).name().replaceAll("GTV ", ""));
}
});
return new ArrayList<>(genericTypes.stream().filter(el -> !el.contains("TPH ")).map(el -> new Type(el, true)).toList());
}
public ArrayList<Type> getAvailableGenericTypes(List<GenericsResult> genericsResult, RefTypeOrTPHOrWildcardOrGeneric parameter) {
logger.info("Searching for resulting Types of Placeholder [" + parameter.toString() + "].");
ArrayList<String> paramTypes = new ArrayList<>();
genericsResult.forEach(conSet -> {
if (parameter.toString().toLowerCase().contains("tph ")) {
paramTypes.add(conSet.resolveTarget(parameter).name());
}
});
return new ArrayList<>(paramTypes.stream().filter(el -> !el.contains("TPH ")).map(el -> new Type(el, true)).toList());
}
public ArrayList<Type> getAvailableTypes(List<ResultSet> resultSets, RefTypeOrTPHOrWildcardOrGeneric parameter) {
logger.info("Searching for resulting Types of Placeholder [" + parameter.toString() + "].");
ArrayList<String> paramTypes = new ArrayList<>();
resultSets.forEach(conSet -> {
if (parameter.toString().toLowerCase().contains("tph ")) {
paramTypes.add(conSet.resolveType(parameter).resolvedType.toString());
}
});
return new ArrayList<>(paramTypes.stream().filter(el -> !el.contains("TPH ")).map(el -> new Type(el, false)).toList());
}
public ArrayList<Type> filterOutDuplicates(List<Type> typeListOne, List<Type> typeListTwo) {
ArrayList<Type> filteredArrayList = new ArrayList<>();
typeListOne.forEach(el -> {
boolean found = false;
for (Type typeInListTwo : typeListTwo) {
found = found || typeInListTwo.getType().equals(el.getType());
}
if (!found) {
filteredArrayList.add(el);
}
});
return filteredArrayList;
}
public ArrayList<Type> filterOutDuplicates(List<Type> typeListOne) {
HashMap<String, Type> hashMap = new HashMap<>();
typeListOne.forEach(el -> {
hashMap.put(el.getType(), el);
});
return new ArrayList<>(hashMap.values());
}
public ArrayList<Type> getClassGenerics(Map<SourceFile, List<GenericsResult>> genericsResult, Method method, ClassOrInterface clazz) {
ArrayList<Type> genericTypes = new ArrayList<>();
genericsResult.forEach(((key, value) -> value.forEach(genericResultSet -> {
var result = genericResultSet.resolveTarget(method.getReturnType());
var genericResult = genericResultSet.getBounds(method.getReturnType(), clazz, method);
if (result != null && genericResult != null) {
genericResult.forEach(res -> {
if (res != null && !res.isOnMethod()) {
genericTypes.add(new Type("<" + result.name() + (res.bound().toString().equals("java.lang.Object") ? ">" : " extends " + res.bound().toString() + ">"), true));
}
}
);
}
})));
return genericTypes;
}
/**
* Zum Erhalt für sowohl Parameter als auch Methoden.
*/
public ArrayList<LSPVariable> infereInput(String input, String path) throws IOException, ClassNotFoundException, URISyntaxException {
logger.info("Infering Types for Input.");
LanguageServerInterface languageServer = new LanguageServerInterface();
var transferObj = languageServer.getResultSetAndAbstractSyntax(path, input);
ArrayList<LSPVariable> methodsWithParametersLSPVariableList = new ArrayList<>();
//TODO: Hier noch irgendwie die Klasse rausfinden oder durchgehen.
//GENERICS OF CLASS
for (var method : transferObj.getAst().getAllMethods()) {
for (var clazz : transferObj.getAst().getClasses()) {
ArrayList<Type> genericTypes = getClassGenerics(transferObj.getGeneratedGenerics(), method, clazz);
TextHelper helper = new TextHelper();
if (!genericTypes.isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPClass("test", genericTypes, clazz.getOffset().getLine(), helper.getClassPositionForGeneric(clazz.getOffset().getLine() - 1, input, clazz.getOffset().getStopIndex()), clazz.getOffset().getStopIndex()));
}
}
} }
return grouped;
for (var method : transferObj.getAst().getAllMethods()) {
var types = getAvailableTypes(transferObj.getResultSets(), method);
var generics = getAvailableGenericTypes(transferObj.getGeneratedGenerics().values().stream().flatMap(List::stream).collect(Collectors.toList()), method);
if (!filterOutDuplicates(types).isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPMethod(method.name, filterOutDuplicates(types), method.getOffset().getLine(), method.getOffset().getCharPositionInLine(), method.getOffset().getStopIndex()));
}
if (!generics.isEmpty()) {
ArrayList<Type> typesThatAreGeneric = filterOutDuplicates(generics, types);
typesThatAreGeneric.forEach(el -> el.setType("<" + el.getType() + "> " + el.getType()));
//TODO: Muss Global und wird mehrmals so gemacht. Das if hier ist eigentlich falsch wegen unnötiger Berechnungszeit
if (!filterOutDuplicates(typesThatAreGeneric).isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPMethod(method.name, filterOutDuplicates(typesThatAreGeneric), method.getOffset().getLine(), method.getOffset().getCharPositionInLine(), method.getOffset().getStopIndex()));
}
}
for (var param : method.getParameterList()) {
ArrayList<Type> typeParam = getAvailableTypes(transferObj.getResultSets(), param.getType());
ArrayList<Type> genericParam = getAvailableGenericTypes(transferObj.getGeneratedGenerics().values().stream().flatMap(List::stream).collect(Collectors.toList()), param.getType());
if (!typeParam.isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPParameter(method.name, filterOutDuplicates(typeParam), param.getOffset().getLine(), param.getOffset().getCharPositionInLine(), param.getOffset().getStopIndex()));
}
if (!genericParam.isEmpty()) {
ArrayList<Type> typesThatAreGeneric = filterOutDuplicates(genericParam, typeParam);
if (!filterOutDuplicates(typesThatAreGeneric).isEmpty()) {
methodsWithParametersLSPVariableList.add(new LSPParameter(method.name, filterOutDuplicates(typesThatAreGeneric), method.getOffset().getLine(), param.getOffset().getCharPositionInLine(), param.getOffset().getStopIndex()));
methodsWithParametersLSPVariableList.add(new LSPParameter(method.name, new ArrayList<>(filterOutDuplicates(typesThatAreGeneric).stream().map(el -> new Type("<" + el.getType() + ">", true)).toList()), method.getOffset().getLine(), method.getOffset().getCharPositionInLine(), method.getOffset().getStopIndex()));
}
}
}
}
return methodsWithParametersLSPVariableList;
} }
private List<LSPVariable> createVariablesFromInserts(HashMap<String, List<PlaceholderVariable>> insertsMap) {
List<LSPVariable> variables = new ArrayList<>();
for (var entry : insertsMap.entrySet()) {
List<PlaceholderVariable> placeholders = entry.getValue();
List<Type> types = placeholders.stream()
.map(p -> new Type(p.getInsertString(), p.point.isGenericClassInsertPoint()))
.toList();
PlaceholderVariable first = placeholders.getFirst();
variables.add(new LSPVariable(
"test",
new ArrayList<>(types),
first.point.point.getLine(),
first.point.point.getCharPositionInLine(),
first.point.point.getStopIndex(),
first.getResultPair().getLeft()
));
}
return variables;
}
} private void deduplicateTypes(List<LSPVariable> variables) {
for (LSPVariable variable : variables) {
LinkedHashMap<String, Type> unique = new LinkedHashMap<>();
for (Type type : variable.getPossibleTypes()) {
unique.putIfAbsent(type.type().replaceAll(" ", ""), type);
}
variable.setPossibleTypes(new ArrayList<>(unique.values()));
}
}
}
@@ -0,0 +1,9 @@
package de.dhbw.model;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.InlayHint;
import java.util.List;
public record DiagnosticsAndTypehints(List<Diagnostic> diagnostics, List<InlayHint> inlayHints) {
}
@@ -1,9 +0,0 @@
package de.dhbw.model;
import java.util.ArrayList;
public class LSPClass extends LSPVariable{
public LSPClass(String name, ArrayList<Type> possibleTypes, int line, int charPosition, int endPosition) {
super(name, possibleTypes, line, charPosition, endPosition);
}
}
@@ -1,9 +0,0 @@
package de.dhbw.model;
import java.util.ArrayList;
public class LSPMethod extends LSPVariable {
public LSPMethod(String name, ArrayList<Type> type, int line, int charPosition, int endPosition) {
super(name, type, line, charPosition, endPosition);
}
}
@@ -1,10 +0,0 @@
package de.dhbw.model;
import java.util.ArrayList;
public class LSPParameter extends LSPMethod {
public LSPParameter(String name, ArrayList<Type> type, int line, int charPosition, int endPosition) {
super(name, type, line, charPosition, endPosition);
}
}
@@ -1,59 +1,33 @@
package de.dhbw.model; package de.dhbw.model;
import java.util.ArrayList; import de.dhbwstuttgart.syntaxtree.type.RefTypeOrTPHOrWildcardOrGeneric;
public class LSPVariable { String name; import java.util.List;
ArrayList<Type> possibleTypes;
int line;
int charPosition;
int endPosition;
boolean needsInference;
public LSPVariable(String name, ArrayList<Type> possibleTypes, int line, int charPosition, int endPosition) { public class LSPVariable {
private final String name;
private final int line;
private final int charPosition;
private final int endPosition;
private final RefTypeOrTPHOrWildcardOrGeneric originalTphName;
private List<Type> possibleTypes;
public LSPVariable(String name, List<Type> possibleTypes, int line, int charPosition,
int endPosition, RefTypeOrTPHOrWildcardOrGeneric originalTphName) {
this.name = name; this.name = name;
this.possibleTypes = possibleTypes; this.possibleTypes = possibleTypes;
this.line = line; this.line = line;
this.charPosition = charPosition; this.charPosition = charPosition;
this.endPosition = endPosition; this.endPosition = endPosition;
this.originalTphName = originalTphName;
} }
public int getEndPosition() { public String getName() { return name; }
return endPosition; public int getLine() { return line; }
} public int getCharPosition() { return charPosition; }
public int getEndPosition() { return endPosition; }
public void setEndPosition(int endPosition) { public RefTypeOrTPHOrWildcardOrGeneric getOriginalTphName() { return originalTphName; }
this.endPosition = endPosition; public List<Type> getPossibleTypes() { return possibleTypes; }
} public void setPossibleTypes(List<Type> possibleTypes) { this.possibleTypes = possibleTypes; }
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ArrayList<Type> getPossibleTypes() {
return possibleTypes;
}
public void setPossibleTypes(ArrayList<Type> possibleTypes) {
this.possibleTypes = possibleTypes;
}
public int getLine() {
return line;
}
public void setLine(int line) {
this.line = line;
}
public int getCharPosition() {
return charPosition;
}
public void setCharPosition(int charPosition) {
this.charPosition = charPosition;
}
} }
@@ -1,55 +0,0 @@
package de.dhbw.model.ParseError;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.atn.ATNConfigSet;
import org.antlr.v4.runtime.dfa.DFA;
import org.eclipse.lsp4j.*;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
public class DiagnoseErrorListener implements ANTLRErrorListener {
private final List<Diagnostic> errorMessages = new ArrayList<>();
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
int endCharPosition = charPositionInLine;
if (offendingSymbol instanceof Token) {
Token offendingToken = (Token) offendingSymbol;
// Berechne die Endposition anhand der Token-Länge
endCharPosition = charPositionInLine + offendingToken.getText().length();
}
// Erstelle den Range mit Start- und Endposition
Range errorRange = new Range(
new Position(line - 1, charPositionInLine), // Startposition
new Position(line - 1, endCharPosition) // Endposition
);
Diagnostic diagnostic = new Diagnostic(
errorRange,
msg,
DiagnosticSeverity.Error,
"JavaTX Language Server"
);
errorMessages.add(diagnostic);
}
@Override
public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) {
}
@Override
public void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) {
}
@Override
public void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) {
}
public List<Diagnostic> getErrorMessages() {
return errorMessages;
}
}
@@ -0,0 +1,61 @@
package de.dhbw.model;
import org.antlr.v4.runtime.Token;
import java.util.Comparator;
import java.util.List;
public class PlaceholderPoint {
public final Token point;
private String insertString;
private int extraOffset = 0;
private final PlaceholderType kind;
public PlaceholderPoint(Token point, String toInsert, PlaceholderType kind) {
this.point = point;
this.kind = kind;
this.insertString = toInsert.endsWith(" ") ? toInsert : toInsert + " ";
}
public boolean isGenericClassInsertPoint() {
return kind == PlaceholderType.GENERIC_CLASS_INSERT;
}
public String insert(String intoSource, List<PlaceholderPoint> additionalOffset) {
return new StringBuilder(intoSource)
.insert(point.getStartIndex() + extraOffset, insertString)
.toString();
}
public String getInsertString() { return insertString; }
public void setInsertString(String insertString) { this.insertString = insertString; }
public PlaceholderType getKind() { return kind; }
public void addExtraOffset(int toAdd) { this.extraOffset += toAdd; }
public int getPositionInCode() { return point.getStartIndex() + extraOffset; }
@Override
public boolean equals(Object obj) {
return this == obj;
}
@Override
public int hashCode() {
return getPositionInCode() * 11 * insertString.hashCode();
}
@Override
public String toString() {
return point.getLine() + ":" + point.getCharPositionInLine() + ":" + insertString;
}
public static final class TypeInsertPointPositionComparator implements Comparator<PlaceholderPoint> {
@Override
public int compare(PlaceholderPoint o1, PlaceholderPoint o2) {
if (o1.point == null && o2.point == null) return 0;
if (o2.point == null) return 1;
if (o1.point == null) return -1;
return Integer.compare(o1.getPositionInCode(), o2.getPositionInCode());
}
}
}
@@ -0,0 +1,7 @@
package de.dhbw.model;
public enum PlaceholderType {
NORMAL_INSERT,
GENERIC_CLASS_INSERT,
GENERIC_METHOD_INSERT
}
@@ -0,0 +1,66 @@
package de.dhbw.model;
import de.dhbwstuttgart.typeinference.result.ResultPair;
import java.util.*;
public class PlaceholderVariable {
public final PlaceholderPoint point;
private final Set<PlaceholderPoint> inserts;
private final ResultPair<?, ?> resultPair;
public PlaceholderVariable(PlaceholderPoint point, Set<PlaceholderPoint> additionalPoints,
ResultPair<?, ?> resultPair) {
this.point = point;
this.inserts = additionalPoints;
this.resultPair = resultPair;
}
public String insert(String intoSource) {
List<PlaceholderPoint> insertsSorted = new ArrayList<>();
insertsSorted.add(point);
if (!point.getInsertString().contains("void")) {
insertsSorted.addAll(inserts);
}
insertsSorted.sort(new PlaceholderPoint.TypeInsertPointPositionComparator().reversed());
String result = intoSource;
for (PlaceholderPoint insertPoint : insertsSorted) {
result = insertPoint.insert(result, new ArrayList<>());
}
return result;
}
public String getInsertString() {
return point.getInsertString();
}
public void reducePackage() {
point.setInsertString(point.getInsertString()
.replaceAll("java\\.lang\\.", "")
.replaceAll("java\\.util\\.", ""));
}
public ResultPair<?, ?> getResultPair() {
return resultPair;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof PlaceholderVariable other)) return false;
return other.point.equals(this.point);
}
@Override
public int hashCode() {
return point.hashCode();
}
@Override
public String toString() {
return point.toString();
}
}
@@ -1,27 +1,4 @@
package de.dhbw.model; package de.dhbw.model;
public class SnippetWithName { public record SnippetWithName(String name, String snippet) {
private String name;
private String snippet;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSnippet() {
return snippet;
}
public void setSnippet(String snippet) {
this.snippet = snippet;
}
public SnippetWithName(String name, String snippet) {
this.name = name;
this.snippet = snippet;
}
} }
@@ -1,27 +1,23 @@
package de.dhbw.model; package de.dhbw.model;
public class Type { import java.util.Objects;
String type;
boolean generic;
public Type(String type, boolean generic){ public record Type(String type, boolean generic) {
this.type = type;
this.generic = generic; @Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Type other)) return false;
return Objects.equals(type, other.type);
} }
public void setGeneric(boolean generic) { @Override
this.generic = generic; public int hashCode() {
return Objects.hashCode(type);
} }
public void setType(String type) { @Override
this.type = type; public String toString() {
}
public String getType() {
return type; return type;
} }
public boolean isGeneric() {
return generic;
}
} }
@@ -0,0 +1,35 @@
package de.dhbw.service;
import de.dhbw.model.LSPVariable;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.InlayHint;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class CacheService {
private HashMap<String, List<InlayHint>> inlayHintsByUri = new HashMap<>();
private HashMap<String, List<Diagnostic>> diagnosticsByUri = new HashMap<>();
private HashMap<String, String> lastSavedFiles = new HashMap<>();
private List<LSPVariable> variables = new ArrayList<>();
public void reset() {
inlayHintsByUri.clear();
diagnosticsByUri.clear();
lastSavedFiles.clear();
variables.clear();
}
public void updateGlobalMaps(List<Diagnostic> diagnostics, List<InlayHint> hints, String uri) {
diagnosticsByUri.put(uri, diagnostics);
inlayHintsByUri.put(uri, hints);
}
public HashMap<String, List<InlayHint>> getInlayHintsByUri() { return inlayHintsByUri; }
public HashMap<String, List<Diagnostic>> getDiagnosticsByUri() { return diagnosticsByUri; }
public HashMap<String, String> getLastSavedFiles() { return lastSavedFiles; }
public List<LSPVariable> getVariables() { return variables; }
public void setVariables(List<LSPVariable> variables) { this.variables = variables; }
}
@@ -0,0 +1,59 @@
package de.dhbw.service;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import java.util.List;
public class ClientService {
private LanguageClient client;
public ClientService(LanguageClient client) {
this.client = client;
}
public void setClient(LanguageClient client) {
this.client = client;
}
public LanguageClient getClient() {
return client;
}
public void publishDiagnostics(String uri, List<Diagnostic> diagnostics) {
client.publishDiagnostics(new PublishDiagnosticsParams(uri, diagnostics));
}
public void sendClientLog(MessageType type, String message) {
client.logMessage(new MessageParams(type, message));
}
public void showMessage(MessageType type, String message) {
client.showMessage(new MessageParams(type, message));
}
public void refreshClient() {
client.refreshInlayHints();
client.refreshDiagnostics();
}
public void startProgress(String taskName, String title) {
Either<String, Integer> token = Either.forLeft(taskName);
client.createProgress(new WorkDoneProgressCreateParams(token));
WorkDoneProgressBegin begin = new WorkDoneProgressBegin();
begin.setTitle(title);
begin.setCancellable(false);
begin.setPercentage(0);
client.notifyProgress(new ProgressParams(token, Either.forLeft(begin)));
}
public void stopProgress(String taskName, String message) {
Either<String, Integer> token = Either.forLeft(taskName);
WorkDoneProgressEnd end = new WorkDoneProgressEnd();
end.setMessage(message);
client.notifyProgress(new ProgressParams(token, Either.forLeft(end)));
}
}
@@ -0,0 +1,30 @@
package de.dhbw.service;
import org.eclipse.lsp4j.Range;
import java.util.HashMap;
public class DocumentStore {
private final HashMap<String, String> files = new HashMap<>();
public String getFileContent(String uri) {
return files.get(uri);
}
public void saveFile(String uri, String content) {
files.put(uri, content);
}
public void clear() {
files.clear();
}
public boolean rangesOverlap(Range range1, Range range2) {
int start1 = range1.getStart().getLine() * 10000 + range1.getStart().getCharacter();
int end1 = range1.getEnd().getLine() * 10000 + range1.getEnd().getCharacter();
int start2 = range2.getStart().getLine() * 10000 + range2.getStart().getCharacter();
int end2 = range2.getEnd().getLine() * 10000 + range2.getEnd().getCharacter();
return start1 <= end2 && start2 <= end1;
}
}
@@ -0,0 +1,31 @@
package de.dhbw.service;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.lsp4j.MessageType;
public class LogService {
private final Logger logger = LogManager.getLogger(LogService.class);
private final ClientService clientService;
public LogService(ClientService clientService) {
this.clientService = clientService;
}
public void log(String message, MessageType type) {
clientService.sendClientLog(type, message);
switch (type) {
case Error -> logger.error(message);
case Warning -> logger.warn(message);
case Info -> logger.info(message);
default -> logger.debug(message);
}
}
public void log(String message) {
clientService.sendClientLog(MessageType.Info, message);
logger.info(message);
}
}
@@ -0,0 +1,21 @@
package de.dhbw.service;
import de.dhbw.helper.ConversionHelper;
import de.dhbwstuttgart.languageServerInterface.ParserInterface;
import org.eclipse.lsp4j.Diagnostic;
import java.util.List;
public class ParserService {
private final ConversionHelper conversionHelper;
public ParserService(ConversionHelper conversionHelper) {
this.conversionHelper = conversionHelper;
}
public List<Diagnostic> getDiagnosticsOfErrors(String input, String uri) {
ParserInterface parserInterface = new ParserInterface();
return conversionHelper.parseErrorsToDiagnostics(parserInterface.getParseErrors(input));
}
}
@@ -1,15 +1,23 @@
import de.dhbw.helper.CodeSnippetOptions; import de.dhbw.helper.CodeSnippetOptions;
import de.dhbw.model.SnippetWithName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
public class CodeSnippetOptionsTest { public class CodeSnippetOptionsTest {
@Test
public void testForLoop() {
CodeSnippetOptions options = new CodeSnippetOptions();
assertEquals(options.getForLoopSnippet().getSnippet(),"for(i = 0; i < ${1:listSize}; i++){\n\n}"); @Test
public void testSnippetsAreProvided() {
CodeSnippetOptions options = new CodeSnippetOptions();
assertEquals(3, options.getSnippets().size());
}
@Test
public void testForLoopSnippet() {
CodeSnippetOptions options = new CodeSnippetOptions();
var forLoop = options.getSnippets().stream()
.filter(s -> s.name().equals("forLoop"))
.findFirst()
.orElseThrow();
assertEquals("for(i = 0; i < ${1:listSize}; i++){\n\n}", forLoop.snippet());
} }
} }
@@ -1,135 +0,0 @@
import de.dhbw.helper.TextHelper;
import de.dhbw.helper.TypeResolver;
import de.dhbwstuttgart.languageServerInterface.LanguageServerInterface;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
public class CompilerInterfaceTest {
@Test
public void testAbstractSyntaxAsString() throws IOException, ClassNotFoundException {
// LanguageServerInterface languageServer = new LanguageServerInterface();
// var res = languageServer.getResultSetAndAbstractSyntax("import java.lang.Integer;\n import java.lang.String;\n" +
// "public class test{\n" +
// "public main(test){" +
// "if(0>1){" +
// "return \"w\";" +
// "}" +
// "Integer i = 0;" +
// "return i;" +
// "}" +
// "}", "TEST");
//
// System.out.println("TEST OUTPUT:");
//
// ArrayList<String> allTypes = new ArrayList<>();
//
// res.getResultSets().forEach(el -> allTypes.add(el.resolveType(res.getAst().getAllMethods().get(0).getReturnType()).resolvedType.toString()));
// var results = res.getGeneratedGenerics().entrySet().iterator().next();
//
//
// System.out.println(res.getResultSets().toString());
//
//
// System.out.println("\n\n\nPRINTED AST:");
// System.out.println(res.getPrintedAst());
}
@Test
public void testConstraintTypes() throws IOException, ClassNotFoundException {
// LanguageServerInterface languageServer = new LanguageServerInterface();
// TypeResolver typeResolver = new TypeResolver();
//
//
// var res = typeResolver.infereInput("import java.lang.Integer; public class test{\n" +
// " \n" +
// " public main(testa){\n" +
// " return testa;\n" +
// " }\n" +
// "}");
// res.forEach(el -> el.getPossibleTypes().forEach(el2 -> System.out.println(el2.getType() + " " + (el2.isGeneric() ? "GENERIC" : "NO GENERIC"))));
}
@Test
public void testTypeFinder() throws IOException, ClassNotFoundException {
// TypeResolver typeResolver = new TypeResolver();
// var inferedMethods = typeResolver.infereInput("import java.lang.Integer;\n import java.lang.String;\n" +
// "public class test{\n" +
// "public main(test){" +
// "if(0>1){" +
// "return \"w\";" +
// "}" +
// "Integer i = 0;" +
// "return i;" +
// "}" +
// "}"
// );
//
// inferedMethods.forEach(el -> System.out.println(el.getName() + ": " + el.getPossibleTypes() + " | " + el.getLine() + " " + el.getCharPosition()));
}
@Test
public void testGenericTypes() throws IOException, ClassNotFoundException {
// TypeResolver typeResolver = new TypeResolver();
// var inferedMethods = typeResolver.infereInput("import java.lang.Integer; public class test{\n" +
// " \n" +
// " public main(testa){\n" +
// " return testa;\n" +
// " }\n" +
// "}"
// );
//
// inferedMethods.forEach(el -> System.out.println(el.getName() + ": " + el.getPossibleTypes() + " | " + el.getLine() + " " + el.getCharPosition()));
}
@Test
public void testTypeFinderParameter() throws IOException, ClassNotFoundException {
// TypeResolver typeResolver = new TypeResolver();
// var inferedMethods = typeResolver.infereInput("import java.lang.Integer;\n" +
// "import java.lang.String; \n" +
// "public class test{\n" +
// " public main(test, test2){\n" +
// " if(1>0){\n" +
// " return test;\n" +
// " }\n" +
// " String i = test;\n" +
// " return 1;\n" +
// "\n" +
// "\n" +
// " }\n" +
// "}"
// );
//
// inferedMethods.forEach(el -> System.out.println(el.getName() + ": " + el.getPossibleTypes()));
}
@Test
public void testCharEnding() throws IOException, ClassNotFoundException {
TextHelper textHelper = new TextHelper();
var endingChar = textHelper.getEndingCharOfStartingChar(3,13,"import java.lang.Integer;\n" +
"import java.lang.String; \n" +
"public class test{\n" +
" public main(test, test2){\n" +
" if(1>0){\n" +
" return test;\n" +
" }\n" +
" String i = test;\n" +
" return 1;\n" +
"\n" +
"\n" +
" }\n" +
"}"
);
Assert.assertEquals(15, (int) endingChar);
}
}
@@ -1,16 +0,0 @@
import de.dhbw.JavaTXTextDocumentService;
import de.dhbw.helper.CodeSnippetOptions;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.Ignore;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JavaTXLanguageDocumentServiceTest {
@Test
@Ignore
public void testWordExtraction() {
JavaTXTextDocumentService service = new JavaTXTextDocumentService();
}
}
@@ -0,0 +1,28 @@
import de.dhbw.helper.TextHelper;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TextHelperTest {
@Test
public void testEndingCharOfStartingChar() {
TextHelper textHelper = new TextHelper();
String input = "import java.lang.Integer;\n" +
"import java.lang.String;\n" +
"public class test{\n" +
" public main(test, test2){\n" +
" }\n" +
"}";
int endingChar = textHelper.getEndingCharOfStartingChar(3, 13, input);
assertEquals(17, endingChar);
}
@Test
public void testGetTextOfChars() {
TextHelper textHelper = new TextHelper();
String input = "line zero\nline one\nline two";
assertEquals("line", textHelper.getTextOfChars(input, 1, 0, 4));
}
}
+6 -1
View File
@@ -55,11 +55,16 @@ The Language Server in itself can be used for any Client. The Clients task is to
![Diagram](docs/diagram.png) ![Diagram](docs/diagram.png)
## Additional Information
- Uses the [LSP-Interface](https://gitea.hb.dhbw-stuttgart.de/JavaTX/JavaCompilerCore/src/branch/LSP-Interface) Branch of the Java-TX Compiler Repository
## Update JavaTX Compiler Dependency as Maven Package ## Update JavaTX Compiler Dependency as Maven Package
If you make changes in the Compiler Interface, you have to change the jar and therefore the Dependency in the Java TX Language Server If you make changes in the Compiler Interface, you have to change the jar and therefore the Dependency in the Java TX Language Server
You can follow this steps: You can follow this steps:
1. package the JavaTX Compiler 1. package the JavaTX Compiler
2. take the Jar-File and copy it into the /lib Folder 2. create a lib Folder at ./LangaugeServer -> ./LanguageServer/lib
2. take the Jar-File and copy it into the /lib Folder at
3. execute this Maven command to add the Jar in your local Repository: ```mvn install:install-file -Dfile=lib/JavaTXcompiler-0.1-jar-with-dependencies.jar -DgroupId=de.dhbwstuttgart -DartifactId=JavaTXcompiler -Dversion=0.1 -Dpackaging=jar``` 3. execute this Maven command to add the Jar in your local Repository: ```mvn install:install-file -Dfile=lib/JavaTXcompiler-0.1-jar-with-dependencies.jar -DgroupId=de.dhbwstuttgart -DartifactId=JavaTXcompiler -Dversion=0.1 -Dpackaging=jar```
4. run ```maven clean```, ```validate``` and ```install``` to load the new Dependency 4. run ```maven clean```, ```validate``` and ```install``` to load the new Dependency
5. you can now package the Language Server or change the code accordingly. 5. you can now package the Language Server or change the code accordingly.
+5
View File
@@ -0,0 +1,5 @@
public class t{
public test(){
return 1;
}
}