blob: 6e40bf99b412505b74b65635e1725f92f808fd6d [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.bcel.generic;
import static com.sun.jna.platform.win32.WinReg.HKEY_LOCAL_MACHINE;
import java.io.File;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Module;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ModularRuntimeImage;
import org.apache.commons.io.function.Uncheck;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import com.sun.jna.platform.win32.Advapi32Util;
/**
* Used from {@code @MethodSource} for tests.
*/
public class JavaHome {
private static final String EXTRA_JAVA_HOMES = "ExtraJavaHomes";
/** A folder containing Java homes, for example, on Windows "C:/Program Files/Eclipse Adoptium/" */
private static final String EXTRA_JAVA_ROOT = "ExtraJavaRoot";
/** The default home for Java installs on Windows for Eclipse Adoptium. */
private static final String ADOPTIUM_WINDOWS = "C:/Program Files/Eclipse Adoptium/";
/** The default home for Java installs on Windows for Eclipse Oracle. */
private static final String ORACLE_WINDOWS = "C:/Program Files/Java/";
private static final String EXTRA_JAVA_ROOT_DEFAULT = ADOPTIUM_WINDOWS + File.pathSeparator + ORACLE_WINDOWS;
private static final String KEY_JDK = "SOFTWARE\\JavaSoft\\Java Development Kit";
private static final String KEY_JDK_9 = "SOFTWARE\\JavaSoft\\JDK";
private static final String KEY_JRE = "SOFTWARE\\JavaSoft\\Java Runtime Environment";
private static final String KEY_JRE_9 = "SOFTWARE\\JavaSoft\\JRE";
private static Stream<Path> find(final Path start, final int maxDepth, final BiPredicate<Path, BasicFileAttributes> matcher,
final FileVisitOption... options) {
// TODO Apache Commons 2.14.0: Use FilesUncheck
return Files.exists(start) ? Uncheck.apply(Files::find, start, maxDepth, matcher, options) : Stream.empty();
}
private static JavaHome from(final String javaHome) {
return new JavaHome(Paths.get(javaHome));
}
private static Stream<String> streamAllWindowsJavaHomes(final String keyJre) {
if (Advapi32Util.registryKeyExists(HKEY_LOCAL_MACHINE, keyJre)) {
return streamWindowsJavaHomes(keyJre, Advapi32Util.registryGetKeys(HKEY_LOCAL_MACHINE, keyJre));
}
return Stream.empty();
}
private static Stream<String> streamFromCustomKey(final String key, final String defaultValue) {
return streamPropertyAndEnvVarValues(key, defaultValue).flatMap(s -> find(Paths.get(s), 1, (p, a) -> Files.isDirectory(p)).map(Path::toString));
}
private static Stream<String> streamFromCustomKeys() {
final String defaultRoot = SystemUtils.IS_OS_WINDOWS ? EXTRA_JAVA_ROOT_DEFAULT : null;
return Stream.concat(streamPropertyAndEnvVarValues(EXTRA_JAVA_HOMES, null), streamFromCustomKey(EXTRA_JAVA_ROOT, defaultRoot));
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of JarFile.
*/
public static Stream<JarEntry> streamJarEntry() {
return streamJavaHome().flatMap(JavaHome::streamJarEntryByExt);
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of JarFile.
*/
public static Stream<JarEntry> streamJarEntryClass() {
return streamJavaHome().flatMap(JavaHome::streamJarEntryByExtClass);
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of JarFile.
*/
public static Stream<String> streamJarEntryClassName() {
return streamJavaHome().flatMap(JavaHome::streamJarEntryByExtClassName);
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of JarFile.
*/
public static Stream<JarFile> streamJarFile() {
return streamJavaHome().flatMap(JavaHome::streamJarFileByExt);
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of Java jar paths.
*/
public static Stream<Path> streamJarPath() {
return streamJavaHome().flatMap(JavaHome::streamJarPathByExt);
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of Java homes.
*/
public static Stream<JavaHome> streamJavaHome() {
return streamJavaHomeString().map(JavaHome::from);
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of Java homes.
*/
public static Stream<String> streamJavaHomeString() {
final Stream<String> streamW = SystemUtils.IS_OS_WINDOWS ? streamWindowsStrings() : Stream.empty();
final Stream<String> streamK = Stream.concat(streamW, streamFromCustomKeys());
final Stream<String> streamJ = StringUtils.isEmpty(SystemUtils.JAVA_HOME) ? Stream.empty() : Stream.of(SystemUtils.JAVA_HOME);
return Stream.concat(streamK, streamJ);
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of Java jar paths.
*/
public static Stream<ModularRuntimeImage> streamModularRuntimeImage() {
return streamJavaHome().map(JavaHome::getModularRuntimeImage);
}
/**
* Used from {@code @MethodSource} for tests.
*
* @return a stream of Java jar paths.
*/
public static Stream<Path> streamModulePath() {
return streamJavaHome().flatMap(JavaHome::streamModuleByExt);
}
private static Stream<String> streamPropertyAndEnvVarValues(final String key, final String defaultValue) {
return Stream.concat(toPathStringStream(System.getProperty(key, defaultValue)), toPathStringStream(System.getenv(key)));
}
private static Stream<String> streamWindowsJavaHomes(final String keyJavaHome, final String[] keys) {
final Set<String> javaHomes = new HashSet<>(keys.length);
for (final String key : keys) {
if (Advapi32Util.registryKeyExists(HKEY_LOCAL_MACHINE, keyJavaHome + "\\" + key)) {
final String javaHome = Advapi32Util.registryGetStringValue(HKEY_LOCAL_MACHINE, keyJavaHome + "\\" + key, "JavaHome");
if (StringUtils.isNoneBlank(javaHome) && new File(javaHome).exists()) {
javaHomes.add(javaHome);
}
}
}
return javaHomes.stream();
}
private static Stream<String> streamWindowsStrings() {
return Stream.concat(Stream.of(KEY_JRE, KEY_JRE_9, KEY_JDK, KEY_JDK_9).flatMap(JavaHome::streamAllWindowsJavaHomes),
streamPropertyAndEnvVarValues(EXTRA_JAVA_HOMES, null)).distinct();
}
private static Stream<String> toPathStringStream(final String path) {
return StringUtils.isEmpty(path) ? Stream.empty() : Stream.of(path.split(File.pathSeparator));
}
private final Path path;
private JavaHome(final Path path) {
this.path = Objects.requireNonNull(path, "path");
}
Stream<Path> find(final int maxDepth, final BiPredicate<Path, BasicFileAttributes> matcher, final FileVisitOption... options) {
return find(path, maxDepth, matcher, options);
}
ModularRuntimeImage getModularRuntimeImage() {
return Uncheck.get(() -> new ModularRuntimeImage(path.toString()));
}
Path getPath() {
return path;
}
private Stream<Path> streamEndsWith(final String suffix) {
return find(10, (p, a) -> p.toString().endsWith(suffix));
}
private Stream<JarEntry> streamJarEntryByExt() {
return streamJarFileByExt().flatMap(JarFile::stream);
}
private Stream<JarEntry> streamJarEntryByExtClass() {
return streamJarEntryByExt().filter(je -> je.getName().endsWith(JavaClass.EXTENSION));
}
private Stream<String> streamJarEntryByExtClassName() {
return streamJarEntryByExtClass().map(je -> Utility.pathToPackage(je.getName().substring(0, je.getName().indexOf(JavaClass.EXTENSION))));
}
private Stream<JarFile> streamJarFileByExt() {
return streamJarPathByExt().map(this::toJarFile);
}
private Stream<Path> streamJarPathByExt() {
return streamEndsWith(".jar");
}
private Stream<Path> streamModuleByExt() {
return streamEndsWith(Module.EXTENSION);
}
private JarFile toJarFile(final Path path) {
return Uncheck.get(() -> new JarFile(path.toFile()));
}
@Override
public String toString() {
return path.toString();
}
}