diff --git a/.azure-pipelines/publish-to-maven.yml b/.azure-pipelines/publish-to-maven.yml
new file mode 100644
index 000000000..a1ef0a204
--- /dev/null
+++ b/.azure-pipelines/publish-to-maven.yml
@@ -0,0 +1,104 @@
+name: $(Date:yyyyMMdd).$(Rev:r)
+resources:
+ repositories:
+ - repository: MicroBuildTemplate
+ type: git
+ name: 1ESPipelineTemplates/MicroBuildTemplate
+ ref: refs/tags/release
+trigger: none
+extends:
+ template: azure-pipelines/1ES.Official.Publish.yml@MicroBuildTemplate
+ parameters:
+ pool:
+ os: linux
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Ubuntu-2004
+ sdl:
+ sourceAnalysisPool:
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Windows_2022
+ os: windows
+ stages:
+ - stage: PublishToMaven
+ jobs:
+ - job: PublishToMaven
+ displayName: Maven Release job
+ templateContext:
+ type: releaseJob
+ isProduction: true
+ steps:
+ - task: DownloadBuildArtifacts@1
+ displayName: 'Download Jar Artifacts'
+ inputs:
+ buildType: specific
+ project: 'a4d27ce2-a42d-4b71-8eef-78cee9a9728e'
+ pipeline: 16486
+ downloadType: specific
+ extractTars: false
+ itemPattern: 'm2/**'
+ - script: |
+ echo "import public key"
+ echo $GPG_PUBLIC_B64 | base64 -d | gpg --import
+
+ echo "import secret key"
+ echo $GPG_SECRET_B64 | base64 -d | gpg --batch --passphrase $GPGPASS --import
+ displayName: 'import GPG keys'
+ env:
+ GPG_PUBLIC_B64: $(GPG_PUBLIC_B64)
+ GPG_SECRET_B64: $(GPG_SECRET_B64)
+ GPGPASS: $(GPGPASS)
+ - task: NodeTool@0
+ displayName: 'Use Node 20.x'
+ inputs:
+ versionSpec: 20.x
+ - script: |
+ cd $(System.ArtifactsDirectory)/m2
+ pluginJarFile=$(basename -- java-debug-parent/*.pom)
+
+ # remove .* from end
+ noExt=${pluginJarFile%.*}
+
+ # remove *- from start
+ export releaseVersion=${noExt##*-}
+ echo $releaseVersion
+
+ export artifactFolder=$(pwd .)
+ wget https://raw.githubusercontent.com/microsoft/java-debug/master/scripts/publishMaven.js
+
+ export GPG_TTY=$(tty)
+ node publishMaven.js -task gpg
+ displayName: 'sign artifacts'
+ env:
+ GPG_PUBLIC_B64: $(GPG_PUBLIC_B64)
+ GPG_SECRET_B64: $(GPG_SECRET_B64)
+ GPGPASS: $(GPGPASS)
+ NEXUS_OSSRHPASS: $(NEXUS_OSSRHPASS)
+ NEXUS_OSSRHUSER: $(NEXUS_OSSRHUSER)
+ NEXUS_STAGINGPROFILEID: $(NEXUS_STAGINGPROFILEID)
+ - template: MicroBuild.Publish.yml@MicroBuildTemplate
+ parameters:
+ intent: 'PackageDistribution'
+ contentType: 'Maven'
+ contentSource: 'Folder'
+ folderLocation: '$(System.ArtifactsDirectory)/m2/java-debug-parent'
+ waitForReleaseCompletion: true
+ owners: 'jinbwan@microsoft.com'
+ approvers: 'roml@microsoft.com'
+ - template: MicroBuild.Publish.yml@MicroBuildTemplate
+ parameters:
+ intent: 'PackageDistribution'
+ contentType: 'Maven'
+ contentSource: 'Folder'
+ folderLocation: '$(System.ArtifactsDirectory)/m2/com.microsoft.java.debug.core'
+ waitForReleaseCompletion: true
+ owners: 'jinbwan@microsoft.com'
+ approvers: 'roml@microsoft.com'
+ - template: MicroBuild.Publish.yml@MicroBuildTemplate
+ parameters:
+ intent: 'PackageDistribution'
+ contentType: 'Maven'
+ contentSource: 'Folder'
+ folderLocation: '$(System.ArtifactsDirectory)/m2/com.microsoft.java.debug.plugin'
+ waitForReleaseCompletion: true
+ owners: 'jinbwan@microsoft.com'
+ approvers: 'roml@microsoft.com'
\ No newline at end of file
diff --git a/.azure-pipelines/signjars-nightly.yml b/.azure-pipelines/signjars-nightly.yml
new file mode 100644
index 000000000..8b1e8d12a
--- /dev/null
+++ b/.azure-pipelines/signjars-nightly.yml
@@ -0,0 +1,138 @@
+name: $(Date:yyyyMMdd).$(Rev:r)
+variables:
+ - name: Codeql.Enabled
+ value: true
+schedules:
+ - cron: 0 5 * * 1,2,3,4,5
+ branches:
+ include:
+ - refs/heads/main
+resources:
+ repositories:
+ - repository: self
+ type: git
+ ref: refs/heads/main
+ - repository: 1esPipelines
+ type: git
+ name: 1ESPipelineTemplates/1ESPipelineTemplates
+ ref: refs/tags/release
+trigger: none
+extends:
+ template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
+ parameters:
+ pool:
+ os: linux
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Ubuntu-2004
+ sdl:
+ sourceAnalysisPool:
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Windows_2022
+ os: windows
+ customBuildTags:
+ - MigrationTooling-mseng-VSJava-13474-Tool
+ stages:
+ - stage: Build
+ jobs:
+ - job: Job_1
+ displayName: Sign-Jars-Nightly
+ templateContext:
+ outputs:
+ - output: pipelineArtifact
+ artifactName: plugin
+ targetPath: $(Build.ArtifactStagingDirectory)
+ displayName: "Publish Artifact: plugin"
+ steps:
+ - checkout: self
+ fetchTags: true
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core 3.1.x'
+ inputs:
+ packageType: 'sdk'
+ version: '3.1.x'
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core 8.0.x'
+ inputs:
+ packageType: 'sdk'
+ version: '8.0.x'
+ - task: MicroBuildSigningPlugin@4
+ displayName: 'Install Signing Plugin'
+ inputs:
+ signType: real
+ azureSubscription: 'MicroBuild Signing Task (MSEng)'
+ useEsrpCli: true
+ ConnectedPMEServiceName: 0e38ce24-f885-4c86-b997-5887b97a1899
+ feedSource: 'https://mseng.pkgs.visualstudio.com/DefaultCollection/_packaging/MicroBuildToolset/nuget/v3/index.json'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)'
+ - task: JavaToolInstaller@0
+ displayName: Use Java 21
+ inputs:
+ versionSpec: "21"
+ jdkArchitectureOption: x64
+ jdkSourceOption: PreInstalled
+ - task: CmdLine@2
+ displayName: Parse the release version from pom.xml
+ inputs:
+ script: |-
+ #!/bin/bash
+
+ sudo apt-get install xmlstarlet
+ xmlstarlet --version
+ RELEASE_VERSION=$(xmlstarlet sel -t -v "/_:project/_:version" pom.xml)
+ echo $RELEASE_VERSION
+ echo "##vso[task.setvariable variable=RELEASE_VERSION]$RELEASE_VERSION"
+ - task: CmdLine@2
+ displayName: Build core.jar
+ inputs:
+ script: |
+ ./mvnw clean install -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository
+
+ mkdir -p jars
+ mv .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/com.microsoft.java.debug.core*.jar jars/
+ - task: CmdLine@2
+ displayName: Sign core jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "com.microsoft.java.debug.core*.jar")
+ for file in $files; do
+ fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171
+ done
+ workingDirectory: 'jars'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CmdLine@2
+ displayName: install signed core.jar
+ inputs:
+ script: cp jars/com.microsoft.java.debug.core*.jar .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/
+ - task: CmdLine@2
+ displayName: Build plugin.jar
+ inputs:
+ script: |-
+ ./mvnw clean install -N -f pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw clean install -f com.microsoft.java.debug.target/pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw clean install -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository
+
+ mkdir -p jars
+ mv .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/com.microsoft.java.debug.plugin*.jar jars/
+ - task: CmdLine@2
+ displayName: Sign plugin jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "com.microsoft.java.debug.plugin*.jar")
+ for file in $files; do
+ fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171
+ done
+ workingDirectory: 'jars'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CopyFiles@2
+ displayName: "Copy plugin.jar to: $(Build.ArtifactStagingDirectory)"
+ inputs:
+ Contents: |+
+ jars/com.microsoft.java.debug.plugin*.jar
+
+ TargetFolder: $(Build.ArtifactStagingDirectory)
diff --git a/.azure-pipelines/signjars-rc.yml b/.azure-pipelines/signjars-rc.yml
new file mode 100644
index 000000000..e87444603
--- /dev/null
+++ b/.azure-pipelines/signjars-rc.yml
@@ -0,0 +1,169 @@
+name: $(Date:yyyyMMdd).$(Rev:r)
+variables:
+ - name: Codeql.Enabled
+ value: true
+resources:
+ repositories:
+ - repository: self
+ type: git
+ ref: refs/heads/main
+ - repository: 1esPipelines
+ type: git
+ name: 1ESPipelineTemplates/1ESPipelineTemplates
+ ref: refs/tags/release
+trigger: none
+extends:
+ template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
+ parameters:
+ pool:
+ os: linux
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Ubuntu-2004
+ sdl:
+ sourceAnalysisPool:
+ name: 1ES_JavaTooling_Pool
+ image: 1ES_JavaTooling_Windows_2022
+ os: windows
+ customBuildTags:
+ - MigrationTooling-mseng-VSJava-9151-Tool
+ stages:
+ - stage: Build
+ jobs:
+ - job: Job_1
+ displayName: Sign-Jars-RC
+ templateContext:
+ outputs:
+ - output: pipelineArtifact
+ artifactName: m2
+ targetPath: $(Build.ArtifactStagingDirectory)/m2
+ displayName: "Publish Artifact: m2"
+ steps:
+ - checkout: self
+ fetchTags: true
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core 3.1.x'
+ inputs:
+ packageType: 'sdk'
+ version: '3.1.x'
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core 8.0.x'
+ inputs:
+ packageType: 'sdk'
+ version: '8.0.x'
+ - task: MicroBuildSigningPlugin@4
+ displayName: 'Install Signing Plugin'
+ inputs:
+ signType: real
+ azureSubscription: 'MicroBuild Signing Task (MSEng)'
+ useEsrpCli: true
+ ConnectedPMEServiceName: 0e38ce24-f885-4c86-b997-5887b97a1899
+ feedSource: 'https://mseng.pkgs.visualstudio.com/DefaultCollection/_packaging/MicroBuildToolset/nuget/v3/index.json'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ MicroBuildOutputFolderOverride: '$(Agent.TempDirectory)'
+ - task: JavaToolInstaller@0
+ displayName: Use Java 21
+ inputs:
+ versionSpec: "21"
+ jdkArchitectureOption: x64
+ jdkSourceOption: PreInstalled
+ - task: CmdLine@2
+ displayName: Parse the release version from pom.xml
+ inputs:
+ script: |-
+ #!/bin/bash
+
+ sudo apt-get install xmlstarlet
+ xmlstarlet --version
+ RELEASE_VERSION=$(xmlstarlet sel -t -v "/_:project/_:version" pom.xml)
+ echo $RELEASE_VERSION
+ echo "##vso[task.setvariable variable=RELEASE_VERSION]$RELEASE_VERSION"
+ - task: CmdLine@2
+ displayName: Build core.jar
+ inputs:
+ script: |
+ ./mvnw -N clean install -Dmaven.repo.local=./.repository
+
+ ./mvnw clean install -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository
+
+ mkdir -p jars
+ mv .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/com.microsoft.java.debug.core*.jar jars/
+ - task: CmdLine@2
+ displayName: Sign core jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "com.microsoft.java.debug.core*.jar")
+ for file in $files; do
+ fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171
+ done
+ workingDirectory: 'jars'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CmdLine@2
+ displayName: install signed core.jar
+ inputs:
+ script: cp jars/com.microsoft.java.debug.core*.jar .repository/com/microsoft/java/com.microsoft.java.debug.core/$RELEASE_VERSION/
+ - task: CmdLine@2
+ displayName: Build plugin.jar
+ inputs:
+ script: |-
+ ./mvnw clean install -f com.microsoft.java.debug.target/pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw clean install -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository
+
+ mkdir -p jars
+ mv .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/com.microsoft.java.debug.plugin*.jar jars/
+ - task: CmdLine@2
+ displayName: Sign plugin jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "com.microsoft.java.debug.plugin*.jar")
+ for file in $files; do
+ fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$fileName" /certs:100010171
+ done
+ workingDirectory: 'jars'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CmdLine@2
+ displayName: install signed plugin.jar
+ inputs:
+ script: cp jars/com.microsoft.java.debug.plugin*.jar .repository/com/microsoft/java/com.microsoft.java.debug.plugin/$RELEASE_VERSION/
+ - task: CmdLine@2
+ displayName: build m2 artifacts
+ inputs:
+ script: |
+ ./mvnw source:jar -f com.microsoft.java.debug.core/pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw javadoc:jar -f com.microsoft.java.debug.core/pom.xml -Ddoclint=none -Dmaven.repo.local=./.repository
+
+ ./mvnw source:jar -f com.microsoft.java.debug.plugin/pom.xml -Dmaven.repo.local=./.repository
+ ./mvnw javadoc:jar -f com.microsoft.java.debug.plugin/pom.xml -Ddoclint=none -Dmaven.repo.local=./.repository
+
+ mkdir -p m2/java-debug-parent
+ cp pom.xml m2/java-debug-parent/java-debug-parent-$RELEASE_VERSION.pom
+
+ mkdir -p m2/com.microsoft.java.debug.core
+ cp com.microsoft.java.debug.core/target/com.microsoft.java.debug.core*.jar m2/com.microsoft.java.debug.core
+ cp com.microsoft.java.debug.core/pom.xml m2/com.microsoft.java.debug.core/com.microsoft.java.debug.core-$RELEASE_VERSION.pom
+
+ mkdir -p m2/com.microsoft.java.debug.plugin
+ cp com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin*.jar m2/com.microsoft.java.debug.plugin
+ cp com.microsoft.java.debug.plugin/pom.xml m2/com.microsoft.java.debug.plugin/com.microsoft.java.debug.plugin-$RELEASE_VERSION.pom
+ - task: CmdLine@2
+ displayName: Sign m2 jars
+ inputs:
+ script: |
+ files=$(find . -type f -name "*.jar")
+ for file in $files; do
+ # fileName=$(basename "$file")
+ dotnet "$MBSIGN_APPFOLDER/DDSignFiles.dll" -- /file:"$file" /certs:100010171
+ done
+ workingDirectory: 'm2'
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: CopyFiles@2
+ displayName: "Copy m2 to: $(Build.ArtifactStagingDirectory)"
+ inputs:
+ Contents: |+
+ m2/**
+ TargetFolder: $(Build.ArtifactStagingDirectory)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b11684f52..9d7579633 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1 @@
-* @testforstephen @jdneo
\ No newline at end of file
+* @testforstephen @jdneo @chagong @wenytang-ms
diff --git a/.github/llms.md b/.github/llms.md
new file mode 100644
index 000000000..55d69b238
--- /dev/null
+++ b/.github/llms.md
@@ -0,0 +1,38 @@
+# Extension Pack for Java
+Extension Pack for Java is a collection of popular extensions that can help write, test and debug Java applications in Visual Studio Code. By installing Extension Pack for Java, the following extensions are installed:
+
+- [📦 Language Support for Java™ by Red Hat ](https://marketplace.visualstudio.com/items?itemName=redhat.java)
+ - Code Navigation
+ - Auto Completion
+ - Refactoring
+ - Code Snippets
+- [📦 Debugger for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug)
+ - Debugging
+- [📦 Test Runner for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-test)
+ - Run & Debug JUnit/TestNG Test Cases
+- [📦 Maven for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-maven)
+ - Project Scaffolding
+ - Custom Goals
+- [📦 Gradle for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-gradle)
+ - View Gradle tasks and project dependencies
+ - Gradle file authoring
+ - Import Gradle projects via [Gradle Build Server](https://github.com/microsoft/build-server-for-gradle)
+- [📦 Project Manager for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-dependency)
+ - Manage Java projects, referenced libraries, resource files, packages, classes, and class members
+- [📦 Visual Studio IntelliCode](https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode)
+ - AI-assisted development
+ - Completion list ranked by AI
+
+## Label
+When labeling an issue, follow the rules below per label category:
+### General Rules
+- Analyze if the issue is related with the scope of using extensions for Java development. If not, STOP labelling IMMEDIATELY.
+- Assign label per category.
+- If a category is not applicable or you're unsure, you may skip it.
+- Do not assign multiple labels within the same category, unless explicitly allowed as an exception.
+
+### Issue Type Labels
+- [bug]: Primary label for real bug issues
+- [enhancement]: Primary label for enhancement issues
+- [documentation]: Primary label for documentation issues
+- [question]: Primary label for question issues
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 41c85cb62..553d95a9a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,80 +12,83 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v5
- - name: Set up JDK 17
- uses: actions/setup-java@v1
- with:
- java-version: '17'
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
- - name: Cache local Maven repository
- uses: actions/cache@v2
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
+ - name: Cache local Maven repository
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
- - name: Verify
- run: ./mvnw clean verify
+ - name: Verify
+ run: ./mvnw clean verify -U
- - name: Checkstyle
- run: ./mvnw checkstyle:check
+ - name: Checkstyle
+ run: ./mvnw checkstyle:check
windows:
name: Windows
runs-on: windows-latest
timeout-minutes: 30
steps:
- - name: Set git to use LF
- run: |
- git config --global core.autocrlf false
- git config --global core.eol lf
+ - name: Set git to use LF
+ run: |
+ git config --global core.autocrlf false
+ git config --global core.eol lf
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v5
- - name: Set up JDK 17
- uses: actions/setup-java@v1
- with:
- java-version: '17'
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
- - name: Cache local Maven repository
- uses: actions/cache@v2
- with:
- path: $HOME/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
+ - name: Cache local Maven repository
+ uses: actions/cache@v4
+ with:
+ path: $HOME/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
- - name: Verify
- run: ./mvnw.cmd clean verify
+ - name: Verify
+ run: ./mvnw.cmd clean verify
- - name: Checkstyle
- run: ./mvnw.cmd checkstyle:check
+ - name: Checkstyle
+ run: ./mvnw.cmd checkstyle:check
darwin:
name: macOS
runs-on: macos-latest
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
-
- - name: Set up JDK 17
- uses: actions/setup-java@v1
- with:
- java-version: '17'
-
- - name: Cache local Maven repository
- uses: actions/cache@v2
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
-
- - name: Verify
- run: ./mvnw clean verify
-
- - name: Checkstyle
- run: ./mvnw checkstyle:check
+ - uses: actions/checkout@v5
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+
+ - name: Cache local Maven repository
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Verify
+ run: ./mvnw clean verify -U
+
+ - name: Checkstyle
+ run: ./mvnw checkstyle:check
diff --git a/.github/workflows/triage-agent.yml b/.github/workflows/triage-agent.yml
new file mode 100644
index 000000000..f1df6e117
--- /dev/null
+++ b/.github/workflows/triage-agent.yml
@@ -0,0 +1,125 @@
+name: AI Triage
+on:
+ issues:
+ types: [opened]
+ workflow_dispatch:
+ inputs:
+ issue_number:
+ description: 'Issue number to triage (manual run). e.g. 123'
+ required: true
+
+run-name: >-
+ AI Triage for Issue #${{ github.event.issue.number || github.event.inputs.issue_number }}
+
+permissions:
+ issues: write
+ contents: read
+
+jobs:
+ label_and_comment:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Get issue data
+ id: get_issue
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const eventName = context.eventName;
+ let issue;
+ if (eventName === 'workflow_dispatch') {
+ const inputs = context.payload.inputs || {};
+ const issueNumber = inputs.issue_number || inputs.issueNumber;
+ if (!issueNumber) core.setFailed('Input issue_number is required for manual run.');
+ const { data } = await github.rest.issues.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: parseInt(issueNumber, 10),
+ });
+ issue = data;
+ } else if (context.payload.issue) {
+ issue = context.payload.issue;
+ } else {
+ core.setFailed('No issue information found in the event payload.');
+ }
+ core.setOutput('id', String(issue.number));
+ core.setOutput('user', String((issue.user && issue.user.login) || ''));
+ core.setOutput('title', String(issue.title || ''));
+ core.setOutput('body', String(issue.body || ''));
+ const labelNames = (issue.labels || []).map(label => label.name);
+ core.setOutput('labels', JSON.stringify(labelNames));
+
+ - name: Call Azure Function
+ id: call_azure_function
+ env:
+ PAYLOAD: >-
+ {
+ "authToken": "${{ secrets.GITHUB_TOKEN }}",
+ "repoId": "microsoft/java-debug",
+ "issueData": {
+ "id": ${{ steps.get_issue.outputs.id }},
+ "user": ${{ toJson(steps.get_issue.outputs.user) }},
+ "title": ${{ toJson(steps.get_issue.outputs.title) }},
+ "body": ${{ toJson(steps.get_issue.outputs.body) }},
+ "labels": ${{ steps.get_issue.outputs.labels }}
+ },
+ "mode": "DirectUpdate"
+ }
+
+ run: |
+ # Make the HTTP request with improved error handling and timeouts
+ echo "Making request to triage agent..."
+
+ # Add timeout handling and better error detection
+ set +e # Don't exit on curl failure
+ response=$(timeout ${{ vars.TRIAGE_AGENT_TIMEOUT }} curl \
+ --max-time 0 \
+ --connect-timeout 30 \
+ --fail-with-body \
+ --silent \
+ --show-error \
+ --write-out "HTTPSTATUS:%{http_code}" \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data "$PAYLOAD" \
+ ${{ secrets.TRIAGE_FUNCTION_LINK }} 2>&1)
+
+ curl_exit_code=$?
+ set -e # Re-enable exit on error
+
+ echo "Curl exit code: $curl_exit_code"
+
+ # Check if curl command timed out or failed
+ if [ $curl_exit_code -eq 124 ]; then
+ echo "❌ Request timed out after 650 seconds"
+ exit 1
+ elif [ $curl_exit_code -ne 0 ]; then
+ echo "❌ Curl command failed with exit code: $curl_exit_code"
+ echo "Response: $response"
+ exit 1
+ fi
+
+ # Extract HTTP status code and response body
+ http_code=$(echo "$response" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2)
+ response_body=$(echo "$response" | sed 's/HTTPSTATUS:[0-9]*$//')
+
+ echo "HTTP Status Code: $http_code"
+
+ # Validate HTTP status code
+ if [ -z "$http_code" ]; then
+ echo "❌ Failed to extract HTTP status code from response"
+ echo "Raw response: $response"
+ exit 1
+ fi
+
+ # Check if the request was successful
+ if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
+ echo "✅ Azure Function call succeeded"
+ else
+ echo "❌ Azure Function call failed with status code: $http_code"
+ echo "Response: $response_body"
+ exit 1
+ fi
\ No newline at end of file
diff --git a/.github/workflows/triage-all-open-issues.yml b/.github/workflows/triage-all-open-issues.yml
new file mode 100644
index 000000000..092f4ef70
--- /dev/null
+++ b/.github/workflows/triage-all-open-issues.yml
@@ -0,0 +1,145 @@
+name: AI Triage - Process All Open Issues
+on:
+ workflow_dispatch:
+ inputs:
+ dry_run:
+ description: 'Dry run mode - only list issues without processing'
+ required: false
+ default: false
+ type: boolean
+ max_issues:
+ description: 'Maximum number of issues to process (0 = all)'
+ required: false
+ default: '0'
+ type: string
+
+permissions:
+ issues: write
+ contents: read
+ actions: write
+
+jobs:
+ get_open_issues:
+ runs-on: ubuntu-latest
+ outputs:
+ issue_numbers: ${{ steps.get_issues.outputs.issue_numbers }}
+ total_count: ${{ steps.get_issues.outputs.total_count }}
+
+ steps:
+ - name: Get all open issues
+ id: get_issues
+ uses: actions/github-script@v6
+ with:
+ script: |
+ // Use Search API to filter issues at API level
+ const { data } = await github.rest.search.issuesAndPullRequests({
+ q: `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open -label:ai-triaged -label:invalid`,
+ sort: 'created',
+ order: 'asc',
+ per_page: 100
+ });
+
+ const actualIssues = data.items;
+
+ let issuesToProcess = actualIssues;
+ const maxIssues = parseInt('${{ inputs.max_issues }}' || '0');
+
+ if (maxIssues > 0 && actualIssues.length > maxIssues) {
+ issuesToProcess = actualIssues.slice(0, maxIssues);
+ console.log(`Limiting to first ${maxIssues} issues out of ${actualIssues.length} total`);
+ }
+
+ const issueNumbers = issuesToProcess.map(issue => issue.number);
+ const totalCount = issuesToProcess.length;
+
+ console.log(`Found ${actualIssues.length} open issues, processing ${totalCount}:`);
+ issuesToProcess.forEach(issue => {
+ console.log(` #${issue.number}: ${issue.title}`);
+ });
+
+ core.setOutput('issue_numbers', JSON.stringify(issueNumbers));
+ core.setOutput('total_count', totalCount);
+
+ process_issues:
+ runs-on: ubuntu-latest
+ needs: get_open_issues
+ if: needs.get_open_issues.outputs.total_count > 0
+
+ strategy:
+ # Process issues one by one (max-parallel: 1)
+ max-parallel: 1
+ matrix:
+ issue_number: ${{ fromJSON(needs.get_open_issues.outputs.issue_numbers) }}
+
+ steps:
+ - name: Log current issue being processed
+ run: |
+ echo "🔄 Processing issue #${{ matrix.issue_number }}"
+ echo "Total issues to process: ${{ needs.get_open_issues.outputs.total_count }}"
+
+ - name: Check if dry run mode
+ if: inputs.dry_run == true
+ run: |
+ echo "🔍 DRY RUN MODE: Would process issue #${{ matrix.issue_number }}"
+ echo "Skipping actual triage processing"
+
+ - name: Trigger triage workflow for issue
+ if: inputs.dry_run != true
+ uses: actions/github-script@v6
+ with:
+ script: |
+ const issueNumber = '${{ matrix.issue_number }}';
+
+ try {
+ console.log(`Triggering triage workflow for issue #${issueNumber}`);
+
+ const response = await github.rest.actions.createWorkflowDispatch({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ workflow_id: 'triage-agent.yml',
+ ref: 'main',
+ inputs: {
+ issue_number: issueNumber
+ }
+ });
+
+ console.log(`✅ Successfully triggered triage workflow for issue #${issueNumber}`);
+
+ } catch (error) {
+ console.error(`❌ Failed to trigger triage workflow for issue #${issueNumber}:`, error);
+ core.setFailed(`Failed to process issue #${issueNumber}: ${error.message}`);
+ }
+
+ - name: Wait for workflow completion
+ if: inputs.dry_run != true
+ run: |
+ echo "⏳ Waiting for triage workflow to complete for issue #${{ matrix.issue_number }}..."
+ echo "Timeout: ${{ vars.TRIAGE_AGENT_TIMEOUT }} seconds"
+ sleep ${{ vars.TRIAGE_AGENT_TIMEOUT }} # Wait for triage workflow completion
+
+ summary:
+ runs-on: ubuntu-latest
+ needs: [get_open_issues, process_issues]
+ if: always()
+
+ steps:
+ - name: Print summary
+ run: |
+ echo "## Triage Processing Summary"
+ echo "Total open issues found: ${{ needs.get_open_issues.outputs.total_count }}"
+
+ if [ "${{ inputs.dry_run }}" == "true" ]; then
+ echo "Mode: DRY RUN (no actual processing performed)"
+ else
+ echo "Mode: FULL PROCESSING"
+ fi
+
+ if [ "${{ needs.process_issues.result }}" == "success" ]; then
+ echo "✅ All issues processed successfully"
+ elif [ "${{ needs.process_issues.result }}" == "failure" ]; then
+ echo "❌ Some issues failed to process"
+ elif [ "${{ needs.process_issues.result }}" == "skipped" ]; then
+ echo "⏭️ Processing was skipped (no open issues found)"
+ else
+ echo "⚠️ Processing completed with status: ${{ needs.process_issues.result }}"
+ fi
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100644
index 41c70a7e0..000000000
Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 9e0264d04..44f3cf2c1 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1 +1,2 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip
\ No newline at end of file
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
diff --git a/com.microsoft.java.debug.core/.classpath b/com.microsoft.java.debug.core/.classpath
index 9ba41a249..b6fe6b96c 100644
--- a/com.microsoft.java.debug.core/.classpath
+++ b/com.microsoft.java.debug.core/.classpath
@@ -13,7 +13,7 @@
-
+
diff --git a/com.microsoft.java.debug.core/pom.xml b/com.microsoft.java.debug.core/pom.xml
index 635993d56..8bfdb7292 100644
--- a/com.microsoft.java.debug.core/pom.xml
+++ b/com.microsoft.java.debug.core/pom.xml
@@ -5,7 +5,7 @@
com.microsoft.java
java-debug-parent
- 0.50.0
+ 0.53.2
com.microsoft.java.debug.core
jar
@@ -42,7 +42,7 @@
org.apache.commons
commons-lang3
- 3.6
+ 3.18.0
com.google.code.gson
@@ -62,7 +62,7 @@
commons-io
commons-io
- 2.11.0
+ 2.14.0
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java
index dfdbad4bb..82859b268 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/Breakpoint.java
@@ -325,7 +325,8 @@ private Location findMethodLocaiton(ReferenceType refType, String methodName, St
for (Method method : methods) {
if (!method.isAbstract() && !method.isNative()
&& methodName.equals(method.name())
- && (methodSiguature.equals(method.genericSignature()) || methodSiguature.equals(method.signature()))) {
+ && (methodSiguature.equals(method.genericSignature()) || methodSiguature.equals(method.signature())
+ || toNoneGeneric(methodSiguature).equals(method.signature()))) {
location = method.location();
break;
}
@@ -334,6 +335,28 @@ private Location findMethodLocaiton(ReferenceType refType, String methodName, St
return location;
}
+ static String toNoneGeneric(String genericSig) {
+ StringBuilder builder = new StringBuilder();
+ boolean append = true;
+ int depth = 0;
+ char[] chars = genericSig.toCharArray();
+ for (int i = 0; i < chars.length; i++) {
+ char c = chars[i];
+ if (c == '<') {
+ depth++;
+ append = (depth == 0);
+ }
+ if (append) {
+ builder.append(c);
+ }
+ if (c == '>') {
+ depth--;
+ append = (depth == 0);
+ }
+ }
+ return builder.toString();
+ }
+
private List findLocaitonsOfLine(Method method, int lineNumber) {
try {
return method.locationsOfLine(lineNumber);
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java
index fbad52fe2..38a234fa9 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/DebugSession.java
@@ -119,7 +119,9 @@ public void detach() {
@Override
public void terminate() {
- if (vm.process() == null || vm.process().isAlive()) {
+ if (vm.process() != null && vm.process().isAlive()) {
+ vm.process().destroy();
+ } else if (vm.process() == null || vm.process().isAlive()) {
vm.exit(0);
}
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java
index f8ac01c3e..bbf215c67 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapterContext.java
@@ -32,7 +32,7 @@
public class DebugAdapterContext implements IDebugAdapterContext {
private static final int MAX_CACHE_ITEMS = 10000;
private final StepFilters defaultFilters = new StepFilters();
- private Map sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS));
+ private Map sourceMappingCache = Collections.synchronizedMap(new LRUCache<>(MAX_CACHE_ITEMS));
private IProviderContext providerContext;
private IProtocolServer server;
@@ -212,7 +212,7 @@ public void setVariableFormatter(IVariableFormatter variableFormatter) {
}
@Override
- public Map getSourceLookupCache() {
+ public Map getSourceLookupCache() {
return sourceMappingCache;
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
index 9a38e8598..e65270eef 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/IDebugAdapterContext.java
@@ -85,7 +85,7 @@ public interface IDebugAdapterContext {
void setVariableFormatter(IVariableFormatter variableFormatter);
- Map getSourceLookupCache();
+ Map getSourceLookupCache();
void setDebuggeeEncoding(Charset encoding);
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
index 5ffed6e0f..f33742852 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/ISourceLookUpProvider.java
@@ -8,7 +8,6 @@
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
-
package com.microsoft.java.debug.core.adapter;
import java.util.List;
@@ -42,18 +41,31 @@ public interface ISourceLookUpProvider extends IProvider {
JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceBreakpoint[] sourceBreakpoints) throws DebugException;
/**
- * Given a fully qualified class name and source file path, search the associated disk source file.
- *
- * @param fullyQualifiedName
- * the fully qualified class name (e.g. com.microsoft.java.debug.core.adapter.ISourceLookUpProvider).
- * @param sourcePath
- * the qualified source file path (e.g. com\microsoft\java\debug\core\adapter\ISourceLookupProvider.java).
- * @return the associated source file uri.
+ * Deprecated, please use {@link #getSource(String, String)} instead.
*/
+ @Deprecated
String getSourceFileURI(String fullyQualifiedName, String sourcePath);
String getSourceContents(String uri);
+ /**
+ * Retrieves a {@link Source} object representing the source code associated with the given fully qualified class name and source file path.
+ * The implementation of this interface can determine a source is "local" or "remote".
+ * In case of "remote" a follow up "source" request will be issued by the client
+ *
+ * @param fullyQualifiedName
+ * the fully qualified class name,
+ * e.g., "com.microsoft.java.debug.core.adapter.ISourceLookUpProvider".
+ * @param sourcePath
+ * the qualified source file path,
+ * e.g., "com/microsoft/java/debug/core/adapter/ISourceLookupProvider.java".
+ * @return A {@link Source} object encapsulating the source file URI obtained from
+ * {@link #getSourceFileURI(String, String)} and the source type as {@link SourceType#LOCAL}.
+ */
+ default Source getSource(String fullyQualifiedName, String sourcePath) {
+ return new Source(getSourceFileURI(fullyQualifiedName, sourcePath), SourceType.LOCAL);
+ }
+
/**
* Returns the Java runtime that the specified project's build path used.
* @param projectName
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java
new file mode 100644
index 000000000..d00b4cb4c
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/Source.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Microsoft Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Microsoft Corporation - initial API and implementation
+ *******************************************************************************/
+
+package com.microsoft.java.debug.core.adapter;
+
+public class Source {
+ public final String uri;
+ public final SourceType type;
+
+ public Source(String uri, SourceType type) {
+ this.uri = uri;
+ this.type = type;
+ }
+
+ public String getUri() {
+ return this.uri;
+ }
+
+ public SourceType getType() {
+ return this.type;
+ }
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java
new file mode 100644
index 000000000..724bf3bda
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/SourceType.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Microsoft Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Microsoft Corporation - initial API and implementation
+ *******************************************************************************/
+package com.microsoft.java.debug.core.adapter;
+
+public enum SourceType {
+ REMOTE,
+ LOCAL
+}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java
index 27fdb1813..7370328b2 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/LaunchUtils.java
@@ -95,7 +95,7 @@ public static synchronized Path generateClasspathJar(String[] classPaths) throws
// In jar manifest, the absolute path C:\a.jar should be converted to the url style file:///C:/a.jar
String classpathValue = String.join(" ", classpathUrls);
attributes.put(Attributes.Name.CLASS_PATH, classpathValue);
- String baseName = "cp_" + getMd5(classpathValue);
+ String baseName = "cp_" + getSha256(classpathValue);
cleanupTempFiles(baseName, ".jar");
Path tempfile = createTempFile(baseName, ".jar");
JarOutputStream jar = new JarOutputStream(new FileOutputStream(tempfile.toFile()), manifest);
@@ -127,7 +127,7 @@ public static synchronized Path generateArgfile(String vmArgs, String[] classPat
}
argfile = argfile.replace("\\", "\\\\");
- String baseName = "cp_" + getMd5(argfile);
+ String baseName = "cp_" + getSha256(argfile);
cleanupTempFiles(baseName, ".argfile");
Path tempfile = createTempFile(baseName, ".argfile");
Files.writeString(tempfile, argfile, encoding);
@@ -364,12 +364,15 @@ private static Path createTempFile(String baseName, String suffix) throws IOExce
}
}
- private static String getMd5(String input) {
+ private static String getSha256(String input) {
try {
- MessageDigest md = MessageDigest.getInstance("MD5");
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] messageDigest = md.digest(input.getBytes());
- BigInteger md5 = new BigInteger(1, messageDigest);
- return md5.toString(Character.MAX_RADIX);
+ // Use only first 16 bytes to keep filename shorter
+ byte[] truncated = new byte[16];
+ System.arraycopy(messageDigest, 0, truncated, 0, 16);
+ BigInteger hash = new BigInteger(1, truncated);
+ return hash.toString(Character.MAX_RADIX);
} catch (NoSuchAlgorithmException e) {
return Integer.toString(input.hashCode(), Character.MAX_RADIX);
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java
index 0ba3b2004..6f54bcedb 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StackTraceRequestHandler.java
@@ -29,22 +29,24 @@
import com.google.gson.JsonObject;
import com.microsoft.java.debug.core.AsyncJdwpUtils;
import com.microsoft.java.debug.core.DebugSettings;
+import com.microsoft.java.debug.core.DebugSettings.Switch;
import com.microsoft.java.debug.core.DebugUtility;
import com.microsoft.java.debug.core.IBreakpoint;
-import com.microsoft.java.debug.core.DebugSettings.Switch;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
import com.microsoft.java.debug.core.adapter.IDebugRequestHandler;
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
+import com.microsoft.java.debug.core.adapter.Source;
+import com.microsoft.java.debug.core.adapter.SourceType;
import com.microsoft.java.debug.core.adapter.formatter.SimpleTypeFormatter;
import com.microsoft.java.debug.core.adapter.variables.StackFrameReference;
+import com.microsoft.java.debug.core.protocol.Events.TelemetryEvent;
import com.microsoft.java.debug.core.protocol.Messages.Response;
import com.microsoft.java.debug.core.protocol.Requests.Arguments;
import com.microsoft.java.debug.core.protocol.Requests.Command;
import com.microsoft.java.debug.core.protocol.Requests.StackTraceArguments;
import com.microsoft.java.debug.core.protocol.Responses;
import com.microsoft.java.debug.core.protocol.Types;
-import com.microsoft.java.debug.core.protocol.Events.TelemetryEvent;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.LocalVariable;
@@ -112,7 +114,7 @@ public CompletableFuture handle(Command command, Arguments arguments,
result.add(lspFrame);
frameReference.setSource(lspFrame.source);
int jdiLineNumber = AdapterUtils.convertLineNumber(jdiFrame.lineNumber, context.isDebuggerLinesStartAt1(), context.isClientLinesStartAt1());
- if (jdiLineNumber != lspFrame.line) {
+ if (jdiLineNumber != lspFrame.line && lspFrame.source != null && lspFrame.source.path != null) {
decompiledClasses.add(lspFrame.source.path);
}
}
@@ -141,7 +143,7 @@ public CompletableFuture handle(Command command, Arguments arguments,
}
private static List resolveStackFrameInfos(StackFrame[] frames, boolean async)
- throws AbsentInformationException, IncompatibleThreadStateException {
+ throws AbsentInformationException, IncompatibleThreadStateException {
List jdiFrames = new ArrayList<>();
List> futures = new ArrayList<>();
for (StackFrame frame : frames) {
@@ -220,8 +222,10 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra
// display "Unknown Source" in the Call Stack View.
clientSource = null;
}
+ // DAP specifies lineNumber to be set to 0 when unavailable
+ clientLineNumber = 0;
} else if (DebugSettings.getCurrent().debugSupportOnDecompiledSource == Switch.ON
- && clientSource != null && clientSource.path != null) {
+ && clientSource != null && clientSource.path != null) {
// Align the original line with the decompiled line.
int[] lineMappings = context.getProvider(ISourceLookUpProvider.class).getOriginalLineMappings(clientSource.path);
int[] renderLines = AdapterUtils.binarySearchMappedLines(lineMappings, clientLineNumber);
@@ -244,7 +248,7 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra
});
if (match) {
clientColumnNumber = AdapterUtils.convertColumnNumber(breakpoint.getColumnNumber(),
- context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1());
+ context.isDebuggerColumnsStartAt1(), context.isClientColumnsStartAt1());
}
}
}
@@ -256,33 +260,44 @@ private Types.StackFrame convertDebuggerStackFrameToClient(StackFrameInfo jdiFra
/**
* Find the source mapping for the specified source file name.
*/
- public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedName, String sourceName, String relativeSourcePath,
+ public static Types.Source convertDebuggerSourceToClient(String fullyQualifiedName, String sourceName,
+ String relativeSourcePath,
IDebugAdapterContext context) throws URISyntaxException {
+
// use a lru cache for better performance
- String uri = context.getSourceLookupCache().computeIfAbsent(fullyQualifiedName, key -> {
- String fromProvider = context.getProvider(ISourceLookUpProvider.class).getSourceFileURI(key, relativeSourcePath);
- // avoid return null which will cause the compute function executed again
- return StringUtils.isBlank(fromProvider) ? "" : fromProvider;
+ Source source = context.getSourceLookupCache().computeIfAbsent(fullyQualifiedName, key -> {
+ Source result = context.getProvider(ISourceLookUpProvider.class).getSource(key, relativeSourcePath);
+ if (result == null) {
+ return new Source("", SourceType.LOCAL);
+ }
+ return result;
});
+ Integer sourceReference = 0;
+ String uri = source.getUri();
+
+ if (source.getType().equals(SourceType.REMOTE)) {
+ sourceReference = context.createSourceReference(source.getUri());
+ }
+
if (!StringUtils.isBlank(uri)) {
// The Source.path could be a file system path or uri string.
if (uri.startsWith("file:")) {
String clientPath = AdapterUtils.convertPath(uri, context.isDebuggerPathsAreUri(), context.isClientPathsAreUri());
- return new Types.Source(sourceName, clientPath, 0);
+ return new Types.Source(sourceName, clientPath, sourceReference);
} else {
// If the debugger returns uri in the Source.path for the StackTrace response, VSCode client will try to find a TextDocumentContentProvider
// to render the contents.
// Language Support for Java by Red Hat extension has already registered a jdt TextDocumentContentProvider to parse the jdt-based uri.
// The jdt uri looks like 'jdt://contents/rt.jar/java.io/PrintStream.class?=1.helloworld/%5C/usr%5C/lib%5C/jvm%5C/java-8-oracle%5C/jre%5C/
// lib%5C/rt.jar%3Cjava.io(PrintStream.class'.
- return new Types.Source(sourceName, uri, 0);
+ return new Types.Source(sourceName, uri, sourceReference);
}
} else {
// If the source lookup engine cannot find the source file, then lookup it in the source directories specified by user.
String absoluteSourcepath = AdapterUtils.sourceLookup(context.getSourcePaths(), relativeSourcePath);
if (absoluteSourcepath != null) {
- return new Types.Source(sourceName, absoluteSourcepath, 0);
+ return new Types.Source(sourceName, absoluteSourcepath, sourceReference);
} else {
return null;
}
diff --git a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java
index 71f28355a..e8f782668 100644
--- a/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java
+++ b/com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/StepRequestHandler.java
@@ -268,7 +268,8 @@ private void handleDebugEvent(DebugEvent debugEvent, IDebugSession debugSession,
} else if (currentStackDepth == threadState.stackDepth) {
// If the ending step location is same as the original location where the step into operation is originated,
// do another step of the same kind.
- if (isSameLocation(currentStepLocation, threadState.stepLocation)) {
+ if (isSameLocation(currentStepLocation, threadState.stepLocation,
+ threadState.targetStepIn)) {
context.getStepResultManager().removeMethodResult(threadId);
threadState.pendingStepRequest = DebugUtility.createStepIntoRequest(thread,
context.getStepFilters().allowClasses,
@@ -438,15 +439,19 @@ private boolean shouldDoExtraStepInto(int originalStackDepth, Location originalL
return true;
}
- private boolean isSameLocation(Location original, Location current) {
+ private boolean isSameLocation(Location current, Location original, MethodInvocation targetStepIn) {
if (original == null || current == null) {
return false;
}
Method originalMethod = original.method();
Method currentMethod = current.method();
+ // if the lines doesn't match, check if the current line is still behind the
+ // target if a target exist. This handles where the target is part of a
+ // expression which is wrapped.
return originalMethod.equals(currentMethod)
- && original.lineNumber() == current.lineNumber();
+ && (original.lineNumber() == current.lineNumber()
+ || (targetStepIn != null && targetStepIn.lineEnd >= current.lineNumber()));
}
/**
diff --git a/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java
new file mode 100644
index 000000000..5cc6fa3d7
--- /dev/null
+++ b/com.microsoft.java.debug.core/src/test/java/com/microsoft/java/debug/core/BreakpointTest.java
@@ -0,0 +1,17 @@
+package com.microsoft.java.debug.core;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class BreakpointTest {
+ @Test
+ public void testToNoneGeneric() {
+ assertEquals("Ljava.util.List;", Breakpoint.toNoneGeneric("Ljava.util.List;"));
+ assertEquals("(Ljava/util/Map;)Ljava/util/Map;", Breakpoint.toNoneGeneric(
+ "(Ljava/util/Map;>;)Ljava/util/Map;>;"));
+ assertEquals("(Ljava/util/Map;)Ljava/util/Map;",
+ Breakpoint.toNoneGeneric(
+ "(Ljava/util/Map;Ljava/util/List;>;)Ljava/util/Map;Ljava/util/List;>;"));
+ }
+}
diff --git a/com.microsoft.java.debug.plugin/.classpath b/com.microsoft.java.debug.plugin/.classpath
index ca5f41e6f..b2be945c8 100644
--- a/com.microsoft.java.debug.plugin/.classpath
+++ b/com.microsoft.java.debug.plugin/.classpath
@@ -1,16 +1,16 @@
-
+
-
+
-
+
diff --git a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF
index 84a0ab36f..f2ceffb53 100644
--- a/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF
+++ b/com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Java Debug Server Plugin
Bundle-SymbolicName: com.microsoft.java.debug.plugin;singleton:=true
-Bundle-Version: 0.50.0
-Bundle-RequiredExecutionEnvironment: JavaSE-11
+Bundle-Version: 0.53.2
+Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: com.microsoft.java.debug.plugin.internal.JavaDebuggerServerPlugin
Bundle-Vendor: Microsoft
@@ -21,8 +21,8 @@ Require-Bundle: org.eclipse.core.runtime,
org.apache.commons.lang3,
org.eclipse.lsp4j,
com.google.guava
-Bundle-ClassPath: lib/commons-io-2.11.0.jar,
+Bundle-ClassPath: lib/commons-io-2.19.0.jar,
.,
lib/rxjava-2.2.21.jar,
lib/reactive-streams-1.0.4.jar,
- lib/com.microsoft.java.debug.core-0.50.0.jar
+ lib/com.microsoft.java.debug.core-0.53.2.jar
diff --git a/com.microsoft.java.debug.plugin/pom.xml b/com.microsoft.java.debug.plugin/pom.xml
index b895ffd28..2e2c0e6a8 100644
--- a/com.microsoft.java.debug.plugin/pom.xml
+++ b/com.microsoft.java.debug.plugin/pom.xml
@@ -5,7 +5,7 @@
com.microsoft.java
java-debug-parent
- 0.50.0
+ 0.53.2
com.microsoft.java.debug.plugin
eclipse-plugin
@@ -51,12 +51,12 @@
commons-io
commons-io
- 2.11.0
+ 2.19.0
com.microsoft.java
com.microsoft.java.debug.core
- 0.50.0
+ 0.53.2
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java
index b14afde83..97581242e 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/BreakpointLocationLocator.java
@@ -22,8 +22,8 @@ public class BreakpointLocationLocator
public BreakpointLocationLocator(CompilationUnit compilationUnit, int lineNumber,
boolean bindingsResolved,
- boolean bestMatch) {
- super(compilationUnit, lineNumber, bindingsResolved, bestMatch);
+ boolean bestMatch, int offset, int end) {
+ super(compilationUnit, lineNumber, bindingsResolved, bestMatch, offset, end);
}
@Override
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
index 3551ab2e6..24c65785d 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/AdvancedLaunchingConnector.java
@@ -195,7 +195,6 @@ private static String[] constructLaunchCommand(Map l
StringBuilder execString = new StringBuilder();
execString.append("\"" + javaHome + slash + "bin" + slash + javaExec + "\"");
- execString.append(" -Xdebug -Xnoagent -Djava.compiler=NONE");
execString.append(" -Xrunjdwp:transport=dt_socket,address=" + address + ",server=n,suspend=" + (suspend ? "y" : "n"));
if (javaOptions != null) {
execString.append(" " + javaOptions);
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java
index 9cb6fbbfa..245281816 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/Compile.java
@@ -14,6 +14,7 @@
package com.microsoft.java.debug.plugin.internal;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
@@ -28,14 +29,16 @@
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.ls.core.internal.BuildWorkspaceStatus;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
+import org.eclipse.jdt.ls.core.internal.handlers.BuildWorkspaceHandler;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.lsp4j.extended.ProjectBuildParams;
import com.microsoft.java.debug.core.Configuration;
@@ -45,20 +48,12 @@ public class Compile {
private static final int GRADLE_BS_COMPILATION_ERROR = 100;
public static Object compile(CompileParams params, IProgressMonitor monitor) {
- IProject mainProject = params == null ? null : ProjectUtils.getProject(params.getProjectName());
- if (mainProject == null) {
- try {
- // Q: is infer project by main class name necessary? perf impact?
- List javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(params.getMainClass());
- if (javaProjects.size() == 1) {
- mainProject = javaProjects.get(0).getProject();
- }
- } catch (CoreException e) {
- JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e);
- }
+ if (params == null) {
+ throw new IllegalArgumentException("The compile parameters should not be null.");
}
- if (isBspProject(mainProject) && !ProjectUtils.isGradleProject(mainProject)) {
+ IProject mainProject = JdtUtils.getMainProject(params.getProjectName(), params.getMainClass());
+ if (JdtUtils.isBspProject(mainProject) && !ProjectUtils.isGradleProject(mainProject)) {
// Just need to trigger a build for the target project, the Gradle build server will
// handle the build dependencies for us.
try {
@@ -78,20 +73,37 @@ public static Object compile(CompileParams params, IProgressMonitor monitor) {
return BuildWorkspaceStatus.SUCCEED;
}
- try {
- if (monitor.isCanceled()) {
- return BuildWorkspaceStatus.CANCELLED;
- }
+ if (monitor.isCanceled()) {
+ return BuildWorkspaceStatus.CANCELLED;
+ }
- long compileAt = System.currentTimeMillis();
- if (params != null && params.isFullBuild()) {
- ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, monitor);
- ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, monitor);
- } else {
- ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
+ ProjectBuildParams buildParams = new ProjectBuildParams();
+ List identifiers = new LinkedList<>();
+ buildParams.setFullBuild(params.isFullBuild);
+ for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
+ if (ProjectsManager.getDefaultProject().equals(javaProject.getProject())) {
+ continue;
}
- logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms");
+ // we only build project which is not a BSP project, in case that the compile request is triggered by
+ // HCR with auto-build disabled, the build for BSP projects will be triggered by JavaHotCodeReplaceProvider.
+ if (!JdtUtils.isBspProject(javaProject.getProject())) {
+ identifiers.add(new TextDocumentIdentifier(javaProject.getProject().getLocationURI().toString()));
+ }
+ }
+ if (identifiers.size() == 0) {
+ return BuildWorkspaceStatus.SUCCEED;
+ }
+ buildParams.setIdentifiers(identifiers);
+ long compileAt = System.currentTimeMillis();
+ BuildWorkspaceHandler buildWorkspaceHandler = new BuildWorkspaceHandler(JavaLanguageServerPlugin.getProjectsManager());
+ BuildWorkspaceStatus status = buildWorkspaceHandler.buildProjects(buildParams, monitor);
+ logger.info("Time cost for ECJ: " + (System.currentTimeMillis() - compileAt) + "ms");
+ if (status == BuildWorkspaceStatus.FAILED || status == BuildWorkspaceStatus.CANCELLED) {
+ return status;
+ }
+
+ try {
IResource currentResource = mainProject;
if (isUnmanagedFolder(mainProject) && StringUtils.isNotBlank(params.getMainClass())) {
IType mainType = ProjectUtils.getJavaProject(mainProject).findType(params.getMainClass());
@@ -135,17 +147,14 @@ public static Object compile(CompileParams params, IProgressMonitor monitor) {
}
}
- if (problemMarkers.isEmpty()) {
- return BuildWorkspaceStatus.SUCCEED;
+ if (!problemMarkers.isEmpty()) {
+ return BuildWorkspaceStatus.WITH_ERROR;
}
-
- return BuildWorkspaceStatus.WITH_ERROR;
} catch (CoreException e) {
- JavaLanguageServerPlugin.logException("Failed to build workspace.", e);
- return BuildWorkspaceStatus.FAILED;
- } catch (OperationCanceledException e) {
- return BuildWorkspaceStatus.CANCELLED;
+ JavaLanguageServerPlugin.log(e);
}
+
+ return BuildWorkspaceStatus.SUCCEED;
}
private static boolean isUnmanagedFolder(IProject project) {
@@ -153,11 +162,6 @@ private static boolean isUnmanagedFolder(IProject project) {
&& ProjectUtils.isJavaProject(project);
}
- private static boolean isBspProject(IProject project) {
- return project != null && ProjectUtils.isJavaProject(project)
- && ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature");
- }
-
private static IProject getDefaultProject() {
return getWorkspaceRoot().getProject(ProjectsManager.DEFAULT_PROJECT_NAME);
}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java
index 1df752f24..431c283f5 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/CompletionProposalRequestor.java
@@ -72,6 +72,8 @@ public final class CompletionProposalRequestor extends CompletionRequestor {
CompletionItemKind.Text);
// @formatter:on
+ private static boolean isFilterFailed = false;
+
/**
* Constructor.
* @param typeRoot ITypeRoot
@@ -321,7 +323,7 @@ private boolean isFiltered(CompletionProposal proposal) {
case CompletionProposal.JAVADOC_TYPE_REF:
case CompletionProposal.TYPE_REF: {
char[] declaringType = getDeclaringType(proposal);
- return declaringType != null && org.eclipse.jdt.ls.core.internal.contentassist.TypeFilter.isFiltered(declaringType);
+ return declaringType != null && isFiltered(declaringType);
}
default: // do nothing
}
@@ -332,6 +334,22 @@ private boolean isFiltered(CompletionProposal proposal) {
return false;
}
+ // Temp workaround for the completion error https://github.com/microsoft/java-debug/issues/534
+ private static boolean isFiltered(char[] fullTypeName) {
+ if (isFilterFailed) {
+ return false;
+ }
+
+ try {
+ return JavaLanguageServerPlugin.getInstance().getTypeFilter().filter(new String(fullTypeName));
+ } catch (NoSuchMethodError ex) {
+ isFilterFailed = true;
+ JavaLanguageServerPlugin.logException("isFiltered for the completion failed.", ex);
+ }
+
+ return false;
+ }
+
/**
* copied from
* org.eclipse.jdt.ui.text.java.CompletionProposalCollector.getDeclaringType(CompletionProposal)
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java
index 2bf905c89..3a2b2f3df 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java
@@ -34,16 +34,20 @@
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
@@ -55,6 +59,7 @@
import org.eclipse.jdt.core.util.ISourceAttribute;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.jdt.ls.core.internal.JobHelpers;
+import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
@@ -63,6 +68,7 @@
import com.microsoft.java.debug.core.IDebugSession;
import com.microsoft.java.debug.core.StackFrameUtility;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
+import com.microsoft.java.debug.core.adapter.Constants;
import com.microsoft.java.debug.core.adapter.ErrorCode;
import com.microsoft.java.debug.core.adapter.HotCodeReplaceEvent;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
@@ -104,6 +110,8 @@ public class JavaHotCodeReplaceProvider implements IHotCodeReplaceProvider, IRes
private List deltaClassNames = new ArrayList<>();
+ private String mainProjectName = "";
+
/**
* Visitor for resource deltas.
*/
@@ -269,6 +277,7 @@ public void initialize(IDebugAdapterContext context, Map options
}
this.context = context;
currentDebugSession = context.getDebugSession();
+ this.mainProjectName = ((String) options.get(Constants.PROJECT_NAME));
}
@Override
@@ -319,6 +328,7 @@ public void onClassRedefined(Consumer> consumer) {
@Override
public CompletableFuture> redefineClasses() {
+ triggerBuildForBspProject();
JobHelpers.waitForBuildJobs(10 * 1000);
return CompletableFuture.supplyAsync(() -> {
List classNames = new ArrayList<>();
@@ -737,4 +747,39 @@ private List getStackFrames(ThreadReference thread, boolean refresh)
}
});
}
+
+ /**
+ * Trigger build separately if the main project is a BSP project.
+ * This is because auto build for BSP project will not update the class files to disk.
+ */
+ private void triggerBuildForBspProject() {
+ // check if the workspace contains BSP project first. This is for performance consideration.
+ // Due to that getJavaProjectFromType() is a heavy operation.
+ if (!containsBspProjects()) {
+ return;
+ }
+
+ IProject mainProject = JdtUtils.getMainProject(this.mainProjectName, context.getMainClass());
+ if (mainProject != null && JdtUtils.isBspProject(mainProject)) {
+ try {
+ ResourcesPlugin.getWorkspace().build(
+ new IBuildConfiguration[]{mainProject.getActiveBuildConfig()},
+ IncrementalProjectBuilder.INCREMENTAL_BUILD,
+ false /*buildReference*/,
+ new NullProgressMonitor()
+ );
+ } catch (CoreException e) {
+ // ignore compilation errors
+ }
+ }
+ }
+
+ private boolean containsBspProjects() {
+ for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
+ if (JdtUtils.isBspProject(javaProject.getProject())) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java
index f0c8795cc..5652b922e 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java
@@ -20,11 +20,14 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.logging.Level;
@@ -32,6 +35,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
@@ -72,8 +76,8 @@
import com.microsoft.java.debug.core.Configuration;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.DebugSettings;
-import com.microsoft.java.debug.core.JavaBreakpointLocation;
import com.microsoft.java.debug.core.DebugSettings.Switch;
+import com.microsoft.java.debug.core.JavaBreakpointLocation;
import com.microsoft.java.debug.core.adapter.AdapterUtils;
import com.microsoft.java.debug.core.adapter.Constants;
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
@@ -85,6 +89,9 @@ public class JdtSourceLookUpProvider implements ISourceLookUpProvider {
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
private static final String JDT_SCHEME = "jdt";
private static final String PATH_SEPARATOR = "/";
+ private static final Set IMPLICITLY_DECLARED_CLASSES = new HashSet<>(
+ Arrays.asList("org.eclipse.jdt.core.dom.UnnamedClass",
+ "org.eclipse.jdt.core.dom.ImplicitTypeDeclaration"));
private ISourceContainer[] sourceContainers = null;
private HashMap options = new HashMap();
@@ -172,6 +179,14 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
.map(sourceBreakpoint -> new JavaBreakpointLocation(sourceBreakpoint.line, sourceBreakpoint.column))
.toArray(JavaBreakpointLocation[]::new);
if (astUnit != null) {
+ List> types = astUnit.types();
+ String unnamedClass = null;
+ // See https://github.com/eclipse-jdt/eclipse.jdt.core/pull/2220
+ // Given that the JDT plans to rename UnamedClass to ImplicitTypeDeclaration, we will check
+ // the class name of the ASTNode to prevent the potential breaking in the future.
+ if (types.size() == 1 && IMPLICITLY_DECLARED_CLASSES.contains(types.get(0).getClass().getName())) {
+ unnamedClass = inferPrimaryTypeName(sourceUri, astUnit);
+ }
Map resolvedLocations = new HashMap<>();
for (JavaBreakpointLocation sourceLocation : sourceLocations) {
int sourceLine = sourceLocation.lineNumber();
@@ -210,8 +225,11 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
// mark it as "unverified".
// In future, we could consider supporting to update the breakpoint to a valid
// location.
+
+ // passing the offset to the constructor, it can recognize the multiline lambda
+ // expression well
BreakpointLocationLocator locator = new BreakpointLocationLocator(astUnit,
- sourceLine, true, true);
+ sourceLine, true, true, astUnit.getPosition(sourceLine, 0), 0);
astUnit.accept(locator);
// When the final valid line location is same as the original line, that
// represents it's a valid breakpoint.
@@ -219,7 +237,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
// be hit in current implementation.
if (sourceLine == locator.getLineLocation()
&& locator.getLocationType() == BreakpointLocationLocator.LOCATION_LINE) {
- sourceLocation.setClassName(locator.getFullyQualifiedTypeName());
+ sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass);
if (resolvedLocations.containsKey(sourceLine)) {
sourceLocation.setAvailableBreakpointLocations(resolvedLocations.get(sourceLine));
} else {
@@ -228,7 +246,7 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
resolvedLocations.put(sourceLine, inlineLocations);
}
} else if (locator.getLocationType() == BreakpointLocationLocator.LOCATION_METHOD) {
- sourceLocation.setClassName(locator.getFullyQualifiedTypeName());
+ sourceLocation.setClassName(StringUtils.isBlank(unnamedClass) ? locator.getFullyQualifiedTypeName() : unnamedClass);
sourceLocation.setMethodName(locator.getMethodName());
sourceLocation.setMethodSignature(locator.getMethodSignature());
}
@@ -238,6 +256,27 @@ public JavaBreakpointLocation[] getBreakpointLocations(String sourceUri, SourceB
return sourceLocations;
}
+ private String inferPrimaryTypeName(String uri, CompilationUnit astUnit) {
+ String fileName = "";
+ String filePath = AdapterUtils.toPath(uri);
+ if (filePath != null && Files.isRegularFile(Paths.get(filePath))) {
+ fileName = Paths.get(filePath).getFileName().toString();
+ } else if (astUnit.getTypeRoot() != null) {
+ fileName = astUnit.getTypeRoot().getElementName();
+ }
+
+ if (StringUtils.isNotBlank(fileName)) {
+ String[] extensions = JavaCore.getJavaLikeExtensions();
+ for (String extension : extensions) {
+ if (fileName.endsWith("." + extension)) {
+ return fileName.substring(0, fileName.length() - 1 - extension.length());
+ }
+ }
+ }
+
+ return fileName;
+ }
+
private BreakpointLocation[] getInlineBreakpointLocations(final CompilationUnit astUnit, int sourceLine) {
List locations = new ArrayList<>();
// The starting position of each line is the default breakpoint location for
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java
index a4c06c6d4..66562ae74 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java
@@ -40,6 +40,8 @@
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer;
import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer;
+import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
+import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.StackFrameUtility;
@@ -415,4 +417,36 @@ public static boolean isSameFile(IResource resource1, IResource resource2) {
return Objects.equals(resource1.getLocation(), resource2.getLocation());
}
+
+ /**
+ * Check if the project is managed by Gradle Build Server.
+ */
+ public static boolean isBspProject(IProject project) {
+ return project != null && ProjectUtils.isJavaProject(project)
+ && ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature");
+ }
+
+ /**
+ * Get main project according to the main project name or main class name,
+ * or return null if the main project cannot be resolved.
+ */
+ public static IProject getMainProject(String mainProjectName, String mainClassName) {
+ IProject mainProject = null;
+ if (StringUtils.isNotBlank(mainProjectName)) {
+ mainProject = ProjectUtils.getProject(mainProjectName);
+ }
+
+ if (mainProject == null && StringUtils.isNotBlank(mainClassName)) {
+ try {
+ List javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(mainClassName);
+ if (javaProjects.size() == 1) {
+ mainProject = javaProjects.get(0).getProject();
+ }
+ } catch (CoreException e) {
+ JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e);
+ }
+ }
+
+ return mainProject;
+ }
}
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java
index a25fbc3f6..654cbb00b 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/MethodInvocationLocator.java
@@ -217,7 +217,7 @@ public boolean visit(ClassInstanceCreation node) {
private boolean shouldVisitNode(ASTNode node) {
int start = unit.getLineNumber(node.getStartPosition());
- int end = unit.getLineNumber(node.getStartPosition() + node.getLength());
+ int end = unit.getLineNumber(node.getStartPosition() + node.getLength() - 1);
if (line >= start && line <= end) {
return true;
diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java
index f70d0c044..fc8189445 100644
--- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java
+++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java
@@ -35,6 +35,7 @@
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
@@ -43,6 +44,7 @@
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
+import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
@@ -99,10 +101,19 @@ private List resolveMainClassCore(List