File: JavaLaunchConfiguration.cs
Web Access
Project: src\src\Aspire.Hosting.Java\Aspire.Hosting.Java.csproj (Aspire.Hosting.Java)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.Json.Serialization;
using Aspire.Hosting.ApplicationModel;
 
#pragma warning disable ASPIREEXTENSION001 // Launch configuration types are experimental.
 
namespace Aspire.Hosting.Java;
 
/// <summary>
/// The launch configuration handed to an IDE so it can run or debug a Java application itself,
/// rather than Aspire starting the process.
/// </summary>
/// <remarks>
/// <para>
/// The IDE always starts a JVM directly, even when the resource would otherwise be launched through
/// <c>mvnw spring-boot:run</c> or <c>gradlew bootRun</c>. Those wrappers fork a second JVM, so a debugger
/// attached to the wrapper process would never see application code. The build-tool invocation is declared
/// with <c>WithLaunchToolArgs(..., ownedByLaunchConfigurationType: "java")</c>, which drops those arguments
/// for exactly this launch configuration type while keeping them for normal process execution.
/// </para>
/// <para>
/// The property names match the <c>vscjava.vscode-java-debug</c> launch schema after the extension
/// translates them, and mirror the keys consumed by <c>extension/src/debugger/languages/java.ts</c>.
/// See https://github.com/microsoft/vscode-java-debug/blob/main/Configuration.md.
/// </para>
/// </remarks>
internal sealed class JavaLaunchConfiguration() : ExecutableLaunchConfiguration("java")
{
    /// <summary>
    /// The debug request type. Java applications are always launched by the IDE, never attached to.
    /// </summary>
    [JsonPropertyName("request")]
    public string Request { get; set; } = "launch";
 
    /// <summary>
    /// The working directory of the Java project. The IDE uses it as the debug session's working
    /// directory and to scope main class resolution to this resource's project.
    /// </summary>
    [JsonPropertyName("working_directory")]
    public string WorkingDirectory { get; set; } = string.Empty;
 
    /// <summary>
    /// The fully qualified main class, optionally prefixed with a Java module name
    /// (<c>[module/]com.example.App</c>), or the path of the <c>.java</c> source file declaring
    /// <c>main</c>. When omitted the IDE resolves the main class itself, scoped to
    /// <see cref="ProjectName"/> when one was supplied.
    /// </summary>
    /// <remarks>
    /// A JAR path is deliberately not accepted here. The debug adapter documents this attribute as
    /// "the fully qualified class name (e.g. [java module name/]com.xyz.MainApp) or the java file path
    /// of the program entry", so it never opens an archive to read <c>Main-Class</c>. An executable JAR
    /// belongs in <see cref="ClassPaths"/> with its manifest's <c>Main-Class</c> sent here.
    /// </remarks>
    [JsonPropertyName("main_class")]
    public string? MainClass { get; set; }
 
    /// <summary>
    /// The name the IDE's Java tooling imported this resource's project under, used to scope main class
    /// resolution.
    /// </summary>
    /// <remarks>
    /// Sent whether or not <see cref="MainClass"/> is known, because the adapter needs the scope in
    /// both cases. Without a main class it searches every project in the workspace, finds one in each
    /// Java resource, and prompts the user to choose on every launch. With a main class it still
    /// searches the whole workspace, and fails the launch with "Main class ... isn't unique in the
    /// workspace" if the class is visible through more than one project. Scoping the search to a single
    /// project resolves both.
    /// </remarks>
    [JsonPropertyName("project_name")]
    public string? ProjectName { get; set; }
 
    /// <summary>
    /// Classpath entries the IDE should launch the JVM with. Empty when the IDE should resolve the
    /// classpath from the project itself, which is the normal case for a Maven or Gradle project.
    /// </summary>
    /// <remarks>
    /// This is how a prebuilt JAR is debugged: the archive goes on the classpath and its manifest's
    /// <c>Main-Class</c> becomes <see cref="MainClass"/>. Without it the adapter would fall back to
    /// resolving the class from a language-server project that does not contain the JAR's classes.
    /// </remarks>
    [JsonPropertyName("class_paths")]
    public string[]? ClassPaths { get; set; }
 
    /// <summary>
    /// The build tool that owns the project, used by the IDE to refresh the project's classpath
    /// before starting a session. One of <c>maven</c>, <c>gradle</c>, or <see langword="null"/> when the
    /// application runs from a prebuilt JAR.
    /// </summary>
    [JsonPropertyName("build_tool")]
    public string? BuildTool { get; set; }
}