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