View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.bcel.generic;
21  
22  import static com.sun.jna.platform.win32.WinReg.HKEY_LOCAL_MACHINE;
23  
24  import java.io.File;
25  import java.nio.file.FileVisitOption;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.Paths;
29  import java.nio.file.attribute.BasicFileAttributes;
30  import java.util.HashSet;
31  import java.util.Objects;
32  import java.util.Set;
33  import java.util.function.BiPredicate;
34  import java.util.jar.JarEntry;
35  import java.util.jar.JarFile;
36  import java.util.stream.Stream;
37  
38  import org.apache.bcel.classfile.JavaClass;
39  import org.apache.bcel.classfile.Module;
40  import org.apache.bcel.classfile.Utility;
41  import org.apache.bcel.util.ModularRuntimeImage;
42  import org.apache.commons.io.function.Uncheck;
43  import org.apache.commons.lang3.StringUtils;
44  import org.apache.commons.lang3.SystemUtils;
45  
46  import com.sun.jna.platform.win32.Advapi32Util;
47  
48  /**
49   * Used from {@code @MethodSource} for tests.
50   */
51  public class JavaHome {
52  
53      private static final String EXTRA_JAVA_HOMES = "ExtraJavaHomes";
54  
55      /** A folder containing Java homes, for example, on Windows "C:/Program Files/Eclipse Adoptium/" */
56      private static final String EXTRA_JAVA_ROOT = "ExtraJavaRoot";
57  
58      /** The default home for Java installs on Windows for Eclipse Adoptium. */
59      private static final String ADOPTIUM_WINDOWS = "C:/Program Files/Eclipse Adoptium/";
60  
61      /** The default home for Java installs on Windows for Eclipse Oracle. */
62      private static final String ORACLE_WINDOWS = "C:/Program Files/Java/";
63  
64      private static final String EXTRA_JAVA_ROOT_DEFAULT = ADOPTIUM_WINDOWS + File.pathSeparator + ORACLE_WINDOWS;
65  
66      private static final String KEY_JDK = "SOFTWARE\\JavaSoft\\Java Development Kit";
67      private static final String KEY_JDK_9 = "SOFTWARE\\JavaSoft\\JDK";
68      private static final String KEY_JRE = "SOFTWARE\\JavaSoft\\Java Runtime Environment";
69      private static final String KEY_JRE_9 = "SOFTWARE\\JavaSoft\\JRE";
70  
71      private static Stream<Path> find(final Path start, final int maxDepth, final BiPredicate<Path, BasicFileAttributes> matcher,
72              final FileVisitOption... options) {
73          // TODO Apache Commons 2.14.0: Use FilesUncheck
74          return Files.exists(start) ? Uncheck.apply(Files::find, start, maxDepth, matcher, options) : Stream.empty();
75      }
76  
77      private static JavaHome from(final String javaHome) {
78          return new JavaHome(Paths.get(javaHome));
79      }
80  
81      private static Stream<String> streamAllWindowsJavaHomes(final String keyJre) {
82          if (Advapi32Util.registryKeyExists(HKEY_LOCAL_MACHINE, keyJre)) {
83              return streamWindowsJavaHomes(keyJre, Advapi32Util.registryGetKeys(HKEY_LOCAL_MACHINE, keyJre));
84          }
85          return Stream.empty();
86      }
87  
88      private static Stream<String> streamFromCustomKey(final String key, final String defaultValue) {
89          return streamPropertyAndEnvVarValues(key, defaultValue).flatMap(s -> find(Paths.get(s), 1, (p, a) -> Files.isDirectory(p)).map(Path::toString));
90      }
91  
92      private static Stream<String> streamFromCustomKeys() {
93          final String defaultRoot = SystemUtils.IS_OS_WINDOWS ? EXTRA_JAVA_ROOT_DEFAULT : null;
94          return Stream.concat(streamPropertyAndEnvVarValues(EXTRA_JAVA_HOMES, null), streamFromCustomKey(EXTRA_JAVA_ROOT, defaultRoot));
95      }
96  
97      /**
98       * Used from {@code @MethodSource} for tests.
99       *
100      * @return a stream of JarFile.
101      */
102     public static Stream<JarEntry> streamJarEntry() {
103         return streamJavaHome().flatMap(JavaHome::streamJarEntryByExt);
104     }
105 
106     /**
107      * Used from {@code @MethodSource} for tests.
108      *
109      * @return a stream of JarFile.
110      */
111     public static Stream<JarEntry> streamJarEntryClass() {
112         return streamJavaHome().flatMap(JavaHome::streamJarEntryByExtClass);
113     }
114 
115     /**
116      * Used from {@code @MethodSource} for tests.
117      *
118      * @return a stream of JarFile.
119      */
120     public static Stream<String> streamJarEntryClassName() {
121         return streamJavaHome().flatMap(JavaHome::streamJarEntryByExtClassName);
122     }
123 
124     /**
125      * Used from {@code @MethodSource} for tests.
126      *
127      * @return a stream of JarFile.
128      */
129     public static Stream<JarFile> streamJarFile() {
130         return streamJavaHome().flatMap(JavaHome::streamJarFileByExt);
131     }
132 
133     /**
134      * Used from {@code @MethodSource} for tests.
135      *
136      * @return a stream of Java jar paths.
137      */
138     public static Stream<Path> streamJarPath() {
139         return streamJavaHome().flatMap(JavaHome::streamJarPathByExt);
140     }
141 
142     /**
143      * Used from {@code @MethodSource} for tests.
144      *
145      * @return a stream of Java homes.
146      */
147     public static Stream<JavaHome> streamJavaHome() {
148         return streamJavaHomeString().map(JavaHome::from);
149     }
150 
151     /**
152      * Used from {@code @MethodSource} for tests.
153      *
154      * @return a stream of Java homes.
155      */
156     public static Stream<String> streamJavaHomeString() {
157         final Stream<String> streamW = SystemUtils.IS_OS_WINDOWS ? streamWindowsStrings() : Stream.empty();
158         final Stream<String> streamK = Stream.concat(streamW, streamFromCustomKeys());
159         final Stream<String> streamJ = StringUtils.isEmpty(SystemUtils.JAVA_HOME) ? Stream.empty() : Stream.of(SystemUtils.JAVA_HOME);
160         return Stream.concat(streamK, streamJ);
161     }
162 
163     /**
164      * Used from {@code @MethodSource} for tests.
165      *
166      * @return a stream of Java jar paths.
167      */
168     public static Stream<ModularRuntimeImage> streamModularRuntimeImage() {
169         return streamJavaHome().map(JavaHome::getModularRuntimeImage);
170     }
171 
172     /**
173      * Used from {@code @MethodSource} for tests.
174      *
175      * @return a stream of Java jar paths.
176      */
177     public static Stream<Path> streamModulePath() {
178         return streamJavaHome().flatMap(JavaHome::streamModuleByExt);
179     }
180 
181     private static Stream<String> streamPropertyAndEnvVarValues(final String key, final String defaultValue) {
182         return Stream.concat(toPathStringStream(System.getProperty(key, defaultValue)), toPathStringStream(System.getenv(key)));
183     }
184 
185     private static Stream<String> streamWindowsJavaHomes(final String keyJavaHome, final String[] keys) {
186         final Set<String> javaHomes = new HashSet<>(keys.length);
187         for (final String key : keys) {
188             if (Advapi32Util.registryKeyExists(HKEY_LOCAL_MACHINE, keyJavaHome + "\\" + key)) {
189                 final String javaHome = Advapi32Util.registryGetStringValue(HKEY_LOCAL_MACHINE, keyJavaHome + "\\" + key, "JavaHome");
190                 if (StringUtils.isNoneBlank(javaHome) && new File(javaHome).exists()) {
191                     javaHomes.add(javaHome);
192                 }
193             }
194         }
195         return javaHomes.stream();
196     }
197 
198     private static Stream<String> streamWindowsStrings() {
199         return Stream.concat(Stream.of(KEY_JRE, KEY_JRE_9, KEY_JDK, KEY_JDK_9).flatMap(JavaHome::streamAllWindowsJavaHomes),
200                 streamPropertyAndEnvVarValues(EXTRA_JAVA_HOMES, null)).distinct();
201     }
202 
203     private static Stream<String> toPathStringStream(final String path) {
204         return StringUtils.isEmpty(path) ? Stream.empty() : Stream.of(path.split(File.pathSeparator));
205     }
206 
207     private final Path path;
208 
209     private JavaHome(final Path path) {
210         this.path = Objects.requireNonNull(path, "path");
211     }
212 
213     Stream<Path> find(final int maxDepth, final BiPredicate<Path, BasicFileAttributes> matcher, final FileVisitOption... options) {
214         return find(path, maxDepth, matcher, options);
215     }
216 
217     ModularRuntimeImage getModularRuntimeImage() {
218         return Uncheck.get(() -> new ModularRuntimeImage(path.toString()));
219     }
220 
221     Path getPath() {
222         return path;
223     }
224 
225     private Stream<Path> streamEndsWith(final String suffix) {
226         return find(10, (p, a) -> p.toString().endsWith(suffix));
227     }
228 
229     private Stream<JarEntry> streamJarEntryByExt() {
230         return streamJarFileByExt().flatMap(JarFile::stream);
231     }
232 
233     private Stream<JarEntry> streamJarEntryByExtClass() {
234         return streamJarEntryByExt().filter(je -> je.getName().endsWith(JavaClass.EXTENSION));
235     }
236 
237     private Stream<String> streamJarEntryByExtClassName() {
238         return streamJarEntryByExtClass().map(je -> Utility.pathToPackage(je.getName().substring(0, je.getName().indexOf(JavaClass.EXTENSION))));
239     }
240 
241     private Stream<JarFile> streamJarFileByExt() {
242         return streamJarPathByExt().map(this::toJarFile);
243     }
244 
245     private Stream<Path> streamJarPathByExt() {
246         return streamEndsWith(".jar");
247     }
248 
249     private Stream<Path> streamModuleByExt() {
250         return streamEndsWith(Module.EXTENSION);
251     }
252 
253     private JarFile toJarFile(final Path path) {
254         return Uncheck.get(() -> new JarFile(path.toFile()));
255     }
256 
257     @Override
258     public String toString() {
259         return path.toString();
260     }
261 }