2954ec76b6ff5ffb57cdfca236317c93e95128f2
[idea/community.git] / platform / util-class-loader / src / com / intellij / util / lang / UrlClassLoader.java
1 // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.util.lang;
3
4 import com.intellij.ReviseWhenPortedToJDK;
5 import com.intellij.openapi.util.text.StringUtilRt;
6 import com.intellij.util.UrlUtilRt;
7 import org.jetbrains.annotations.ApiStatus;
8 import org.jetbrains.annotations.NotNull;
9 import org.jetbrains.annotations.Nullable;
10
11 import java.io.File;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.lang.invoke.MethodHandles;
15 import java.lang.invoke.MethodType;
16 import java.lang.reflect.Field;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.net.URLClassLoader;
20 import java.nio.ByteBuffer;
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import java.security.ProtectionDomain;
24 import java.util.*;
25 import java.util.function.BiConsumer;
26 import java.util.function.BiPredicate;
27 import java.util.function.Function;
28 import java.util.function.Predicate;
29
30 /**
31  * A class loader that allows for various customizations, e.g. not locking jars or using a special cache to speed up class loading.
32  * Should be constructed using {@link #build()} method.
33  */
34 public class UrlClassLoader extends ClassLoader implements ClassPath.ClassDataConsumer {
35   private static final boolean isParallelCapable = registerAsParallelCapable();
36   private static final ClassLoader appClassLoader = UrlClassLoader.class.getClassLoader();
37
38   private static final ThreadLocal<Boolean> skipFindingResource = new ThreadLocal<>();
39
40   private final List<Path> files;
41   protected final ClassPath classPath;
42   private final ClassLoadingLocks<String> classLoadingLocks;
43   private final boolean isBootstrapResourcesAllowed;
44
45   protected final @NotNull ClassPath.ClassDataConsumer classDataConsumer =
46     ClassPath.recordLoadingTime ? new ClassPath.MeasuringClassDataConsumer(this) : this;
47
48   /**
49    * Called by the VM to support dynamic additions to the class path.
50    *
51    * @see java.lang.instrument.Instrumentation#appendToSystemClassLoaderSearch
52    */
53   @SuppressWarnings("unused")
54   final void appendToClassPathForInstrumentation(@NotNull String jar) {
55     addFiles(Collections.singletonList(Paths.get(jar)));
56   }
57
58   /**
59    * There are two definitions of ClassPath class.
60    * First one from app class loader that used by bootstrap.
61    * Another one from core class loader that created as result of creating of plugin class loader.
62    * Core class loader doesn't use bootstrap class loader as parent, instead, only platform classloader is used (only JDK classes).
63    */
64   @ApiStatus.Internal
65   public final @NotNull ClassPath getClassPath() {
66     return classPath;
67   }
68
69   @ApiStatus.Internal
70   public static @NotNull Collection<Map.Entry<String, Path>> getLoadedClasses() {
71     return ClassPath.getLoadedClasses();
72   }
73
74   /**
75    * See com.intellij.TestAll#getClassRoots()
76    */
77   public final @NotNull List<Path> getBaseUrls() {
78     return classPath.getBaseUrls();
79   }
80
81   // called via reflection
82   @SuppressWarnings({"unused", "MethodMayBeStatic"})
83   public final @NotNull Map<String, Long> getLoadingStats() {
84     return ClassPath.getLoadingStats();
85   }
86
87   public static @NotNull UrlClassLoader.Builder build() {
88     return new Builder();
89   }
90
91   /** @deprecated use {@link #build()} (left for compatibility with `java.system.class.loader` setting) */
92   @Deprecated
93   @ReviseWhenPortedToJDK("9")
94   public UrlClassLoader(@NotNull ClassLoader parent) {
95     this(createDefaultBuilderForJdk(parent), null, isParallelCapable);
96
97     registerInClassLoaderValueMap(parent, this);
98   }
99
100   protected static void registerInClassLoaderValueMap(@NotNull ClassLoader parent, @NotNull ClassLoader classLoader) {
101     // without this ToolProvider.getSystemJavaCompiler() does not work in jdk 9+
102     try {
103       Field f = ClassLoader.class.getDeclaredField("classLoaderValueMap");
104       f.setAccessible(true);
105       f.set(classLoader, f.get(parent));
106     }
107     catch (Exception ignored) {
108     }
109   }
110
111   protected static @NotNull UrlClassLoader.Builder createDefaultBuilderForJdk(@NotNull ClassLoader parent) {
112     Builder configuration = new Builder();
113
114     if (parent instanceof URLClassLoader) {
115       URL[] urls = ((URLClassLoader)parent).getURLs();
116       configuration.files = new ArrayList<>(urls.length);
117       for (URL url : urls) {
118         configuration.files.add(Paths.get(url.getPath()));
119       }
120     }
121     else {
122       String[] parts = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
123       configuration.files = new ArrayList<>(parts.length);
124       for (String s : parts) {
125         configuration.files.add(new File(s).toPath());
126       }
127     }
128
129     configuration.parent = parent.getParent();
130     configuration.lockJars = true;
131     configuration.useCache = true;
132     configuration.isClassPathIndexEnabled = true;
133     configuration.isBootstrapResourcesAllowed = Boolean.parseBoolean(System.getProperty("idea.allow.bootstrap.resources", "true"));
134     configuration.autoAssignUrlsWithProtectionDomain();
135     return configuration;
136   }
137
138   protected UrlClassLoader(@NotNull UrlClassLoader.Builder builder, boolean isParallelCapable) {
139     this(builder, null, isParallelCapable);
140   }
141
142   /**
143    * @deprecated Do not extend UrlClassLoader. If you cannot avoid it, use {@link #UrlClassLoader(Builder, boolean)}.
144    */
145   @ApiStatus.ScheduledForRemoval(inVersion = "2022.1")
146   @Deprecated
147   protected UrlClassLoader(@NotNull UrlClassLoader.Builder builder) {
148     this(builder, null, false);
149   }
150
151   protected UrlClassLoader(@NotNull UrlClassLoader.Builder builder,
152                            @Nullable Function<Path, ResourceFile> resourceFileFactory,
153                            boolean isParallelCapable) {
154     this(builder, resourceFileFactory, isParallelCapable, false);
155   }
156
157   protected UrlClassLoader(@NotNull UrlClassLoader.Builder builder,
158                            @Nullable Function<Path, ResourceFile> resourceFileFactory,
159                            boolean isParallelCapable,
160                            boolean isMimicJarUrlConnectionNeeded) {
161     super(builder.parent);
162
163     files = builder.files;
164
165     Set<Path> urlsWithProtectionDomain = builder.pathsWithProtectionDomain;
166     if (urlsWithProtectionDomain == null) {
167       urlsWithProtectionDomain = Collections.emptySet();
168     }
169
170     classPath = new ClassPath(files, urlsWithProtectionDomain, builder, resourceFileFactory, isMimicJarUrlConnectionNeeded);
171
172     isBootstrapResourcesAllowed = builder.isBootstrapResourcesAllowed;
173     classLoadingLocks = isParallelCapable ? new ClassLoadingLocks<>() : null;
174   }
175
176   protected UrlClassLoader(@NotNull List<Path> files, @NotNull ClassPath classPath) {
177     super(null);
178
179     this.files = files;
180     this.classPath = classPath;
181     isBootstrapResourcesAllowed = false;
182     classLoadingLocks = new ClassLoadingLocks<>();
183   }
184
185   /** @deprecated adding URLs to a classloader at runtime could lead to hard-to-debug errors */
186   @Deprecated
187   public final void addURL(@NotNull URL url) {
188     addFiles(Collections.singletonList(Paths.get(url.getPath())));
189   }
190
191   @ApiStatus.Internal
192   public final void addFiles(@NotNull List<Path> files) {
193     classPath.addFiles(files);
194     this.files.addAll(files);
195   }
196
197   public final @NotNull List<URL> getUrls() {
198     List<URL> result = new ArrayList<>();
199     for (Path file : files) {
200       try {
201         result.add(file.toUri().toURL());
202       }
203       catch (MalformedURLException ignored) {
204       }
205     }
206     return result;
207   }
208
209   public final @NotNull List<Path> getFiles() {
210     return Collections.unmodifiableList(files);
211   }
212
213   public final boolean hasLoadedClass(String name) {
214     Class<?> aClass = findLoadedClass(name);
215     return aClass != null && aClass.getClassLoader() == this;
216   }
217
218   @Override
219   protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
220     if (name.startsWith("com.intellij.util.lang.")) {
221       return appClassLoader.loadClass(name);
222     }
223
224     Class<?> clazz;
225     try {
226       clazz = classPath.findClass(name, classDataConsumer);
227     }
228     catch (IOException e) {
229       throw new ClassNotFoundException(name, e);
230     }
231     if (clazz == null) {
232       throw new ClassNotFoundException(name);
233     }
234     return clazz;
235   }
236
237   private void definePackageIfNeeded(@NotNull String name, Loader loader) throws IOException {
238     int lastDotIndex = name.lastIndexOf('.');
239     if (lastDotIndex == -1) {
240       return;
241     }
242
243     String packageName = name.substring(0, lastDotIndex);
244     // check if package already loaded
245     if (isPackageDefined(packageName)) {
246       return;
247     }
248
249     try {
250       Map<Loader.Attribute, String> attributes = loader.getAttributes();
251       if (attributes == null || attributes.isEmpty()) {
252         definePackage(packageName, null, null, null, null, null, null, null);
253       }
254       else {
255         definePackage(packageName,
256                       attributes.get(Loader.Attribute.SPEC_TITLE),
257                       attributes.get(Loader.Attribute.SPEC_VERSION),
258                       attributes.get(Loader.Attribute.SPEC_VENDOR),
259                       attributes.get(Loader.Attribute.IMPL_TITLE),
260                       attributes.get(Loader.Attribute.IMPL_VERSION),
261                       attributes.get(Loader.Attribute.IMPL_VENDOR),
262                       null);
263       }
264     }
265     catch (IllegalArgumentException ignore) {
266       // do nothing, package already defined by some another thread
267     }
268   }
269
270   protected boolean isPackageDefined(String packageName) {
271     //noinspection deprecation
272     return getPackage(packageName) != null;
273   }
274
275   protected ProtectionDomain getProtectionDomain() {
276     return null;
277   }
278
279   @Override
280   public boolean isByteBufferSupported(@NotNull String name, @Nullable ProtectionDomain protectionDomain) {
281     return true;
282   }
283
284   @Override
285   public Class<?> consumeClassData(@NotNull String name, byte[] data, Loader loader, @Nullable ProtectionDomain protectionDomain)
286     throws IOException {
287     definePackageIfNeeded(name, loader);
288     return super.defineClass(name, data, 0, data.length, protectionDomain == null ? getProtectionDomain() : protectionDomain);
289   }
290
291   @Override
292   public Class<?> consumeClassData(@NotNull String name, ByteBuffer data, Loader loader, @Nullable ProtectionDomain protectionDomain)
293     throws IOException {
294     definePackageIfNeeded(name, loader);
295     return super.defineClass(name, data, protectionDomain == null ? getProtectionDomain() : protectionDomain);
296   }
297
298   @Override
299   public @Nullable URL findResource(@NotNull String name) {
300     if (skipFindingResource.get() != null) {
301       return null;
302     }
303     Resource resource = doFindResource(name);
304     return resource != null ? resource.getURL() : null;
305   }
306
307   @Override
308   public @Nullable InputStream getResourceAsStream(@NotNull String name) {
309     Resource resource = doFindResource(name);
310     if (resource != null) {
311       try {
312         return resource.getInputStream();
313       }
314       catch (IOException e) {
315         logError("Cannot load resource " + name, e);
316         return null;
317       }
318     }
319
320     if (isBootstrapResourcesAllowed) {
321       skipFindingResource.set(Boolean.TRUE);
322       try {
323         URL url = super.getResource(name);
324         if (url != null) {
325           try {
326             return url.openStream();
327           }
328           catch (IOException ignore) { }
329         }
330       }
331       finally {
332         skipFindingResource.set(null);
333       }
334     }
335
336     return null;
337   }
338
339   private @Nullable Resource doFindResource(@NotNull String name) {
340     String canonicalPath = toCanonicalPath(name);
341     Resource resource = classPath.findResource(canonicalPath);
342     if (resource == null && canonicalPath.startsWith("/") && classPath.findResource(canonicalPath.substring(1)) != null) {
343       logError("Calling `ClassLoader#getResource` with leading slash doesn't work; strip", new IllegalArgumentException(name));
344     }
345     return resource;
346   }
347
348   public final void processResources(@NotNull String dir,
349                                      @NotNull Predicate<? super String> fileNameFilter,
350                                      @NotNull BiConsumer<? super String, ? super InputStream> consumer) throws IOException {
351     classPath.processResources(dir, fileNameFilter, consumer);
352   }
353
354   @Override
355   protected @NotNull Enumeration<URL> findResources(@NotNull String name) throws IOException {
356     return classPath.getResources(name);
357   }
358
359   @Override
360   protected final @NotNull Object getClassLoadingLock(String className) {
361     return classLoadingLocks == null ? this : classLoadingLocks.getOrCreateLock(className);
362   }
363
364   @ApiStatus.Internal
365   public @Nullable BiPredicate<String, Boolean> resolveScopeManager;
366
367   public @Nullable Class<?> loadClassInsideSelf(@NotNull String name, boolean forceLoadFromSubPluginClassloader) throws IOException {
368     synchronized (getClassLoadingLock(name)) {
369       Class<?> c = findLoadedClass(name);
370       if (c != null) {
371         return c;
372       }
373
374       if (!forceLoadFromSubPluginClassloader) {
375         // "self" makes sense for PluginClassLoader, but not for UrlClassLoader - our parent it is implementation detail
376         ClassLoader parent = getParent();
377         if (parent != null) {
378           try {
379             c = parent.loadClass(name);
380           }
381           catch (ClassNotFoundException ignore) {
382           }
383         }
384
385         if (c != null) {
386           return c;
387         }
388       }
389       return classPath.findClass(name, classDataConsumer);
390     }
391   }
392
393   /**
394    * An interface for a pool to store internal caches that can be shared between different class loaders,
395    * if they contain the same URLs in their class paths.<p/>
396    *
397    * The implementation is subject to change so one shouldn't rely on it.
398    *
399    * @see #createCachePool()
400    * @see Builder#useCache
401    */
402   public interface CachePool { }
403
404   /**
405    * @return a new pool to be able to share internal caches between different class loaders, if they contain the same URLs
406    * in their class paths.
407    */
408   public static @NotNull CachePool createCachePool() {
409     return new CachePoolImpl();
410   }
411
412   @SuppressWarnings("DuplicatedCode")
413   protected static String toCanonicalPath(@NotNull String path) {
414     if (path.isEmpty()) {
415       return path;
416     }
417
418     if (path.charAt(0) == '.') {
419       if (path.length() == 1) {
420         return "";
421       }
422       char c = path.charAt(1);
423       if (c == '/') {
424         path = path.substring(2);
425       }
426     }
427
428     // trying to speed up the common case when there are no "//" or "/."
429     int index = -1;
430     do {
431       index = path.indexOf('/', index + 1);
432       char next = index == path.length() - 1 ? 0 : path.charAt(index + 1);
433       if (next == '.' || next == '/') {
434         break;
435       }
436     }
437     while (index != -1);
438     if (index == -1) {
439       return path;
440     }
441
442     StringBuilder result = new StringBuilder(path.length());
443     int start = processRoot(path, result);
444     int dots = 0;
445     boolean separator = true;
446
447     for (int i = start; i < path.length(); ++i) {
448       char c = path.charAt(i);
449       if (c == '/') {
450         if (!separator) {
451           processDots(result, dots, start);
452           dots = 0;
453         }
454         separator = true;
455       }
456       else if (c == '.') {
457         if (separator || dots > 0) {
458           ++dots;
459         }
460         else {
461           result.append('.');
462         }
463         separator = false;
464       }
465       else {
466         while (dots > 0) {
467           result.append('.');
468           dots--;
469         }
470         result.append(c);
471         separator = false;
472       }
473     }
474
475     if (dots > 0) {
476       processDots(result, dots, start);
477     }
478     return result.toString();
479   }
480
481   @SuppressWarnings("DuplicatedCode")
482   private static void processDots(@NotNull StringBuilder result, int dots, int start) {
483     if (dots == 2) {
484       int pos = -1;
485       if (!StringUtilRt.endsWith(result, "/../") && !"../".contentEquals(result)) {
486         pos = StringUtilRt.lastIndexOf(result, '/', start, result.length() - 1);
487         if (pos >= 0) {
488           ++pos;  // separator found, trim to next char
489         }
490         else if (start > 0) {
491           pos = start;  // path is absolute, trim to root ('/..' -> '/')
492         }
493         else if (result.length() > 0) {
494           pos = 0;  // path is relative, trim to default ('a/..' -> '')
495         }
496       }
497       if (pos >= 0) {
498         result.delete(pos, result.length());
499       }
500       else {
501         result.append("../");  // impossible to traverse, keep as-is
502       }
503     }
504     else if (dots != 1) {
505       for (int i = 0; i < dots; i++) {
506         result.append('.');
507       }
508       result.append('/');
509     }
510   }
511
512   @SuppressWarnings("DuplicatedCode")
513   private static int processRoot(@NotNull String path, @NotNull StringBuilder result) {
514     if (!path.isEmpty() && path.charAt(0) == '/') {
515       result.append('/');
516       return 1;
517     }
518
519     if (path.length() > 2 && path.charAt(1) == ':' && path.charAt(2) == '/') {
520       result.append(path, 0, 3);
521       return 3;
522     }
523
524     return 0;
525   }
526
527   @SuppressWarnings({"UseOfSystemOutOrSystemErr", "SameParameterValue"})
528   private void logError(String message, Throwable t) {
529     try {
530       Class<?> logger = Class.forName("com.intellij.openapi.diagnostic.Logger", false, this);
531       MethodHandles.Lookup lookup = MethodHandles.lookup();
532       Object instance = lookup.findStatic(logger, "getInstance", MethodType.methodType(logger, Class.class)).invoke(getClass());
533       lookup.findVirtual(logger, "error", MethodType.methodType(void.class, String.class, Throwable.class))
534         .bindTo(instance)
535         .invokeExact(message, t);
536     }
537     catch (Throwable tt) {
538       tt.addSuppressed(t);
539       tt.printStackTrace(System.err);
540     }
541   }
542
543   // work around corrupted URLs produced by File.getURL()
544   // public for test
545   public static @NotNull String urlToFilePath(@NotNull String url) {
546     int start = url.startsWith("file:") ? "file:".length() : 0;
547     int end = url.indexOf("!/");
548     if (url.charAt(start) == '/') {
549       // trim leading slashes before drive letter
550       if (url.length() > (start + 2) && url.charAt(start + 2) == ':') {
551         start++;
552       }
553     }
554     return UrlUtilRt.unescapePercentSequences(url, start, end < 0 ? url.length() : end).toString();
555   }
556
557   public static final class Builder {
558     private static final boolean isClassPathIndexEnabledGlobalValue = Boolean.parseBoolean(System.getProperty("idea.classpath.index.enabled", "true"));
559
560     List<Path> files = Collections.emptyList();
561     @Nullable Set<Path> pathsWithProtectionDomain;
562     ClassLoader parent;
563     boolean lockJars = true;
564     boolean useCache;
565     boolean isClassPathIndexEnabled;
566     boolean isBootstrapResourcesAllowed;
567     boolean errorOnMissingJar = true;
568     @Nullable CachePoolImpl cachePool;
569     Predicate<? super Path> cachingCondition;
570
571     Builder() { }
572
573     /**
574      * @deprecated Use {@link #files(List)}. Using of {@link URL} is discouraged in favor of modern {@link Path}.
575      */
576     @ApiStatus.ScheduledForRemoval(inVersion = "2022.2")
577     @Deprecated
578     public @NotNull UrlClassLoader.Builder urls(@NotNull List<URL> urls) {
579       List<Path> files = new ArrayList<>(urls.size());
580       for (URL url : urls) {
581         files.add(Paths.get(urlToFilePath(url.getPath())));
582       }
583       this.files = files;
584       return this;
585     }
586
587     public @NotNull UrlClassLoader.Builder files(@NotNull List<Path> paths) {
588       this.files = paths;
589       return this;
590     }
591
592     /**
593      * Marks URLs that are signed by Sun/Oracle and whose signatures must be verified.
594      */
595     @NotNull UrlClassLoader.Builder urlsWithProtectionDomain(@NotNull Set<Path> value) {
596       pathsWithProtectionDomain = value;
597       return this;
598     }
599
600     public @NotNull UrlClassLoader.Builder parent(ClassLoader parent) {
601       this.parent = parent;
602       return this;
603     }
604
605     /**
606      * ZipFile handles opened in JarLoader will be kept in SoftReference. Depending on OS, the option significantly speeds up classloading
607      * from libraries. Caveat: for Windows opened handle will lock the file preventing its modification.
608      * Thus, the option is recommended when jars are not modified or process that uses this option is transient.
609      */
610     public @NotNull UrlClassLoader.Builder allowLock(boolean lockJars) {
611       this.lockJars = lockJars;
612       return this;
613     }
614
615     /**
616      * Build backward index of packages / class or resource names that allows avoiding IO during classloading.
617      */
618     public @NotNull UrlClassLoader.Builder useCache() {
619       useCache = true;
620       return this;
621     }
622
623     public @NotNull UrlClassLoader.Builder useCache(boolean useCache) {
624       this.useCache = useCache;
625       return this;
626     }
627
628     /**
629      * FileLoader will save list of files / packages under its root and use this information instead of walking filesystem for
630      * speedier classloading. Should be used only when the caches could be properly invalidated, e.g. when new file appears under
631      * FileLoader's root. Currently, the flag is used for faster unit test / developed Idea running, because Idea's make (as of 14.1) ensures deletion of
632      * such information upon appearing new file for output root.
633      * N.b. Idea make does not ensure deletion of cached information upon deletion of some file under local root but false positives are not a
634      * logical error since code is prepared for that and disk access is performed upon class / resource loading.
635      * See also Builder#usePersistentClasspathIndexForLocalClassDirectories.
636      */
637     public @NotNull UrlClassLoader.Builder usePersistentClasspathIndexForLocalClassDirectories() {
638       this.isClassPathIndexEnabled = isClassPathIndexEnabledGlobalValue;
639       return this;
640     }
641
642     /**
643      * Requests the class loader being built to use cache and, if possible, retrieve and store the cached data from a special cache pool
644      * that can be shared between several loaders.
645      *
646      * @param pool      cache pool
647      * @param condition a custom policy to provide a possibility to prohibit caching for some URLs.
648      */
649     public @NotNull UrlClassLoader.Builder useCache(@NotNull UrlClassLoader.CachePool pool, @NotNull Predicate<? super Path> condition) {
650       useCache = true;
651       cachePool = (CachePoolImpl)pool;
652       cachingCondition = condition;
653       return this;
654     }
655
656     public @NotNull UrlClassLoader.Builder noPreload() {
657       return this;
658     }
659
660     public @NotNull UrlClassLoader.Builder allowBootstrapResources() {
661       return allowBootstrapResources(true);
662     }
663
664     public @NotNull UrlClassLoader.Builder allowBootstrapResources(boolean allowBootstrapResources) {
665       isBootstrapResourcesAllowed = allowBootstrapResources;
666       return this;
667     }
668
669     public @NotNull UrlClassLoader.Builder setLogErrorOnMissingJar(boolean log) {
670       errorOnMissingJar = log;
671       return this;
672     }
673
674     public @NotNull UrlClassLoader.Builder autoAssignUrlsWithProtectionDomain() {
675       Set<Path> result = null;
676       for (Path path : files) {
677         if (isUrlNeedsProtectionDomain(path)) {
678           if (result == null) {
679             result = new HashSet<>();
680           }
681           result.add(path);
682         }
683       }
684       pathsWithProtectionDomain = result;
685       return this;
686     }
687
688     public @NotNull UrlClassLoader get() {
689       return new UrlClassLoader(this, null, isParallelCapable);
690     }
691
692     private static boolean isUrlNeedsProtectionDomain(@NotNull Path file) {
693       String path = file.toString();
694       // BouncyCastle needs a protection domain
695       if (path.endsWith(".jar")) {
696         int offset = path.lastIndexOf(file.getFileSystem().getSeparator().charAt(0)) + 1;
697         //noinspection SpellCheckingInspection
698         if (path.startsWith("bcprov-", offset) || path.startsWith("bcpkix-", offset)) {
699           return true;
700         }
701       }
702       return false;
703     }
704   }
705 }