Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Separated JDK management code into its own module
Browse files Browse the repository at this point in the history
Fixes #1857
quintesse committed Nov 27, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 6189150 commit afc114d
Showing 43 changed files with 2,629 additions and 1,624 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -90,6 +90,8 @@ sourceSets {
sourceSets.main.compileClasspath += sourceSets.java9.output.classesDirs;

dependencies {
implementation project(':jdkmanager')

implementation 'com.offbytwo:docopt:0.6.0.20150202'

implementation 'org.apache.commons:commons-text:1.11.0'
22 changes: 22 additions & 0 deletions jdkmanager/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.classpath
.project
.vscode
.settings
target
.idea
*.iml
/build
.gradle
.factorypath
bin
homebrew-tap
RESULTS
*.db
jbang-action
out
node_modules
package-lock.json
*.jfr
itests/hello.java
*.class
CHANGELOG.md
31 changes: 31 additions & 0 deletions jdkmanager/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
id 'java'
}

group = 'dev.jbang.jvm'
version = '0.1.0'

sourceCompatibility = '8'
targetCompatibility = '8'

repositories {
mavenCentral()
}

dependencies {
implementation 'org.apache.commons:commons-compress:1.26.2'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'org.apache.httpcomponents:httpclient-cache:4.5.14'
implementation 'com.google.code.gson:gson:2.11.0'

implementation "org.slf4j:slf4j-nop:1.7.30"
implementation "org.slf4j:jcl-over-slf4j:1.7.30"
implementation "org.jspecify:jspecify:1.0.0"

testImplementation platform('org.junit:junit-bom:5.10.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
}

test {
useJUnitPlatform()
}
122 changes: 122 additions & 0 deletions jdkmanager/src/main/java/dev/jbang/jvm/Jdk.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package dev.jbang.jvm;

import java.nio.file.Path;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import dev.jbang.jvm.util.JavaUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

public interface Jdk extends Comparable<Jdk> {
@NonNull JdkProvider getProvider();

@NonNull String getId();

@NonNull String getVersion();

@Nullable Path getHome();

int getMajorVersion();

@NonNull Jdk install();

void uninstall();

boolean isInstalled();

class Default implements Jdk {
@NonNull private final transient JdkProvider provider;
@NonNull private final String id;
@NonNull private final String version;
@Nullable private final Path home;
@NonNull private final Set<String> tags = new HashSet<>();

Default(
@NonNull JdkProvider provider,
@NonNull String id,
@Nullable Path home,
@NonNull String version,
@NonNull String... tags) {
this.provider = provider;
this.id = id;
this.version = version;
this.home = home;
}

@Override
@NonNull
public JdkProvider getProvider() {
return provider;
}

/** Returns the id that is used to uniquely identify this JDK across all providers */
@Override
@NonNull
public String getId() {
return id;
}

/** Returns the JDK's version */
@Override
@NonNull
public String getVersion() {
return version;
}

/**
* The path to where the JDK is installed. Can be <code>null</code> which means the JDK
* isn't currently installed by that provider
*/
@Override
@Nullable
public Path getHome() {
return home;
}

@Override
public int getMajorVersion() {
return JavaUtils.parseJavaVersion(getVersion());
}

@Override
@NonNull
public Jdk install() {
return provider.install(this);
}

@Override
public void uninstall() {
provider.uninstall(this);
}

@Override
public boolean isInstalled() {
return home != null;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Default jdk = (Default) o;
return id.equals(jdk.id) && Objects.equals(home, jdk.home);
}

@Override
public int hashCode() {
return Objects.hash(home, id);
}

@Override
public int compareTo(Jdk o) {
return Integer.compare(getMajorVersion(), o.getMajorVersion());
}

@Override
public String toString() {
return getMajorVersion() + " (" + version + ", " + id + ", " + home + ")";
}
}
}
516 changes: 516 additions & 0 deletions jdkmanager/src/main/java/dev/jbang/jvm/JdkManager.java

Large diffs are not rendered by default.

180 changes: 180 additions & 0 deletions jdkmanager/src/main/java/dev/jbang/jvm/JdkProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package dev.jbang.jvm;

import java.nio.file.Path;
import java.util.*;

import dev.jbang.jvm.util.JavaUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
* This interface must be implemented by providers that are able to give access to JDKs installed on
* the user's system. Some providers will also be able to manage those JDKs by installing and
* uninstalling them at the user's request. In those cases the <code>canUpdate()</code> should
* return <code>true</code>.
*
* <p>The providers deal in JDK identifiers, not in versions. Those identifiers are specific to the
* implementation but should follow two important rules: 1. they must be unique across
* implementations 2. they must start with an integer specifying the main JDK version
*/
public interface JdkProvider {

default Jdk createJdk(@NonNull String id, @Nullable Path home, @NonNull String version) {
return new Jdk.Default(this, id, home, version);
}

default String name() {
String nm = getClass().getSimpleName();
return nm.substring(0, nm.length() - 11).toLowerCase();
}

/**
* For providers that can update this returns a set of JDKs that are available for installation.
* Providers might set the <code>home</code> field of the JDK objects if the respective JDK is
* currently installed on the user's system, but only if they can ensure that it's the exact
* same version, otherwise they should just leave the field <code>null</code>.
*
* @return List of <code>Jdk</code> objects
*/
@NonNull
default List<Jdk> listAvailable() {
throw new UnsupportedOperationException(
"Listing available JDKs is not supported by " + getClass().getName());
}

/**
* Returns a set of JDKs that are currently installed on the user's system.
*
* @return List of <code>Jdk</code> objects, possibly empty
*/
@NonNull List<Jdk> listInstalled();

/**
* Determines if a JDK of the requested version is currently installed by this provider and if
* so returns its respective <code>Jdk</code> object, otherwise it returns <code>null</code>. If
* <code>openVersion</code> is set to true the method will also return the next installed
* version if the exact version was not found.
*
* @param version The specific JDK version to return
* @param openVersion Return newer version if exact is not available
* @return A <code>Jdk</code> object or <code>null</code>
*/
@Nullable
default Jdk getJdkByVersion(int version, boolean openVersion) {
List<Jdk> jdks = listInstalled();
Jdk res;
if (openVersion) {
res =
jdks.stream()
.sorted()
.filter(jdk -> jdk.getMajorVersion() >= version)
.findFirst()
.orElse(null);
} else {
res =
jdks.stream()
.filter(jdk -> jdk.getMajorVersion() == version)
.findFirst()
.orElse(null);
}
return res;
}

/**
* Determines if the given id refers to a JDK managed by this provider and if so returns its
* respective <code>Jdk</code> object, otherwise it returns <code>null</code>.
*
* @param id The id to look for
* @return A <code>Jdk</code> object or <code>null</code>
*/
@Nullable Jdk getJdkById(@NonNull String id);

/**
* Determines if the given path belongs to a JDK managed by this provider and if so returns its
* respective <code>Jdk</code> object, otherwise it returns <code>null</code>.
*
* @param jdkPath The path to look for
* @return A <code>Jdk</code> object or <code>null</code>
*/
@Nullable Jdk getJdkByPath(@NonNull Path jdkPath);

/**
* For providers that can update this installs the indicated JDK
*
* @param jdk The <code>Jdk</code> object of the JDK to install
* @return A <code>Jdk</code> object
* @throws UnsupportedOperationException if the provider can not update
*/
@NonNull
default Jdk install(@NonNull Jdk jdk) {
throw new UnsupportedOperationException(
"Installing a JDK is not supported by " + getClass().getName());
}

/**
* Uninstalls the indicated JDK
*
* @param jdk The <code>Jdk</code> object of the JDK to uninstall
* @throws UnsupportedOperationException if the provider can not update
*/
default void uninstall(@NonNull Jdk jdk) {
throw new UnsupportedOperationException(
"Uninstalling a JDK is not supported by " + getClass().getName());
}

/**
* Indicates if the provider can be used or not. This can perform sanity checks like the
* availability of certain package being installed on the system or even if the system is
* running a supported operating system.
*
* @return True if the provider can be used, false otherwise
*/
default boolean canUse() {
return true;
}

/**
* Indicates if the provider is able to (un)install JDKs or not
*
* @return True if JDKs can be (un)installed, false otherwise
*/
default boolean canUpdate() {
return false;
}

/**
* This is a special "dummy" provider that can be used to create <code>Jdk</code> objects for
* JDKs that don't seem to belong to any of the known providers but for which we still want an
* object to represent them.
*/
class UnknownJdkProvider implements JdkProvider {
private static final UnknownJdkProvider instance = new UnknownJdkProvider();

@NonNull
@Override
public List<Jdk> listInstalled() {
return Collections.emptyList();
}

@Nullable
@Override
public Jdk getJdkById(@NonNull String id) {
return null;
}

@Nullable
@Override
public Jdk getJdkByPath(@NonNull Path jdkPath) {
Optional<String> version = JavaUtils.resolveJavaVersionStringFromPath(jdkPath);
if (version.isPresent()) {
return createJdk("unknown", jdkPath, version.get());
} else {
return null;
}
}

public static Jdk createJdk(Path jdkPath) {
return instance.getJdkByPath(jdkPath);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package dev.jbang.jvm.jdkproviders;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.JdkProvider;
import dev.jbang.jvm.util.JavaUtils;
import dev.jbang.jvm.util.OsUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

public abstract class BaseFoldersJdkProvider implements JdkProvider {
protected final Path jdksRoot;

private static final Logger LOGGER = Logger.getLogger(BaseFoldersJdkProvider.class.getName());

protected BaseFoldersJdkProvider(Path jdksRoot) {
this.jdksRoot = jdksRoot;
}

@NonNull
@Override
public List<Jdk> listInstalled() {
if (Files.isDirectory(jdksRoot)) {
try (Stream<Path> jdkPaths = listJdkPaths()) {
return jdkPaths.map(this::createJdk)
.filter(Objects::nonNull)
.sorted(Jdk::compareTo)
.collect(Collectors.toList());
} catch (IOException e) {
LOGGER.log(Level.FINE, "Couldn't list installed JDKs", e);
}
}
return Collections.emptyList();
}

@Nullable
@Override
public Jdk getJdkById(@NonNull String id) {
if (isValidId(id)) {
try (Stream<Path> jdkPaths = listJdkPaths()) {
return jdkPaths.filter(p -> jdkId(p.getFileName().toString()).equals(id))
.map(this::createJdk)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
} catch (IOException e) {
LOGGER.log(Level.FINE, "Couldn't list installed JDKs", e);
}
}
return null;
}

@Nullable
@Override
public Jdk getJdkByPath(@NonNull Path jdkPath) {
if (jdkPath.startsWith(jdksRoot)) {
try (Stream<Path> jdkPaths = listJdkPaths()) {
return jdkPaths.filter(jdkPath::startsWith)
.map(this::createJdk)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
} catch (IOException e) {
LOGGER.log(Level.FINE, "Couldn't list installed JDKs", e);
}
}
return null;
}

/**
* Returns a path to the requested JDK. This method should never return <code>null</code> and
* should return the path where the requested JDK is either currently installed or where it
* would be installed if it were available. This only needs to be implemented for providers that
* are updatable.
*
* @param jdk The identifier of the JDK to install
* @return A path to the requested JDK
*/
@NonNull
protected Path getJdkPath(@NonNull String jdk) {
return jdksRoot.resolve(jdk);
}

private Predicate<Path> sameJdk(Path jdkRoot) {
Path release = jdkRoot.resolve("release");
return (Path p) -> {
try {
return Files.isSameFile(p.resolve("release"), release);
} catch (IOException e) {
return false;
}
};
}

protected Stream<Path> listJdkPaths() throws IOException {
if (Files.isDirectory(jdksRoot)) {
return Files.list(jdksRoot);
}
return Stream.empty();
}

@Nullable
protected Jdk createJdk(Path home) {
String name = home.getFileName().toString();
Optional<String> version = JavaUtils.resolveJavaVersionStringFromPath(home);
if (version.isPresent()) {
return createJdk(jdkId(name), home, version.get());
}
return null;
}

protected boolean acceptFolder(Path jdkFolder) {
return OsUtils.searchPath("javac", jdkFolder.resolve("bin").toString()) != null;
}

protected boolean isValidId(String id) {
return id.endsWith("-" + name());
}

protected abstract String jdkId(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dev.jbang.jvm.jdkproviders;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.JdkProvider;
import dev.jbang.jvm.util.JavaUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
* This JDK provider returns the "current" JDK, which is the JDK that is currently being used to run
* JBang.
*/
public class CurrentJdkProvider implements JdkProvider {
@NonNull
@Override
public List<Jdk> listInstalled() {
String jh = System.getProperty("java.home");
if (jh != null) {
Path jdkHome = Paths.get(jh);
jdkHome = JavaUtils.jre2jdk(jdkHome);
Optional<String> version = JavaUtils.resolveJavaVersionStringFromPath(jdkHome);
if (version.isPresent()) {
String id = "current";
return Collections.singletonList(createJdk(id, jdkHome, version.get()));
}
}
return Collections.emptyList();
}

@Nullable
@Override
public Jdk getJdkById(@NonNull String id) {
if (id.equals(name())) {
List<Jdk> l = listInstalled();
if (!l.isEmpty()) {
return l.get(0);
}
}
return null;
}

@Nullable
@Override
public Jdk getJdkByPath(@NonNull Path jdkPath) {
List<Jdk> installed = listInstalled();
Jdk def = !installed.isEmpty() ? installed.get(0) : null;
return def != null && def.getHome() != null && jdkPath.startsWith(def.getHome())
? def
: null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package dev.jbang.jvm.jdkproviders;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.JdkProvider;
import dev.jbang.jvm.util.FileUtils;
import dev.jbang.jvm.util.JavaUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
* This JDK provider returns the "default" JDK if it was set (using <code>jbang jdk default</code>).
*/
public class DefaultJdkProvider implements JdkProvider {
@NonNull private final Path defaultJdkLink;

public static final String DEFAULT_ID = "default";

public DefaultJdkProvider(@NonNull Path defaultJdkLink) {
this.defaultJdkLink = defaultJdkLink;
}

@NonNull
@Override
public List<Jdk> listInstalled() {
if (Files.isDirectory(defaultJdkLink)) {
Optional<String> version = JavaUtils.resolveJavaVersionStringFromPath(defaultJdkLink);
if (version.isPresent()) {
return Collections.singletonList(createJdk(DEFAULT_ID, defaultJdkLink, version.get()));
}
}
return Collections.emptyList();
}

@Nullable
@Override
public Jdk getJdkById(@NonNull String id) {
if (id.equals(DEFAULT_ID)) {
List<Jdk> l = listInstalled();
if (!l.isEmpty()) {
return l.get(0);
}
}
return null;
}

@Nullable
@Override
public Jdk getJdkByPath(@NonNull Path jdkPath) {
List<Jdk> installed = listInstalled();
Jdk def = !installed.isEmpty() ? installed.get(0) : null;
return def != null && def.getHome() != null && jdkPath.startsWith(def.getHome())
? def
: null;
}

@Override
public @NonNull Jdk install(@NonNull Jdk jdk) {
Jdk defJdk = getJdkById(DEFAULT_ID);
if (defJdk != null && defJdk.isInstalled() && !jdk.equals(defJdk)) {
uninstall(defJdk);
}
FileUtils.createLink(defaultJdkLink, jdk.getHome());
return defJdk;
}

@Override
public void uninstall(@NonNull Jdk jdk) {
FileUtils.deletePath(defaultJdkLink);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
package dev.jbang.jvm.jdkproviders;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.util.*;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
* JVM's main JDK provider that can download and install the JDKs provided by the Foojay Disco API.
* They get installed in JBang's cache folder.
*/
public class FoojayJdkProvider extends BaseFoldersJdkProvider {
private static final String FOOJAY_JDK_DOWNLOAD_URL =
"https://api.foojay.io/disco/v3.0/directuris?";
private static final String FOOJAY_JDK_VERSIONS_URL =
"https://api.foojay.io/disco/v3.0/packages?";

private static final Logger LOGGER = Logger.getLogger(FoojayJdkProvider.class.getName());

private static class JdkResult {
String java_version;
int major_version;
String release_status;
}

private static class VersionsResponse {
List<JdkResult> result;
}

public FoojayJdkProvider(Path jdksPath) {
super(jdksPath);
}

@NonNull
@Override
public List<Jdk> listAvailable() {
try {
List<Jdk> result = new ArrayList<>();
Consumer<String> addJdk =
version -> {
result.add(createJdk(jdkId(version), null, version));
};
String distro = getVendor();
if (distro == null) {
VersionsResponse res =
NetUtils.readJsonFromUrl(
getVersionsUrl(OsUtils.getOS(), OsUtils.getArch(), "temurin"),
VersionsResponse.class);
filterEA(res.result).forEach(jdk -> addJdk.accept(jdk.java_version));
res =
NetUtils.readJsonFromUrl(
getVersionsUrl(OsUtils.getOS(), OsUtils.getArch(), "aoj"),
VersionsResponse.class);
filterEA(res.result).forEach(jdk -> addJdk.accept(jdk.java_version));
} else {
VersionsResponse res =
NetUtils.readJsonFromUrl(
getVersionsUrl(OsUtils.getOS(), OsUtils.getArch(), distro),
VersionsResponse.class);
filterEA(res.result).forEach(jdk -> addJdk.accept(jdk.java_version));
}
result.sort(Jdk::compareTo);
return Collections.unmodifiableList(result);
} catch (IOException e) {
LOGGER.log(Level.FINE, "Couldn't list available JDKs", e);
}
return Collections.emptyList();
}

// Filter out any EA releases for which a GA with
// the same major version exists
private List<JdkResult> filterEA(List<JdkResult> jdks) {
Set<Integer> GAs =
jdks.stream()
.filter(jdk -> jdk.release_status.equals("ga"))
.map(jdk -> jdk.major_version)
.collect(Collectors.toSet());

JdkResult[] lastJdk = new JdkResult[] {null};
return jdks.stream()
.filter(
jdk -> {
if (lastJdk[0] == null
|| lastJdk[0].major_version != jdk.major_version
&& (jdk.release_status.equals("ga")
|| !GAs.contains(jdk.major_version))) {
lastJdk[0] = jdk;
return true;
} else {
return false;
}
})
.collect(Collectors.toList());
}

@Nullable
@Override
public Jdk getJdkByVersion(int version, boolean openVersion) {
Path jdk = jdksRoot.resolve(Integer.toString(version));
if (Files.isDirectory(jdk)) {
return createJdk(jdk);
} else if (openVersion) {
return super.getJdkByVersion(version, true);
}
return null;
}

@NonNull
@Override
public Jdk install(@NonNull Jdk jdk) {
int version = jdkVersion(jdk.getId());
LOGGER.log(
Level.INFO,
"Downloading JDK {0}. Be patient, this can take several minutes...",
version);
String url = getDownloadUrl(version, OsUtils.getOS(), OsUtils.getArch(), getVendor());
LOGGER.log(Level.FINE, "Downloading {0}", url);
Path jdkDir = getJdkPath(jdk.getId());
Path jdkTmpDir = jdkDir.getParent().resolve(jdkDir.getFileName() + ".tmp");
Path jdkOldDir = jdkDir.getParent().resolve(jdkDir.getFileName() + ".old");
FileUtils.deletePath(jdkTmpDir);
FileUtils.deletePath(jdkOldDir);
try {
Path jdkPkg = NetUtils.downloadFromUrl(url);
LOGGER.log(Level.INFO, "Installing JDK {0}...", version);
LOGGER.log(Level.FINE, "Unpacking to {0}", jdkDir);
UnpackUtils.unpackJdk(jdkPkg, jdkTmpDir);
if (Files.isDirectory(jdkDir)) {
Files.move(jdkDir, jdkOldDir);
} else if (Files.isSymbolicLink(jdkDir)) {
// This means we have a broken/invalid link
FileUtils.deletePath(jdkDir);
}
Files.move(jdkTmpDir, jdkDir);
FileUtils.deletePath(jdkOldDir);
Optional<String> fullVersion = JavaUtils.resolveJavaVersionStringFromPath(jdkDir);
if (!fullVersion.isPresent()) {
throw new IllegalStateException("Cannot obtain version of recently installed JDK");
}
return createJdk(jdk.getId(), jdkDir, fullVersion.get());
} catch (Exception e) {
FileUtils.deletePath(jdkTmpDir);
if (!Files.isDirectory(jdkDir) && Files.isDirectory(jdkOldDir)) {
try {
Files.move(jdkOldDir, jdkDir);
} catch (IOException ex) {
// Ignore
}
}
String msg = "Required Java version not possible to download or install.";
/*
Jdk defjdk = JdkManager.getJdk(null, false);
if (defjdk != null) {
msg +=
" You can run with '--java "
+ defjdk.getMajorVersion()
+ "' to force using the default installed Java.";
}
*/
LOGGER.log(Level.FINE, msg);
throw new IllegalStateException(
"Unable to download or install JDK version " + version, e);
}
}

@Override
public void uninstall(@NonNull Jdk jdk) {
Path jdkDir = getJdkPath(jdk.getId());
FileUtils.deletePath(jdkDir);
}

@NonNull
@Override
protected Path getJdkPath(@NonNull String jdk) {
return getJdksPath().resolve(Integer.toString(jdkVersion(jdk)));
}

@Override
public boolean canUpdate() {
return true;
}

private static String getDownloadUrl(
int version, OsUtils.OS os, OsUtils.Arch arch, String distro) {
return FOOJAY_JDK_DOWNLOAD_URL + getUrlParams(version, os, arch, distro);
}

private static String getVersionsUrl(OsUtils.OS os, OsUtils.Arch arch, String distro) {
return FOOJAY_JDK_VERSIONS_URL + getUrlParams(null, os, arch, distro);
}

private static String getUrlParams(
Integer version, OsUtils.OS os, OsUtils.Arch arch, String distro) {
Map<String, String> params = new HashMap<>();
if (version != null) {
params.put("version", String.valueOf(version));
}

if (distro == null) {
if (version == null || version == 8 || version == 11 || version >= 17) {
distro = "temurin";
} else {
distro = "aoj";
}
}
params.put("distro", distro);

String archiveType;
if (os == OsUtils.OS.windows) {
archiveType = "zip";
} else {
archiveType = "tar.gz";
}
params.put("archive_type", archiveType);

params.put("architecture", arch.name());
params.put("package_type", "jdk");
params.put("operating_system", os.name());

if (os == OsUtils.OS.windows) {
params.put("libc_type", "c_std_lib");
} else if (os == OsUtils.OS.mac) {
params.put("libc_type", "libc");
} else {
params.put("libc_type", "glibc");
}

params.put("javafx_bundled", "false");
params.put("latest", "available");
params.put("release_status", "ga,ea");
params.put("directly_downloadable", "true");

return urlEncodeUTF8(params);
}

static String urlEncodeUTF8(Map<?, ?> map) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(
String.format(
"%s=%s",
urlEncodeUTF8(entry.getKey().toString()),
urlEncodeUTF8(entry.getValue().toString())));
}
return sb.toString();
}

static String urlEncodeUTF8(String s) {
return URLEncoder.encode(s, StandardCharsets.UTF_8);
}

@NonNull
public Path getJdksPath() {
return jdksRoot;
}

@NonNull
@Override
protected String jdkId(String name) {
int majorVersion = JavaUtils.parseJavaVersion(name);
return majorVersion + "-jbang";
}

private static int jdkVersion(String jdk) {
return JavaUtils.parseJavaVersion(jdk);
}

// TODO refactor
private static String getVendor() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dev.jbang.jvm.jdkproviders;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.JdkProvider;
import dev.jbang.jvm.util.JavaUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
* This JDK provider detects if a JDK is already available on the system by looking at <code>
* JAVA_HOME</code> environment variable.
*/
public class JavaHomeJdkProvider implements JdkProvider {
@NonNull
@Override
public List<Jdk> listInstalled() {
Path jdkHome = JavaUtils.getJavaHomeEnv();
if (jdkHome != null && Files.isDirectory(jdkHome)) {
Optional<String> version = JavaUtils.resolveJavaVersionStringFromPath(jdkHome);
if (version.isPresent()) {
String id = "javahome";
return Collections.singletonList(createJdk(id, jdkHome, version.get()));
}
}
return Collections.emptyList();
}

@Nullable
@Override
public Jdk getJdkById(@NonNull String id) {
if (id.equals(name())) {
List<Jdk> l = listInstalled();
if (!l.isEmpty()) {
return l.get(0);
}
}
return null;
}

@Nullable
@Override
public Jdk getJdkByPath(@NonNull Path jdkPath) {
List<Jdk> installed = listInstalled();
Jdk def = !installed.isEmpty() ? installed.get(0) : null;
return def != null && def.getHome() != null && jdkPath.startsWith(def.getHome())
? def
: null;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package dev.jbang.net.jdkproviders;
package dev.jbang.jvm.jdkproviders;

import org.jspecify.annotations.NonNull;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
* This JDK provider is intended to detects JDKs that have been installed in
* standard location of the users linux distro.
@@ -22,13 +21,11 @@
public class LinuxJdkProvider extends BaseFoldersJdkProvider {
private static final Path JDKS_ROOT = Paths.get("/usr/lib/jvm");

@Nonnull
@Override
protected Path getJdksRoot() {
return JDKS_ROOT;
public LinuxJdkProvider() {
super(JDKS_ROOT);
}

@Nullable
@NonNull
@Override
protected String jdkId(String name) {
return name + "-linux";
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dev.jbang.jvm.jdkproviders;

import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.JdkProvider;
import dev.jbang.jvm.util.JavaUtils;
import dev.jbang.jvm.util.OsUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
* This JDK provider detects if a JDK is already available on the system by first looking at the
* user's <code>PATH</code>.
*/
public class PathJdkProvider implements JdkProvider {
@NonNull
@Override
public List<Jdk> listInstalled() {
Path jdkHome = null;
Path javac = OsUtils.searchPath("javac");
if (javac != null) {
javac = javac.toAbsolutePath();
jdkHome = javac.getParent().getParent();
}
if (jdkHome != null) {
Optional<String> version = JavaUtils.resolveJavaVersionStringFromPath(jdkHome);
if (version.isPresent()) {
String id = "path";
return Collections.singletonList(createJdk(id, jdkHome, version.get()));
}
}
return Collections.emptyList();
}

@Nullable
@Override
public Jdk getJdkById(@NonNull String id) {
if (id.equals(name())) {
List<Jdk> l = listInstalled();
if (!l.isEmpty()) {
return l.get(0);
}
}
return null;
}

@Nullable
@Override
public Jdk getJdkByPath(@NonNull Path jdkPath) {
List<Jdk> installed = listInstalled();
Jdk def = !installed.isEmpty() ? installed.get(0) : null;
return def != null && def.getHome() != null && jdkPath.startsWith(def.getHome())
? def
: null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package dev.jbang.jvm.jdkproviders;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.util.OsUtils;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
* This JDK provider detects any JDKs that have been installed using the Scoop package manager.
* Windows only.
*/
public class ScoopJdkProvider extends BaseFoldersJdkProvider {
private static final Path SCOOP_APPS =
Paths.get(System.getProperty("user.home")).resolve("scoop/apps");

public ScoopJdkProvider() {
super(SCOOP_APPS);
}

@NonNull
@Override
protected Stream<Path> listJdkPaths() throws IOException {
if (Files.isDirectory(jdksRoot)) {
try (Stream<Path> paths = Files.list(jdksRoot)) {
return paths.filter(p -> p.getFileName().startsWith("openjdk"))
.map(p -> p.resolve("current"));
}
}
return Stream.empty();
}

@Override
protected String jdkId(String name) {
return name + "-scoop";
}

@Nullable
@Override
protected Jdk createJdk(Path home) {
try {
// Try to resolve any links
home = home.toRealPath();
} catch (IOException e) {
throw new IllegalStateException("Couldn't resolve 'current' link: " + home, e);
}
return super.createJdk(home);
}

@Override
public boolean canUse() {
return OsUtils.isWindows() && Files.isDirectory(SCOOP_APPS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.jbang.jvm.jdkproviders;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/** This JDK provider detects any JDKs that have been installed using the SDKMAN package manager. */
public class SdkmanJdkProvider extends BaseFoldersJdkProvider {
private static final Path JDKS_ROOT =
Paths.get(System.getProperty("user.home")).resolve(".sdkman/candidates/java");

public SdkmanJdkProvider() {
super(JDKS_ROOT);
}

@Nullable
@Override
protected String jdkId(String name) {
return name + "-sdkman";
}

@Override
public boolean canUse() {
return Files.isDirectory(JDKS_ROOT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package dev.jbang.jvm.util;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.HttpCacheUpdateException;
import org.apache.http.impl.client.cache.DefaultHttpCacheEntrySerializer;

public class FileHttpCacheStorage implements HttpCacheStorage {

private final Path cacheDir;
private final DefaultHttpCacheEntrySerializer serializer;

public FileHttpCacheStorage(Path cacheDir) {
this.cacheDir = cacheDir;
this.serializer = new DefaultHttpCacheEntrySerializer();
try {
Files.createDirectories(cacheDir);
} catch (IOException e) {
throw new RuntimeException("Failed to create cache directory", e);
}
}

@Override
public synchronized void putEntry(String key, HttpCacheEntry entry) throws IOException {
Path filePath = cacheDir.resolve(encodeKey(key));
try (OutputStream os = Files.newOutputStream(filePath);
BufferedOutputStream bos = new BufferedOutputStream(os)) {
serializer.writeTo(entry, bos);
}
}

@Override
public synchronized HttpCacheEntry getEntry(String key) throws IOException {
Path filePath = cacheDir.resolve(encodeKey(key));
if (Files.exists(filePath)) {
try (InputStream is = Files.newInputStream(filePath);
BufferedInputStream bis = new BufferedInputStream(is)) {
return serializer.readFrom(bis);
}
}
return null;
}

@Override
public synchronized void removeEntry(String key) throws IOException {
Path filePath = cacheDir.resolve(encodeKey(key));
Files.deleteIfExists(filePath);
}

@Override
public synchronized void updateEntry(String key, HttpCacheUpdateCallback callback)
throws IOException, HttpCacheUpdateException {
Path filePath = cacheDir.resolve(encodeKey(key));
HttpCacheEntry existingEntry = null;
if (Files.exists(filePath)) {
try (InputStream is = Files.newInputStream(filePath);
BufferedInputStream bis = new BufferedInputStream(is)) {
existingEntry = serializer.readFrom(bis);
}
}
HttpCacheEntry updatedEntry = callback.update(existingEntry);
putEntry(key, updatedEntry);
}

private String encodeKey(String key) {
// You can use more sophisticated encoding if necessary
return key.replaceAll("[^a-zA-Z0-9-_]", "_");
}
}
122 changes: 122 additions & 0 deletions jdkmanager/src/main/java/dev/jbang/jvm/util/FileUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package dev.jbang.jvm.util;

import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

public class FileUtils {
private static final Logger LOGGER = Logger.getLogger(JavaUtils.class.getName());

public static void createLink(Path link, Path target) {
if (!Files.exists(link)) {
// On Windows we use junction for directories because their
// creation doesn't require any special privileges.
if (OsUtils.isWindows() && Files.isDirectory(target)) {
if (createJunction(link, target.toAbsolutePath())) {
return;
}
} else {
if (createSymbolicLink(link, target.toAbsolutePath())) {
return;
}
}
throw new IllegalStateException("Failed to create link " + link + " -> " + target);
}
}

private static boolean createSymbolicLink(Path link, Path target) {
try {
mkdirs(link.getParent());
Files.createSymbolicLink(link, target);
return true;
} catch (IOException e) {
if (OsUtils.isWindows()
&& e instanceof AccessDeniedException
&& e.getMessage().contains("privilege")) {
LOGGER.log(
Level.INFO,
"Creation of symbolic link failed {0} -> {1}}",
new Object[] {link, target});
LOGGER.info(
"This is a known issue with trying to create symbolic links on Windows.");
LOGGER.info("See the information available at the link below for a solution:");
LOGGER.info(
"https://www.jbang.dev/documentation/guide/latest/usage.html#usage-on-windows");
}
LOGGER.log(Level.FINE, "Failed to create symbolic link " + link + " -> " + target, e);
}
return false;
}

private static boolean createJunction(Path link, Path target) {
if (!Files.exists(link) && Files.exists(link, LinkOption.NOFOLLOW_LINKS)) {
// We automatically remove broken links
deletePath(link);
}
mkdirs(link.getParent());
return OsUtils.runCommand(
"cmd.exe", "/c", "mklink", "/j", link.toString(), target.toString())
!= null;
}

/**
* Returns true if the final part of the path is a symbolic link.
* @param path The path to check
* @return true if the final part of the path is a symbolic link
* @throws IOException if an I/O error occurs
*/
public static boolean isLink(Path path) throws IOException {
Path parent = path.toAbsolutePath().getParent().toRealPath();
Path absPath = parent.resolve(path.getFileName());
return !absPath.toRealPath().equals(absPath.toRealPath(LinkOption.NOFOLLOW_LINKS));
}

public static void mkdirs(Path p) {
try {
Files.createDirectories(p);
} catch (IOException e) {
throw new IllegalStateException("Failed to create directory " + p, e);
}
}

public static void deletePath(Path path) {
try {
if (isLink(path)) {
LOGGER.log(Level.FINE, "Deleting link {0}", path);
Files.delete(path);
} else if (Files.isDirectory(path)) {
LOGGER.log(Level.FINE, "Deleting folder {0}", path);
try (Stream<Path> s = Files.walk(path)) {
s.sorted(Comparator.reverseOrder())
.forEach(
f -> {
try {
Files.delete(f);
} catch (IOException e) {
throw new IllegalStateException("Failed to delete " + f, e);
}
});
}
} else if (Files.exists(path)) {
LOGGER.log(Level.FINE, "Deleting file {0}", path);
Files.delete(path);
} else if (Files.exists(path, LinkOption.NOFOLLOW_LINKS)) {
LOGGER.log(Level.FINE, "Deleting broken link {0}", path);
Files.delete(path);
}
} catch (Exception e) {
throw new IllegalStateException("Failed to delete " + path, e);
}
}

public static String extension(String name) {
int p = name.lastIndexOf('.');
return p > 0 ? name.substring(p + 1) : "";
}
}
130 changes: 130 additions & 0 deletions jdkmanager/src/main/java/dev/jbang/jvm/util/JavaUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package dev.jbang.jvm.util;

import static java.lang.System.getenv;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class JavaUtils {

private static final Pattern javaVersionPattern = Pattern.compile("\"([^\"]+)\"");

private static final Logger LOGGER = Logger.getLogger(JavaUtils.class.getName());

public static boolean isRequestedVersion(String rv) {
return rv.matches("\\d+[+]?");
}

public static int minRequestedVersion(String rv) {
return Integer.parseInt(isOpenVersion(rv) ? rv.substring(0, rv.length() - 1) : rv);
}

public static boolean isOpenVersion(String version) {
return version.endsWith("+");
}

public static int parseJavaVersion(String version) {
if (version != null) {
try {
String[] nums = version.split("[-.+]");
String num = nums.length > 1 && nums[0].equals("1") ? nums[1] : nums[0];
return Integer.parseInt(num);
} catch (NumberFormatException ex) {
// Ignore
}
}
return 0;
}

public static Optional<Integer> resolveJavaVersionFromPath(Path home) {
return resolveJavaVersionStringFromPath(home).map(JavaUtils::parseJavaVersion);
}

public static Optional<String> resolveJavaVersionStringFromPath(Path home) {
Optional<String> res = readJavaVersionStringFromReleaseFile(home);
if (!res.isPresent()) {
res = readJavaVersionStringFromJavaCommand(home);
}
return res;
}

public static Optional<String> readJavaVersionStringFromReleaseFile(Path home) {
try (Stream<String> lines = Files.lines(home.resolve("release"))) {
return lines.filter(
l ->
l.startsWith("JAVA_VERSION=")
|| l.startsWith("JAVA_RUNTIME_VERSION="))
.map(JavaUtils::parseJavaOutput)
.findAny();
} catch (IOException e) {
LOGGER.fine("Unable to read 'release' file in path: " + home);
return Optional.empty();
}
}

public static Optional<String> readJavaVersionStringFromJavaCommand(Path home) {
Optional<String> res;
Path javaCmd = OsUtils.searchPath("java", home.resolve("bin").toString());
if (javaCmd != null) {
String output = OsUtils.runCommand(javaCmd.toString(), "-version");
res = Optional.ofNullable(parseJavaOutput(output));
} else {
res = Optional.empty();
}
if (!res.isPresent()) {
LOGGER.log(Level.FINE, "Unable to obtain version from: '{0} -version'", javaCmd);
}
return res;
}

public static String parseJavaOutput(String output) {
if (output != null) {
Matcher m = javaVersionPattern.matcher(output);
if (m.find() && m.groupCount() == 1) {
return m.group(1);
}
}
return null;
}

/**
* Returns the Path to JAVA_HOME
*
* @return A Path pointing to JAVA_HOME or null if it isn't defined
*/
public static Path getJavaHomeEnv() {
if (getenv("JAVA_HOME") != null) {
return Paths.get(getenv("JAVA_HOME"));
} else {
return null;
}
}

/**
* Method takes the given path which might point to a Java home directory or to the `jre`
* directory inside it and makes sure to return the path to the actual home directory.
*/
public static Path jre2jdk(Path jdkHome) {
// Detect if the current JDK is a JRE and try to find the real home
if (!Files.isRegularFile(jdkHome.resolve("release"))) {
Path jh = jdkHome.toAbsolutePath();
try {
jh = jh.toRealPath();
} catch (IOException e) {
// Ignore error
}
if (jh.endsWith("jre") && Files.isRegularFile(jh.getParent().resolve("release"))) {
jdkHome = jh.getParent();
}
}
return jdkHome;
}
}
115 changes: 115 additions & 0 deletions jdkmanager/src/main/java/dev/jbang/jvm/util/NetUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package dev.jbang.jvm.util;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Function;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpClientBuilder;

public class NetUtils {

public static final RequestConfig DEFAULT_REQUEST_CONFIG =
RequestConfig.custom()
.setConnectionRequestTimeout(10000)
.setConnectTimeout(10000)
.setSocketTimeout(30000)
.build();

public static <T> T readJsonFromUrl(String url, Class<T> klass) throws IOException {
HttpClientBuilder builder = createDefaultHttpClientBuilder();
return readJsonFromUrl(builder, url, klass);
}

public static <T> T readJsonFromUrl(HttpClientBuilder builder, String url, Class<T> klass)
throws IOException {
return requestUrl(builder, url, response -> handleJsonResult(klass, response));
}

public static Path downloadFromUrl(String url) throws IOException {
HttpClientBuilder builder = createDefaultHttpClientBuilder();
return downloadFromUrl(builder, url);
}

public static Path downloadFromUrl(HttpClientBuilder builder, String url) throws IOException {
return requestUrl(builder, url, NetUtils::handleDownloadResult);
}

public static HttpClientBuilder createDefaultHttpClientBuilder() {
CacheConfig cacheConfig = CacheConfig.custom().setMaxCacheEntries(1000).build();

FileHttpCacheStorage cacheStorage = new FileHttpCacheStorage(Paths.get("http-cache"));

// return HttpClientBuilder.create().setDefaultRequestConfig(DEFAULT_REQUEST_CONFIG);
return CachingHttpClientBuilder.create()
.setCacheConfig(cacheConfig)
.setHttpCacheStorage(cacheStorage)
.setDefaultRequestConfig(DEFAULT_REQUEST_CONFIG);
}

public static <T> T requestUrl(
HttpClientBuilder builder, String url, Function<HttpResponse, T> responseHandler)
throws IOException {
try (CloseableHttpClient httpClient = builder.build()) {
HttpGet httpGet = new HttpGet(url);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
int responseCode = response.getStatusLine().getStatusCode();
if (responseCode != 200) {
throw new IOException(
"Failed to read from URL: "
+ url
+ ", response code: #"
+ responseCode);
}
HttpEntity entity = response.getEntity();
if (entity == null) {
throw new IOException("Failed to read from URL: " + url + ", no content");
}
return responseHandler.apply(response);
}
} catch (UncheckedIOException e) {
throw new IOException("Failed to read from URL: " + url + ", " + e.getMessage(), e);
}
}

private static <T> T handleJsonResult(Class<T> klass, HttpResponse response) {
try {
String mimeType = ContentType.getOrDefault(response.getEntity()).getMimeType();
if (!mimeType.equals("application/json")) {
throw new IOException("Unexpected MIME type: " + mimeType);
}
HttpEntity entity = response.getEntity();
try (InputStream is = entity.getContent()) {
Gson parser = new GsonBuilder().create();
return parser.fromJson(new InputStreamReader(is), klass);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private static Path handleDownloadResult(HttpResponse response) {
try {
HttpEntity entity = response.getEntity();
try (InputStream is = entity.getContent()) {
// TODO implement
return null;
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
181 changes: 181 additions & 0 deletions jdkmanager/src/main/java/dev/jbang/jvm/util/OsUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package dev.jbang.jvm.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OsUtils {

private static final Logger LOGGER = Logger.getLogger(OsUtils.class.getName());

public enum OS {
linux,
mac,
windows,
aix,
unknown
}

public enum Arch {
x32,
x64,
aarch64,
arm,
arm64,
ppc64,
ppc64le,
s390x,
riscv64,
unknown
}

public static OS getOS() {
String os =
System.getProperty("os.name")
.toLowerCase(Locale.ENGLISH)
.replaceAll("[^a-z0-9]+", "");
if (os.startsWith("mac") || os.startsWith("osx")) {
return OS.mac;
} else if (os.startsWith("linux")) {
return OS.linux;
} else if (os.startsWith("win")) {
return OS.windows;
} else if (os.startsWith("aix")) {
return OS.aix;
} else {
LOGGER.log(Level.FINE, "Unknown OS: {0}", os);
return OS.unknown;
}
}

public static Arch getArch() {
String arch =
System.getProperty("os.arch")
.toLowerCase(Locale.ENGLISH)
.replaceAll("[^a-z0-9]+", "");
if (arch.matches("^(x8664|amd64|ia32e|em64t|x64)$")) {
return Arch.x64;
} else if (arch.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) {
return Arch.x32;
} else if (arch.matches("^(aarch64)$")) {
return Arch.aarch64;
} else if (arch.matches("^(arm)$")) {
return Arch.arm;
} else if (arch.matches("^(ppc64)$")) {
return Arch.ppc64;
} else if (arch.matches("^(ppc64le)$")) {
return Arch.ppc64le;
} else if (arch.matches("^(s390x)$")) {
return Arch.s390x;
} else if (arch.matches("^(arm64)$")) {
return Arch.arm64;
} else if (arch.matches("^(riscv64)$")) {
return Arch.riscv64;
} else {
LOGGER.log(Level.FINE, "Unknown Arch: {0}", arch);
return Arch.unknown;
}
}

public static boolean isWindows() {
return getOS() == OS.windows;
}

public static boolean isMac() {
return getOS() == OS.mac;
}

/**
* Searches the locations defined by PATH for the given executable
*
* @param cmd The name of the executable to look for
* @return A Path to the executable, if found, null otherwise
*/
public static Path searchPath(String cmd) {
String envPath = System.getenv("PATH");
envPath = envPath != null ? envPath : "";
return searchPath(cmd, envPath);
}

/**
* Searches the locations defined by `paths` for the given executable
*
* @param cmd The name of the executable to look for
* @param paths A string containing the paths to search
* @return A Path to the executable, if found, null otherwise
*/
public static Path searchPath(String cmd, String paths) {
return Arrays.stream(paths.split(File.pathSeparator))
.map(dir -> Paths.get(dir).resolve(cmd))
.flatMap(OsUtils::executables)
.filter(OsUtils::isExecutable)
.findFirst()
.orElse(null);
}

private static Stream<Path> executables(Path base) {
if (isWindows()) {
return Stream.of(
Paths.get(base.toString() + ".exe"),
Paths.get(base.toString() + ".bat"),
Paths.get(base.toString() + ".cmd"),
Paths.get(base.toString() + ".ps1"));
} else {
return Stream.of(base);
}
}

private static boolean isExecutable(Path file) {
if (Files.isRegularFile(file)) {
if (isWindows()) {
String nm = file.getFileName().toString().toLowerCase();
return nm.endsWith(".exe")
|| nm.endsWith(".bat")
|| nm.endsWith(".cmd")
|| nm.endsWith(".ps1");
} else {
return Files.isExecutable(file);
}
}
return false;
}

/**
* Runs the given command + arguments and returns its output (both stdout and stderr) as a
* string
*
* @param cmd The command to execute
* @return The output of the command or null if anything went wrong
*/
public static String runCommand(String... cmd) {
try {
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);
Process p = pb.start();
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String cmdOutput = br.lines().collect(Collectors.joining("\n"));
int exitCode = p.waitFor();
if (exitCode == 0) {
return cmdOutput;
} else {
LOGGER.log(
Level.FINE,
"Command failed: #{0} - {1}",
new Object[] {exitCode, cmdOutput});
}
} catch (IOException | InterruptedException ex) {
LOGGER.log(Level.FINE, "Error running: " + String.join(" ", cmd), ex);
}
return null;
}
}
219 changes: 219 additions & 0 deletions jdkmanager/src/main/java/dev/jbang/jvm/util/UnpackUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package dev.jbang.jvm.util;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFilePermission;
import java.util.*;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;

public class UnpackUtils {

public static void unpackJdk(Path archive, Path outputDir) throws IOException {
String name = archive.toString().toLowerCase(Locale.ENGLISH);
Path selectFolder = OsUtils.isMac() ? Paths.get("Contents/Home") : null;
if (name.endsWith(".zip")) {
unzip(archive, outputDir, true, selectFolder, UnpackUtils::defaultZipEntryCopy);
} else if (name.endsWith(".tar.gz") || name.endsWith(".tgz")) {
untargz(archive, outputDir, true, selectFolder);
}
}

public static void unpack(Path archive, Path outputDir) throws IOException {
unpack(archive, outputDir, false);
}

public static void unpack(Path archive, Path outputDir, boolean stripRootFolder)
throws IOException {
unpack(archive, outputDir, stripRootFolder, null);
}

public static void unpack(
Path archive, Path outputDir, boolean stripRootFolder, Path selectFolder)
throws IOException {
String name = archive.toString().toLowerCase(Locale.ENGLISH);
if (name.endsWith(".zip") || name.endsWith(".jar")) {
unzip(
archive,
outputDir,
stripRootFolder,
selectFolder,
UnpackUtils::defaultZipEntryCopy);
} else if (name.endsWith(".tar.gz") || name.endsWith(".tgz")) {
untargz(archive, outputDir, stripRootFolder, selectFolder);
} else {
throw new IllegalArgumentException(
"Unsupported archive format: " + FileUtils.extension(archive.toString()));
}
}

public static void unzip(
Path zip,
Path outputDir,
boolean stripRootFolder,
Path selectFolder,
ExistingZipFileHandler onExisting)
throws IOException {
try (ZipFile zipFile = new ZipFile(zip.toFile())) {
Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry zipEntry = entries.nextElement();
Path entry = Paths.get(zipEntry.getName());
if (stripRootFolder) {
if (entry.getNameCount() == 1) {
continue;
}
entry = entry.subpath(1, entry.getNameCount());
}
if (selectFolder != null) {
if (!entry.startsWith(selectFolder) || entry.equals(selectFolder)) {
continue;
}
entry = entry.subpath(selectFolder.getNameCount(), entry.getNameCount());
}
entry = outputDir.resolve(entry).normalize();
if (!entry.startsWith(outputDir)) {
throw new IOException(
"Entry is outside of the target dir: " + zipEntry.getName());
}
if (zipEntry.isDirectory()) {
Files.createDirectories(entry);
} else if (zipEntry.isUnixSymlink()) {
Scanner s = new Scanner(zipFile.getInputStream(zipEntry)).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : "";
Files.createSymbolicLink(entry, Paths.get(result));
} else {
if (!Files.isDirectory(entry.getParent())) {
Files.createDirectories(entry.getParent());
}
if (Files.isRegularFile(entry)) {
onExisting.handle(zipFile, zipEntry, entry);
} else {
defaultZipEntryCopy(zipFile, zipEntry, entry);
}
}
}
}
}

public interface ExistingZipFileHandler {
void handle(ZipFile zipFile, ZipArchiveEntry zipEntry, Path outFile) throws IOException;
}

public static void defaultZipEntryCopy(ZipFile zipFile, ZipArchiveEntry zipEntry, Path outFile)
throws IOException {
try (InputStream zis = zipFile.getInputStream(zipEntry)) {
Files.copy(zis, outFile, StandardCopyOption.REPLACE_EXISTING);
}
int mode = zipEntry.getUnixMode();
if (mode != 0 && !OsUtils.isWindows()) {
Set<PosixFilePermission> permissions =
PosixFilePermissionSupport.toPosixFilePermissions(mode);
Files.setPosixFilePermissions(outFile, permissions);
}
}

public static void untargz(
Path targz, Path outputDir, boolean stripRootFolder, Path selectFolder)
throws IOException {
try (TarArchiveInputStream tarArchiveInputStream =
new TarArchiveInputStream(
new GzipCompressorInputStream(
Files.newInputStream(targz.toFile().toPath())))) {
TarArchiveEntry targzEntry;
while ((targzEntry = tarArchiveInputStream.getNextEntry()) != null) {
Path entry = Paths.get(targzEntry.getName()).normalize();
if (stripRootFolder) {
if (entry.getNameCount() == 1) {
continue;
}
entry = entry.subpath(1, entry.getNameCount());
}
if (selectFolder != null) {
if (!entry.startsWith(selectFolder) || entry.equals(selectFolder)) {
continue;
}
entry = entry.subpath(selectFolder.getNameCount(), entry.getNameCount());
}
entry = outputDir.resolve(entry).normalize();
if (!entry.startsWith(outputDir)) {
throw new IOException(
"Entry is outside of the target dir: " + targzEntry.getName());
}
if (targzEntry.isDirectory()) {
Files.createDirectories(entry);
} else {
if (!Files.isDirectory(entry.getParent())) {
Files.createDirectories(entry.getParent());
}
Files.copy(tarArchiveInputStream, entry, StandardCopyOption.REPLACE_EXISTING);
int mode = targzEntry.getMode();
if (mode != 0 && !OsUtils.isWindows()) {
Set<PosixFilePermission> permissions =
PosixFilePermissionSupport.toPosixFilePermissions(mode);
Files.setPosixFilePermissions(entry, permissions);
}
}
}
}
}
}

class PosixFilePermissionSupport {

private static final int OWNER_READ_FILEMODE = 0b100_000_000;
private static final int OWNER_WRITE_FILEMODE = 0b010_000_000;
private static final int OWNER_EXEC_FILEMODE = 0b001_000_000;

private static final int GROUP_READ_FILEMODE = 0b000_100_000;
private static final int GROUP_WRITE_FILEMODE = 0b000_010_000;
private static final int GROUP_EXEC_FILEMODE = 0b000_001_000;

private static final int OTHERS_READ_FILEMODE = 0b000_000_100;
private static final int OTHERS_WRITE_FILEMODE = 0b000_000_010;
private static final int OTHERS_EXEC_FILEMODE = 0b000_000_001;

private PosixFilePermissionSupport() {}

static Set<PosixFilePermission> toPosixFilePermissions(int octalFileMode) {
Set<PosixFilePermission> permissions = new LinkedHashSet<>();
// Owner
if ((octalFileMode & OWNER_READ_FILEMODE) == OWNER_READ_FILEMODE) {
permissions.add(PosixFilePermission.OWNER_READ);
}
if ((octalFileMode & OWNER_WRITE_FILEMODE) == OWNER_WRITE_FILEMODE) {
permissions.add(PosixFilePermission.OWNER_WRITE);
}
if ((octalFileMode & OWNER_EXEC_FILEMODE) == OWNER_EXEC_FILEMODE) {
permissions.add(PosixFilePermission.OWNER_EXECUTE);
}
// Group
if ((octalFileMode & GROUP_READ_FILEMODE) == GROUP_READ_FILEMODE) {
permissions.add(PosixFilePermission.GROUP_READ);
}
if ((octalFileMode & GROUP_WRITE_FILEMODE) == GROUP_WRITE_FILEMODE) {
permissions.add(PosixFilePermission.GROUP_WRITE);
}
if ((octalFileMode & GROUP_EXEC_FILEMODE) == GROUP_EXEC_FILEMODE) {
permissions.add(PosixFilePermission.GROUP_EXECUTE);
}
// Others
if ((octalFileMode & OTHERS_READ_FILEMODE) == OTHERS_READ_FILEMODE) {
permissions.add(PosixFilePermission.OTHERS_READ);
}
if ((octalFileMode & OTHERS_WRITE_FILEMODE) == OTHERS_WRITE_FILEMODE) {
permissions.add(PosixFilePermission.OTHERS_WRITE);
}
if ((octalFileMode & OTHERS_EXEC_FILEMODE) == OTHERS_EXEC_FILEMODE) {
permissions.add(PosixFilePermission.OTHERS_EXECUTE);
}
return permissions;
}
}
3 changes: 2 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ plugins {
id "com.gradle.enterprise"
}


// Configuration of com.gradle.enterprise (build scan) plugin
gradleEnterprise {
buildScan {
@@ -20,3 +19,5 @@ gradleEnterprise {
//publishAlways()
}
}

include 'jdkmanager'
13 changes: 8 additions & 5 deletions src/main/java/dev/jbang/Cache.java
Original file line number Diff line number Diff line change
@@ -5,10 +5,12 @@
import java.nio.file.Path;

import dev.jbang.cli.ExitException;
import dev.jbang.net.JdkManager;
import dev.jbang.net.JdkProvider;
import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.JdkManager;
import dev.jbang.util.Util;

import static dev.jbang.util.JavaUtil.jdkManager;

public class Cache {

public enum CacheClass {
@@ -23,11 +25,12 @@ static void setupCache(Path dir) {
public static void clearCache(CacheClass... classes) {
for (CacheClass cc : classes) {
Util.infoMsg("Clearing cache for " + cc.name());
if (cc == CacheClass.jdks && Util.isWindows() && JdkManager.isCurrentJdkManaged()) {
JdkManager jdkMan = jdkManager();
if (cc == CacheClass.jdks && Util.isWindows() && jdkMan.isCurrentJdkManaged()) {
// We're running using a managed JDK on Windows so we can't just delete the
// entire folder!
for (JdkProvider.Jdk jdk : JdkManager.listInstalledJdks()) {
JdkManager.uninstallJdk(jdk);
for (Jdk jdk : jdkMan.listInstalledJdks()) {
jdkMan.uninstallJdk(jdk);
}
}
if (cc == CacheClass.deps) {
10 changes: 6 additions & 4 deletions src/main/java/dev/jbang/cli/App.java
Original file line number Diff line number Diff line change
@@ -21,8 +21,8 @@
import dev.jbang.Settings;
import dev.jbang.catalog.CatalogUtil;
import dev.jbang.dependencies.DependencyUtil;
import dev.jbang.net.JdkManager;
import dev.jbang.net.JdkProvider;
import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.JdkManager;
import dev.jbang.source.Project;
import dev.jbang.source.ProjectBuilder;
import dev.jbang.util.CommandBuffer;
@@ -31,6 +31,8 @@

import picocli.CommandLine;

import static dev.jbang.util.JavaUtil.jdkManager;

@CommandLine.Command(name = "app", description = "Manage scripts installed on the user's PATH as commands.", subcommands = {
AppInstall.class, AppList.class,
AppUninstall.class, AppSetup.class })
@@ -401,7 +403,7 @@ public static boolean needsSetup() {
*/
public static boolean guessWithJava() {
boolean withJava;
JdkProvider.Jdk defJdk = JdkManager.getJdk(null, false);
Jdk defJdk = jdkManager().getJdk(null, false);
String javaHome = System.getenv("JAVA_HOME");
Path javacCmd = Util.searchPath("javac");
withJava = defJdk != null
@@ -415,7 +417,7 @@ public static boolean guessWithJava() {
public static int setup(boolean withJava, boolean force, boolean chatty) {
Path jdkHome = null;
if (withJava) {
JdkProvider.Jdk defJdk = JdkManager.getDefaultJdk();
Jdk defJdk = jdkManager().getDefaultJdk();
if (defJdk == null) {
Util.infoMsg("No default JDK set, use 'jbang jdk default <version>' to set one.");
return EXIT_UNEXPECTED_STATE;
9 changes: 5 additions & 4 deletions src/main/java/dev/jbang/cli/Info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.jbang.cli;

import static dev.jbang.Settings.CP_SEPARATOR;
import static dev.jbang.util.JavaUtil.jdkManager;

import java.io.IOException;
import java.lang.reflect.Field;
@@ -19,8 +20,8 @@

import dev.jbang.dependencies.ArtifactInfo;
import dev.jbang.dependencies.MavenRepo;
import dev.jbang.net.JdkManager;
import dev.jbang.net.JdkProvider;
import dev.jbang.jvm.Jdk;
import dev.jbang.jvm.JdkManager;
import dev.jbang.source.*;
import dev.jbang.util.JavaUtil;
import dev.jbang.util.ModuleUtil;
@@ -117,8 +118,8 @@ public ScriptInfo(BuildContext ctx, boolean assureJdkInstalled) {
requestedJavaVersion = prj.getJavaVersion();

try {
JdkProvider.Jdk jdk = assureJdkInstalled ? JdkManager.getOrInstallJdk(requestedJavaVersion)
: JdkManager.getJdk(requestedJavaVersion, false);
Jdk jdk = assureJdkInstalled ? jdkManager().getOrInstallJdk(requestedJavaVersion)
: jdkManager().getJdk(requestedJavaVersion, false);
if (jdk != null && jdk.isInstalled()) {
availableJdkPath = jdk.getHome().toString();
}
38 changes: 20 additions & 18 deletions src/main/java/dev/jbang/cli/Jdk.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.jbang.cli;

import static dev.jbang.cli.BaseCommand.*;
import static dev.jbang.util.JavaUtil.jdkManager;

import java.io.IOException;
import java.io.PrintStream;
@@ -12,8 +13,7 @@
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;

import dev.jbang.net.JdkManager;
import dev.jbang.net.JdkProvider;
import dev.jbang.jvm.JdkManager;
import dev.jbang.util.JavaUtil;
import dev.jbang.util.Util;

@@ -28,6 +28,8 @@ public class Jdk {
@CommandLine.Mixin
JdkProvidersMixin jdkProvidersMixin;

private JdkManager jdkMan = jdkManager();

@CommandLine.Command(name = "install", description = "Installs a JDK.")
public Integer install(
@CommandLine.Option(names = { "--force",
@@ -36,13 +38,13 @@ public Integer install(
@CommandLine.Parameters(paramLabel = "existingJdkPath", index = "1", description = "Pre installed JDK path", arity = "0..1") String path)
throws IOException {
jdkProvidersMixin.initJdkProviders();
JdkProvider.Jdk jdk = JdkManager.getInstalledJdk(versionOrId, true);
dev.jbang.jvm.Jdk jdk = jdkMan.getInstalledJdk(versionOrId, true);
if (force || jdk == null) {
if (!Util.isNullOrBlankString(path)) {
JdkManager.linkToExistingJdk(path, Integer.parseInt(versionOrId));
jdkMan.linkToExistingJdk(path, Integer.parseInt(versionOrId));
} else {
if (jdk == null) {
jdk = JdkManager.getJdk(versionOrId, true);
jdk = jdkMan.getJdk(versionOrId, true);
}
jdk.install();
}
@@ -62,14 +64,14 @@ public Integer list(
@CommandLine.Option(names = {
"--format" }, description = "Specify output format ('text' or 'json')") FormatMixin.Format format) {
jdkProvidersMixin.initJdkProviders();
JdkProvider.Jdk defaultJdk = JdkManager.getDefaultJdk();
dev.jbang.jvm.Jdk defaultJdk = jdkMan.getDefaultJdk();
int defMajorVersion = defaultJdk != null ? defaultJdk.getMajorVersion() : 0;
PrintStream out = System.out;
List<JdkProvider.Jdk> jdks;
List<dev.jbang.jvm.Jdk> jdks;
if (available) {
jdks = JdkManager.listAvailableJdks();
jdks = jdkMan.listAvailableJdks();
} else {
jdks = JdkManager.listInstalledJdks();
jdks = jdkMan.listInstalledJdks();
}
List<JdkOut> jdkOuts = jdks .stream()
.map(jdk -> new JdkOut(jdk.getId(), jdk.getVersion(), jdk.getProvider().name(),
@@ -153,11 +155,11 @@ public int compareTo(JdkOut o) {
public Integer uninstall(
@CommandLine.Parameters(paramLabel = "version", index = "0", description = "The version to install", arity = "1") String versionOrId) {
jdkProvidersMixin.initJdkProviders();
JdkProvider.Jdk jdk = JdkManager.getInstalledJdk(versionOrId, true);
dev.jbang.jvm.Jdk jdk = jdkMan.getInstalledJdk(versionOrId, true);
if (jdk == null) {
throw new ExitException(EXIT_INVALID_INPUT, "JDK " + versionOrId + " is not installed");
}
JdkManager.uninstallJdk(jdk);
jdkMan.uninstallJdk(jdk);
Util.infoMsg("Uninstalled JDK:\n " + versionOrId);
return EXIT_OK;
}
@@ -166,7 +168,7 @@ public Integer uninstall(
public Integer home(
@CommandLine.Parameters(paramLabel = "versionOrId", index = "0", description = "The version of the JDK to select", arity = "0..1") String versionOrId) {
jdkProvidersMixin.initJdkProviders();
Path home = JdkManager.getOrInstallJdk(versionOrId).getHome();
Path home = jdkMan.getOrInstallJdk(versionOrId).getHome();
if (home != null) {
String homeStr = Util.pathToString(home);
System.out.println(homeStr);
@@ -178,12 +180,12 @@ public Integer home(
public Integer javaEnv(
@CommandLine.Parameters(paramLabel = "versionOrId", index = "0", description = "The version of the JDK to select", arity = "0..1") String versionOrId) {
jdkProvidersMixin.initJdkProviders();
JdkProvider.Jdk jdk = null;
dev.jbang.jvm.Jdk jdk = null;
if (versionOrId != null && JavaUtil.isRequestedVersion(versionOrId)) {
jdk = JdkManager.getJdk(versionOrId, true);
jdk = jdkMan.getJdk(versionOrId, true);
}
if (jdk == null || !jdk.isInstalled()) {
jdk = JdkManager.getOrInstallJdk(versionOrId);
jdk = jdkMan.getOrInstallJdk(versionOrId);
}
Path home = jdk.getHome();
if (home != null) {
@@ -228,11 +230,11 @@ public Integer javaEnv(
public Integer defaultJdk(
@CommandLine.Parameters(paramLabel = "version", index = "0", description = "The version of the JDK to select", arity = "0..1") String versionOrId) {
jdkProvidersMixin.initJdkProviders();
JdkProvider.Jdk defjdk = JdkManager.getDefaultJdk();
dev.jbang.jvm.Jdk defjdk = jdkMan.getDefaultJdk();
if (versionOrId != null) {
JdkProvider.Jdk jdk = JdkManager.getOrInstallJdk(versionOrId);
dev.jbang.jvm.Jdk jdk = jdkMan.getOrInstallJdk(versionOrId);
if (defjdk == null || (!jdk.equals(defjdk) && !Objects.equals(jdk.getHome(), defjdk.getHome()))) {
JdkManager.setDefaultJdk(jdk);
jdkMan.setDefaultJdk(jdk);
} else {
Util.infoMsg("Default JDK already set to " + defjdk.getMajorVersion());
}
5 changes: 3 additions & 2 deletions src/main/java/dev/jbang/cli/JdkProvidersMixin.java
Original file line number Diff line number Diff line change
@@ -3,8 +3,9 @@
import java.util.ArrayList;
import java.util.List;

import dev.jbang.net.JdkManager;
import dev.jbang.jvm.JdkManager;

import dev.jbang.util.JavaUtil;
import picocli.CommandLine;

public class JdkProvidersMixin {
@@ -15,7 +16,7 @@ public class JdkProvidersMixin {

protected void initJdkProviders() {
if (jdkProviders != null && !jdkProviders.isEmpty()) {
JdkManager.initProvidersByName(jdkProviders);
JavaUtil.initProvidersByName(jdkProviders);
}
}

6 changes: 3 additions & 3 deletions src/main/java/dev/jbang/dependencies/ModularClassPath.java
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;

import dev.jbang.net.JdkProvider;
import dev.jbang.jvm.Jdk;
import dev.jbang.util.Util;

public class ModularClassPath {
@@ -67,7 +67,7 @@ boolean hasJavaFX() {
return javafx.get();
}

public List<String> getAutoDectectedModuleArguments(@Nonnull JdkProvider.Jdk jdk) {
public List<String> getAutoDectectedModuleArguments(@Nonnull Jdk jdk) {
if (hasJavaFX() && supportsModules(jdk)) {
List<String> commandArguments = new ArrayList<>();

@@ -132,7 +132,7 @@ public List<String> getAutoDectectedModuleArguments(@Nonnull JdkProvider.Jdk jdk
}
}

protected boolean supportsModules(JdkProvider.Jdk jdk) {
protected boolean supportsModules(Jdk jdk) {
return jdk.getMajorVersion() >= 9;
}

552 changes: 0 additions & 552 deletions src/main/java/dev/jbang/net/JdkManager.java

This file was deleted.

283 changes: 0 additions & 283 deletions src/main/java/dev/jbang/net/JdkProvider.java

This file was deleted.

132 changes: 0 additions & 132 deletions src/main/java/dev/jbang/net/jdkproviders/BaseFoldersJdkProvider.java

This file was deleted.

57 changes: 0 additions & 57 deletions src/main/java/dev/jbang/net/jdkproviders/CurrentJdkProvider.java

This file was deleted.

55 changes: 0 additions & 55 deletions src/main/java/dev/jbang/net/jdkproviders/DefaultJdkProvider.java

This file was deleted.

275 changes: 0 additions & 275 deletions src/main/java/dev/jbang/net/jdkproviders/JBangJdkProvider.java

This file was deleted.

55 changes: 0 additions & 55 deletions src/main/java/dev/jbang/net/jdkproviders/JavaHomeJdkProvider.java

This file was deleted.

Loading

0 comments on commit afc114d

Please sign in to comment.