diff --git a/.classpath b/.classpath
index 5576b4361..dff964fcf 100644
--- a/.classpath
+++ b/.classpath
@@ -22,6 +22,7 @@
+
@@ -36,5 +37,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..4391ce004
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: lukehutch
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..15cffe75f
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ time: "02:00"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ time: "03:00"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..d87baada3
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,29 @@
+name: Java CI
+
+on:
+ pull_request:
+ branches:
+ - latest
+ push:
+ branches:
+ - latest
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-latest, windows-latest, macos-latest ]
+ java: [ '8', '11', '13', '15', '16', '17', '18', '19' ]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: ${{ matrix.java }}
+ - name: print Java version
+ run: java -version
+ - name: Build with Maven
+ run: ./mvnw --no-transfer-progress -B clean test
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 000000000..8006381f8
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,47 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "latest" ]
+ pull_request:
+ branches: [ "latest" ]
+ schedule:
+ - cron: "47 11 * * 6"
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ java ]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Java SDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: 8
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ queries: +security-and-quality
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{ matrix.language }}"
diff --git a/.gitignore b/.gitignore
index 46cc9ac4a..b39e9e349 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,12 @@
+!.gitignore
+
pom.xml.releaseBackup
release.properties
*.class
!CompiledWithJDK8.class
+!module-info.class
+
/target/
bin/
tmp/
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..44f3cf2c1
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
diff --git a/.project b/.project
index b8e1f6a83..8b3ccf423 100644
--- a/.project
+++ b/.project
@@ -17,8 +17,18 @@
- org.eclipse.pde.PluginNatureorg.eclipse.m2e.core.maven2Natureorg.eclipse.jdt.core.javanature
+
+
+ 1700088758021
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
diff --git a/.travis.yml b/.travis.yml
index 935fbadb5..3a0a18254 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,16 @@
language: java
jdk:
- openjdk8
- - openjdk11 # LTS
- - openjdk-ea # Early access
+ - openjdk11
+ - openjdk12
+ - openjdk13
+ #- openjdk-ea
-sudo: true # https://github.com/travis-ci/travis-ci/issues/6593
+#ignore default install step
+install: true
cache:
directories:
- $HOME/.m2
+
+script: ./mvnw clean verify
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..7b016a89f
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.compile.nullAnalysis.mode": "automatic"
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE-ClassGraph.txt
similarity index 96%
rename from LICENSE
rename to LICENSE-ClassGraph.txt
index fcdf34dec..eddec3610 100644
--- a/LICENSE
+++ b/LICENSE-ClassGraph.txt
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Luke Hutchison
+Copyright (c) 2019 Luke Hutchison
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 4fe571e13..7f48fb220 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# ClassGraph
-
+
-ClassGraph (formerly **FastClasspathScanner**) is an uber-fast, ultra-lightweight, parallelized classpath scanner, module scanner, and build-time/runtime annotation processor for Java, Scala, Kotlin and other JVM languages.
+ClassGraph is an uber-fast parallelized classpath scanner and module scanner for Java, Scala, Kotlin and other JVM languages.
-| _ClassGraph won a Duke's Choice Award (a recognition of the most useful and/or innovative software in the Java ecosystem) at Oracle Code One 2018._ Thanks to all the users who have reported bugs, requested features, offered suggestions, and submitted pull requests to help get ClassGraph to where it is today. |
+| _ClassGraph won a Duke's Choice Award (a recognition of the most useful and/or innovative software in the Java ecosystem) at Oracle Code One 2018, and a Google Open Source Peer Bonus award in 2022._ Thanks to all the users who have reported bugs, requested features, offered suggestions, and submitted pull requests to help get ClassGraph to where it is today. |
|-----------------------------|
[-blue.svg)](#)
@@ -15,20 +15,25 @@ ClassGraph (formerly **FastClasspathScanner**) is an uber-fast, ultra-lightweigh
[](https://github.com/classgraph/classgraph/issues/)
[](https://lgtm.com/projects/g/classgraph/classgraph/alerts/)
[](https://lgtm.com/projects/g/classgraph/classgraph/context:java)
-[](https://www.codacy.com/app/lukehutch/classgraph)
+[](https://www.codacy.com/gh/classgraph/classgraph/dashboard?utm_source=github.com&utm_medium=referral&utm_content=classgraph/classgraph&utm_campaign=Badge_Grade)
+
+[](#)
+[](https://github.com/classgraph/classgraph/network/dependents?package_id=UGFja2FnZS0xODcxNTE4NTM%3D)
+[](https://seladb.github.io/StarTrack-js/#/preload?r=classgraph,classgraph)
[](https://mvnrepository.com/artifact/io.github.classgraph/classgraph)
[](https://javadoc.io/doc/io.github.classgraph/classgraph)
-[](https://github.com/classgraph/classgraph/blob/master/LICENSE)
-[](#)
+[](https://gitter.im/classgraph/Lobby)
-[](https://seladb.github.io/StarTrack-js/?u=classgraph&r=classgraph)
-[](https://gitter.im/classgraph/Lobby)
+[](https://github.com/classgraph/classgraph/blob/master/LICENSE)
+
+| ClassGraph is stable and mature, and has a low bug report rate, despite being used by hundreds of projects. |
+|-----------------------------|
### ClassGraph vs. Java Introspection
-ClassGraph has the ability to "invert" the Java class and/or reflection API, or has the ability to index classes and resources. For example, the Java class and reflection API can tell you the interfaces implemented by a given class, or can give you the list of annotations on a class; ClassGraph can find **all classes that implement a given interface**, or can find **all classes that are annotated with a given annotation**. The Java API can load the content of a resource file with a specific path in a specific ClassLoader, but ClassGraph can find and load **all resources in all classloaders with paths matching a given pattern**.
+ClassGraph has the ability to "invert" the Java class and/or reflection API, or has the ability to index classes and resources. For example, the Java class and reflection API can tell you the superclass of a given class, or the interfaces implemented by a given class, or can give you the list of annotations on a class; ClassGraph can find **all classes that extend a given class** (all subclasses of a given class), or **all classes that implement a given interface**, or **all classes that are annotated with a given annotation**. The Java API can load the content of a resource file with a specific path in a specific ClassLoader, but ClassGraph can find and load **all resources in all classloaders with paths matching a given pattern**.
### Examples
@@ -39,10 +44,10 @@ String pkg = "com.xyz";
String routeAnnotation = pkg + ".Route";
try (ScanResult scanResult =
new ClassGraph()
- .verbose() // Log to stderr
- .enableAllInfo() // Scan classes, methods, fields, annotations
- .whitelistPackages(pkg) // Scan com.xyz and subpackages (omit to scan all packages)
- .scan()) { // Start the scan
+ .verbose() // Log to stderr
+ .enableAllInfo() // Scan classes, methods, fields, annotations
+ .acceptPackages(pkg) // Scan com.xyz and subpackages (omit to scan all packages)
+ .scan()) { // Start the scan
for (ClassInfo routeClassInfo : scanResult.getClassesWithAnnotation(routeAnnotation)) {
AnnotationInfo routeAnnotationInfo = routeClassInfo.getAnnotationInfo(routeAnnotation);
List routeParamVals = routeAnnotationInfo.getParameterValues();
@@ -56,10 +61,11 @@ try (ScanResult scanResult =
The following code finds all JSON files in `META-INF/config` in all ClassLoaders or modules, and calls the method `readJson(String path, String content)` with the path and content of each file.
```java
-try (ScanResult scanResult = new ClassGraph().whitelistPathsNonRecursive("META-INF/config").scan()) {
- scanResult.getResourcesWithExtension("json").forEachByteArray((Resource res, byte[] content) -> {
- readJson(res.getPath(), new String(content, StandardCharsets.UTF_8));
- });
+try (ScanResult scanResult = new ClassGraph().acceptPathsNonRecursive("META-INF/config").scan()) {
+ scanResult.getResourcesWithExtension("json")
+ .forEachByteArray((Resource res, byte[] content) -> {
+ readJson(res.getPath(), new String(content, StandardCharsets.UTF_8));
+ });
}
```
@@ -69,49 +75,109 @@ See the [code examples](https://github.com/classgraph/classgraph/wiki/Code-examp
ClassGraph provides a number of important capabilities to the JVM ecosystem:
-* ClassGraph has the ability to build a model in memory of the entire relatedness graph of all classes, annotations, interfaces, methods and fields that are visible to the JVM. This graph can be [queried in a wide range of ways](https://github.com/classgraph/classgraph/wiki/Code-examples), enabling some degree of *metaprogramming* in JVM languages -- the ability to write code that analyzes or responds to the properties of other code.
+* ClassGraph has the ability to build a model in memory of the entire relatedness graph of all classes, annotations, interfaces, methods and fields that are visible to the JVM, and can even read [type annotations](https://docs.oracle.com/javase/tutorial/java/annotations/type_annotations.html). This graph of class metadata can be [queried in a wide range of ways](https://github.com/classgraph/classgraph/wiki/Code-examples), enabling some degree of *metaprogramming* in JVM languages -- the ability to write code that analyzes or responds to the properties of other code.
* ClassGraph reads the classfile bytecode format directly, so it can read all information about classes without loading or initializing them.
* ClassGraph is fully compatible with the new JPMS module system (Project Jigsaw / JDK 9+), i.e. it can scan both the traditional classpath and the module path. However, the code is also fully backwards compatible with JDK 7 and JDK 8 (i.e. the code is compiled in Java 7 compatibility mode, and all interaction with the module system is implemented via reflection for backwards compatibility).
-* ClassGraph scans the classpath or module path using [carefully optimized multithreaded code](https://github.com/classgraph/classgraph/wiki/How-fast-is-ClassGraph%3F) for the shortest possible scan times, and it runs as close as possible to I/O bandwidth limits, even on a fast SSD.
+* ClassGraph scans the classpath or module path using [carefully optimized multithreaded code](https://github.com/classgraph/classgraph/wiki/How-fast-is-ClassGraph) for the shortest possible scan times, and it runs as close as possible to I/O bandwidth limits, even on a fast SSD.
* ClassGraph handles more [classpath specification mechanisms](https://github.com/classgraph/classgraph/wiki/Classpath-specification-mechanisms) found in the wild than any other classpath scanner, making code that depends upon ClassGraph maximally portable.
* ClassGraph can scan the classpath and module path either at runtime or [at build time](https://github.com/classgraph/classgraph/wiki/Build-Time-Scanning) (e.g. to implement annotation processing for Android).
* ClassGraph can [find classes that are duplicated or defined more than once in the classpath or module path](https://github.com/classgraph/classgraph/wiki/Code-examples#find-all-duplicate-class-definitions-in-the-classpath-or-module-path), which can help find the cause of strange class resolution behaviors.
-* ClassGraph can [create GraphViz visualizations of the class graph structure](https://github.com/classgraph/classgraph/wiki/API:-ClassInfo#generating-a-graphviz-dot-file-for-class-graph-visualization), which can help with code understanding: (click to enlarge | [see graph legend here](https://github.com/classgraph/classgraph/blob/master/src/test/java/com/xyz/classgraph-fig-legend.png))
+* ClassGraph can [create GraphViz visualizations of the class graph structure](https://github.com/classgraph/classgraph/wiki/ClassInfo-API#generating-a-graphviz-dot-file-for-class-graph-visualization), which can help with code understanding: (click to enlarge; [see graph legend here](https://github.com/classgraph/classgraph/blob/master/src/test/java/com/xyz/classgraph-fig-legend.png))
-
+
-## Documentation
+## Downloading
-[See the wiki for complete documentation and usage information.](https://github.com/classgraph/classgraph/wiki)
+### Maven dependency
+
+Replace `X.Y.Z` below with the latest [release number](https://github.com/classgraph/classgraph/releases). (Alternatively, you could use `LATEST` in place of `X.Y.Z` instead if you just want to grab the latest version -- although be aware that that may lead to non-reproducible builds, since the ClassGraph version number could increase at any time. You could use [dependency locking](https://docs.gradle.org/current/userguide/dependency_locking.html) to address this.)
+
+```xml
+
+ io.github.classgraph
+ classgraph
+ X.Y.Z
+
+```
+
+See instructions for [use as a module](https://github.com/classgraph/classgraph/wiki#use-as-a-module).
+
+### Running on JDK 16+
+
+The JDK team decided to start enforcing strong encapsulation in JDK 16+. That will means that by default, ClassGraph will not be able to find the classpath of your project, if all of the following are true:
+
+* You are running on JDK 16+
+* You are using a legacy classloader (rather than the module system)
+* Your classloader does not expose its classpath via a public field or method (i.e. the full classpath can only be determined by reflection of private fields or methods).
+
+If your ClassGraph code works in JDK versions less than 16 but breaks in JDK 16+ (meaning that ClassGraph can no longer find your classes), you have probably run into this problem.
+
+ClassGraph can use either of the following libraries to silently circumvent all of Java's security mechanisms (visibility/access checks, security manager restrictions, and strong encapsulation), in order to read the classpath from private fields and methods of classloaders.
+
+* Narcissus by Luke Hutchison (@lukehutch), author of ClassGraph
+* JVM-Driver by Roberto Gentili (@burningwave), author of [Burningwave Core](https://github.com/burningwave/core).
+
+**To clarify, you do *only* need to use Narcissus or JVM-driver if ClassGraph cannot find the classpath elements from your classloader, due to the enforcement of strong encapsulation, or if it is problematic that you are getting reflection access warnings on the console.**
-## Status
+To use one of these libraries:
-**FastClasspathScanner was renamed to ClassGraph, and released as version 4**.
+* Upgrade ClassGraph to the latest version
+* Either:
+ 1. Add the [Narcissus](https://github.com/toolfactory/narcissus) library to your project as an extra dependency (this includes a native library, and only Linux x86/x64, Windows x86/x64, and Mac OS X x64 are currently supported -- feel free to contribute native code builds for other platforms or architectures).
+ 2. Set `ClassGraph.CIRCUMVENT_ENCAPSULATION = CircumventEncapsulationMethod.NARCISSUS;` before interacting with ClassGraph in any other way (this will load the Narcissus library as ClassGraph's reflection driver).
+* Or:
+ 1. Add the [JVM-Driver](https://github.com/toolfactory/jvm-driver) library to your project as an extra dependency (this uses only Java code and works to bypass encapsulation without native code for all JDK versions between 8 and 18).
+ 2. Set `ClassGraph.CIRCUMVENT_ENCAPSULATION = CircumventEncapsulationMethod.JVM_DRIVER;` before interacting with ClassGraph in any other way (this will load the JVM-Driver library as ClassGraph's reflection driver).
-ClassGraph has a completely revamped API. See the [porting notes](https://github.com/classgraph/classgraph/wiki/Porting-FastClasspathScanner-code-to-ClassGraph) for information on porting from the older FastClasspathScanner version 3 API.
+JDK 16's strong encapsulation is just the first step of trying to lock down Java's internals, so further restrictions are possible (e.g. it is likely that `setAccessible(true)` will fail in future JDK releases, even within a module, and probably the JNI API will be locked down soon, making Narcissus require a commandline flag to work). Therefore, **please convince your upstream runtime environment maintainers to expose the full classpath from their classloader using a public method or field, otherwise ClassGraph may stop working for your runtime environment in the future.**
-In particular, the Maven group id has changed from `io.github.lukehutch.fast-classpath-scanner` to **`io.github.classgraph`** in version 4. Please see the new [Maven dependency rule](https://github.com/classgraph/classgraph/wiki) and module "requires" line in the Wiki documentation.
+### Pre-built JARs
+
+You can get pre-built JARs (usable on JRE 7 or newer) from [Sonatype](https://oss.sonatype.org/#nexus-search;quick~io.github.classgraph).
+
+### Building from source
+
+ClassGraph must be built on JDK 8 or newer (due to the presence of `@FunctionalInterface` annotations on some interfaces), but is built using `-target 1.7` for backwards compatibility with JRE 7.
+
+The following commands will build the most recent version of ClassGraph from git master. The compiled package will then be in the "classgraph/target" directory.
+
+```bash
+git clone https://github.com/classgraph/classgraph.git
+cd classgraph
+export JAVA_HOME=/usr/java/default # Or similar -- Maven needs JAVA_HOME
+./mvnw -Dmaven.test.skip=true package
+```
+
+This will allow you to build a local SNAPSHOT jar in `target/`. Alternatively, use `./mvnw -Dmaven.test.skip=true install` to build a SNAPSHOT jar and then copy it into your local repository, so that you can use it in your Maven projects. Note that may need to do `./mvnw dependency:resolve` in your project if you overwrite an older snapshot with a newer one.
+
+`./mvnw -U` updates from remote repositories an may overwrite your local artifact. But you can always change the `artifactId` or the `groupId` of your local ClassGraph build to place your local build artifact in another location within your local repository.
+
+## Documentation
+
+[See the wiki for complete documentation and usage information.](https://github.com/classgraph/classgraph/wiki)
+
+**ClassGraph was known as FastClasspathScanner prior to version 4**. See the [porting notes](https://github.com/classgraph/classgraph/wiki/Porting-FastClasspathScanner-code-to-ClassGraph) for information on porting from the older FastClasspathScanner API.
## Mailing List
* Feel free to subscribe to the [ClassGraph-Users](https://groups.google.com/d/forum/classgraph-users) email list for updates, or to ask questions.
* There is also a [Gitter room](https://gitter.im/classgraph/Lobby) for discussion of ClassGraph.
-## Author
+## Sponsorship
ClassGraph was written by Luke Hutchison ([@LH](http://twitter.com/LH) on Twitter).
-Please donate if this library makes your life easier:
+If ClassGraph is critical to your work, you can help fund further development through the [GitHub Sponsors Program](https://github.com/sponsors/lukehutch).
-[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=luke.hutch@gmail.com&lc=US&item_name=Luke%20Hutchison&item_number=ClassGraph&no_note=0¤cy_code=USD&bn=PP-DonationsBF:btn_donateCC_LG.gif:NonHostedGuest)
+
-### Acknowledgments
+## Acknowledgments
ClassGraph would not be possible without contributions from numerous users, including in the form of bug reports, feature requests, code contributions, and assistance with testing.
-### Alternatives
+## Alternatives
Some other classpath scanning mechanisms include:
@@ -129,17 +195,21 @@ Some other classpath scanning mechanisms include:
* [Javassist](http://jboss-javassist.github.io/javassist/)
* [ObjectWeb ASM](http://asm.ow2.org/)
* [QDox](https://github.com/paul-hammant/qdox), a fast Java source parser and indexer
-* [bndtools](https://github.com/bndtools/bnd), which is able to ["crawl"/parse the bytecode of class files](https://github.com/bndtools/bnd/blob/master/biz.aQute.bndlib/src/aQute/bnd/osgi/Clazz.java) to find all imports/dependencies, among other things.
+* [bndtools](https://github.com/bndtools/bnd), which is able to ["crawl"/parse the bytecode of class files](https://github.com/bndtools/bnd/blob/master/biz.aQute.bndlib/src/aQute/bnd/osgi/Clazz.java) to find all imports/dependencies, among other things.
* [coffea](https://github.com/sbilinski/coffea), a command line tool and Python library for analyzing static dependences in Java bytecode
+* [org.clapper.classutil.ClassFinder](https://github.com/bmc/classutil/blob/master/src/main/scala/org/clapper/classutil/ClassFinder.scala)
+* [com.google.common.reflect.ClassPath](https://github.com/google/guava/blob/master/guava/src/com/google/common/reflect/ClassPath.java)
+* [jdependency](https://github.com/tcurdt/jdependency)
+* [Burningwave Core](https://github.com/burningwave/core#burningwave-core-)
## License
**The MIT License (MIT)**
-**Copyright (c) 2019 Luke Hutchison**
-
+**Copyright (c) 2022 Luke Hutchison**
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/mvnw b/mvnw
new file mode 100755
index 000000000..e9cf8d330
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,295 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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
+#
+# http://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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.3
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
+fi
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 000000000..3fd2be860
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,189 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.3
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+
+$MAVEN_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/pom.xml b/pom.xml
index 5caad0585..f19d6afaf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,513 +1,566 @@
- 4.0.0
+ 4.0.0
- io.github.classgraph
- classgraph
- 4.8.12-SNAPSHOT
- ClassGraph
+ io.github.classgraph
+ classgraph
+ 4.8.184
+ ClassGraph
- The uber-fast, ultra-lightweight classpath and module path scanner for JVM languages.
+ The uber-fast, ultra-lightweight classpath and module scanner for JVM languages.
- https://github.com/classgraph/classgraph
+ https://github.com/classgraph/classgraph
-
-
- The MIT License (MIT)
- http://opensource.org/licenses/MIT
- repo
-
-
+
+
+ The MIT License (MIT)
+ http://opensource.org/licenses/MIT
+ repo
+
+
-
-
- Luke Hutchison
- luke.hutch@gmail.com
- --
- https://github.com/lukehutch
-
-
+
+
+ Luke Hutchison
+ luke.hutch@gmail.com
+ ClassGraph
+ https://github.com/classgraph
+
+
-
- scm:git:git@github.com:classgraph/classgraph.git
- scm:git:git@github.com:classgraph/classgraph.git
- https://github.com/classgraph/classgraph
- HEAD
-
+
+ scm:git:git@github.com:classgraph/classgraph.git
+ scm:git:git@github.com:classgraph/classgraph.git
+ https://github.com/classgraph/classgraph
+ classgraph-4.8.182
+
-
- https://github.com/classgraph/classgraph/issues
-
+
+ https://github.com/classgraph/classgraph/issues
+
-
-
- ossrh
- https://oss.sonatype.org/content/repositories/snapshots
-
-
- ossrh
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
-
-
+
+
+ UTF-8
+ UTF-8
-
-
- UTF-8
- UTF-8
+
+
-
-
-
+
+
+
+
+
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- org.apache.felix
- maven-bundle-plugin
-
-
- org.moditect
- moditect-maven-plugin
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
-
-
- org.apache.maven.plugins
- maven-jar-plugin
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
-
-
- org.apache.maven.plugins
- maven-release-plugin
-
-
+
+
+
+
+ io.github.toolfactory
+ narcissus
+ 1.0.11
+ true
+
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- 3.0.0-M2
-
-
- org.codehaus.mojo
- animal-sniffer-enforcer-rule
- 1.17
-
-
-
-
-
- check-signatures
- test
-
- enforce
-
-
-
-
-
- org.codehaus.mojo.signature
-
- java17
- 1.0
-
-
-
-
-
-
-
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.2
+ test
+
+
+ org.openjdk.jmh
+ jmh-core
+ 1.37
+ test
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ 1.37
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.25.3
+ test
+
+
+ javax.enterprise
+ cdi-api
+ 2.0
+ test
+
+
+ org.ops4j.pax.url
+ pax-url-aether
+ 2.6.14
+ test
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.13
+ test
+
+
+ org.slf4j
+ slf4j-jdk14
+ 2.0.13
+ test
+
+
+ org.hibernate.javax.persistence
+ hibernate-jpa-2.1-api
+ 1.0.2.Final
+ test
+
+
+ com.google.jimfs
+ jimfs
+ 1.3.0
+ test
+
+
+ jakarta.validation
+ jakarta.validation-api
+ 3.0.2
+ test
+
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.8.0
-
- UTF-8
-
-
-
-
-
-
- 7
- 7
-
-
-
- default-testCompile
- test-compile
-
- testCompile
-
-
- UTF-8
-
- 8
- 8
-
-
-
-
+
+
+
+
+
+
+ org.eclipse.jdt
+ org.eclipse.jdt.annotation
+ 2.3.0
+ provided
+
+
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.0.0-M3
-
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.4.1
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.3.1
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.5.0
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.4.0
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ 3.1.0
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.3.1
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.6.3
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.3
+
-
-
- org.apache.felix
- maven-bundle-plugin
- 4.1.0
- true
-
-
- Utilities
- ${project.groupId}.${project.artifactId}
- ${project.description}
- Luke Hutchison
-
-
-
-
- bundle-manifest
- process-classes
-
- manifest
-
-
-
-
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.3.2
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 3.1.1
+
-
-
-
-
- org.moditect
- moditect-maven-plugin
- 1.0.0.Beta2
-
-
- add-module-infos
- package
-
- add-module-info
-
-
-
-
- 9
- true
-
-
-
-
-
-
- /**
- ClassGraph, the uber-fast, ultra-lightweight, parallelized
- Java classpath scanner,
- module scanner, and annotation processor for JVM
- languages. https://github.com/classgraph/classgraph */
- module
- io.github.classgraph {
- exports io.github.classgraph;
- requires java.xml;
- requires jdk.unsupported;
- requires java.management;
- requires java.logging;
- }
-
-
-
-
-
-
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.8.0
+ true
+
+ central
+ true
+
+
+
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
- 3.0.0
-
-
- add-test-source
- generate-test-sources
-
- add-test-source
-
-
-
- src/test/perf
-
-
-
-
-
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+ org.codehaus.mojo
+ animal-sniffer-enforcer-rule
+ 1.23
+
+
+
+
+
+ enforce-versions
+ validate
+
+ enforce
+
+
+
+
+ [3.6.3,)
+
+
+
+
+
+
+ check-signatures
+ compile
+
+ enforce
+
+
+
+
+
+ org.codehaus.mojo.signature
+ java17
+ 1.0
+
+
+
+
+
+
+
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.1.1
-
-
- ${project.build.outputDirectory}/META-INF/MANIFEST.MF
-
- true
- true
-
-
-
-
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+
+ copy-license-to-target
+ process-resources
+
+ copy-resources
+
+
+ ${project.build.outputDirectory}
+
+
+ ${basedir}
+ false
+
+ LICENSE-ClassGraph.txt
+
+
+
+
+
+
+ copy-license-to-javadocs
+ process-resources
+
+ copy-resources
+
+
+ ${project.build.directory}/apidocs
+
+
+ ${basedir}
+ false
+
+ LICENSE-ClassGraph.txt
+
+
+
+
+
+
+
-
-
- org.apache.maven.plugins
- maven-source-plugin
- 3.0.1
-
-
- attach-sources
-
- jar-no-fork
-
-
-
-
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+
+
+
+
+
+
+
+
+
+
+ add-module-info-to-jar
+ package
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ add-modular-javadoc
+ verify
+
+ run
+
+
+
+
+
+
+
+
+
+
+
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 3.0.1
-
-
- attach-javadocs
-
- jar
-
-
- none
- ${javadoc.html.version}
- nonapi.*
-
-
-
-
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ UTF-8
+
+ 8
+ false
+
+ -Xlint:all
+ -Xlint:-options
+ -Werror
+
+
+
+
+ default-testCompile
+ test-compile
+
+ testCompile
+
+
+ UTF-8
+ 8
+
+ -parameters
+
+
+
+
+
-
-
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.8
- true
-
- ossrh
- https://oss.sonatype.org/
- true
-
-
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ ${surefireArgLine}
+
+
-
-
- org.apache.maven.plugins
- maven-release-plugin
- 2.5.3
-
- true
- false
- release
- deploy
-
-
-
-
-
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-test-source
+ generate-test-sources
+
+ add-test-source
+
+
+
+ src/test/perf
+
+
+
+
+
-
-
-
-
- junit
- junit
- 4.13-beta-1
- test
-
-
- org.openjdk.jmh
- jmh-core
- 1.21
- test
-
-
- org.openjdk.jmh
- jmh-generator-annprocess
- 1.21
- test
-
-
- org.assertj
- assertj-core
- 3.11.1
- test
-
-
- javax.enterprise
- cdi-api
- 1.0-SP4
- test
-
-
- org.ops4j.pax.url
- pax-url-aether
- 2.6.1
- test
-
-
- org.slf4j
- slf4j-api
- 1.8.0-beta2
- test
-
-
- org.slf4j
- slf4j-jdk14
- 1.8.0-beta2
- test
-
-
- org.hibernate.javax.persistence
- hibernate-jpa-2.1-api
- 1.0.2.Final
- test
-
-
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+ true
+
+ true
+
+ true
+ true
+
+
+
+ Utilities
+ http://opensource.org/licenses/MIT
+ 2
+ ClassGraph
+ ${project.groupId}.${project.artifactId}
+ Luke Hutchison
+ ${project.description}
+ ${project.version}
+ osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))"
+ io.github.classgraph;version="${project.version}"
+
+
+ javax.xml.xpath,javax.xml.namespace,javax.xml.parsers,org.w3c.dom,sun.misc;resolution:="optional",sun.nio.ch;resolution:="optional",io.github.toolfactory.narcissus;resolution:="optional",io.github.toolfactory.jvm;resolution:="optional"
+
+ java.xml,jdk.unsupported,java.management,java.logging
+
+
+ true
+
+
+
+
-
-
-
- jdk9plus
-
- -html5
-
-
- true
-
-
- [9,)
-
-
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+ package
+
+ jar-no-fork
+
+
+
+
-
-
- release
-
-
- performRelease
- true
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.6
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
-
-
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+ package
+
+ jar
+
+
+ 8
+ ${javadoc.html.version}
+ all
+ nonapi.*
+ public
+
+
+
+
+
+
+
-
-
-
- only-eclipse
-
-
-
- m2e.version
-
-
-
-
-
-
-
-
-
- org.eclipse.m2e
- lifecycle-mapping
- 1.0.0
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- [1.0.0,)
-
- enforce
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ jdk9plus
+
+ [9,)
+
+
+ -html5
+
+
+ true
+
+
+
+ jdk17plus
+
+ [17,)
+
+
+ --enable-native-access=ALL-UNNAMED
+
+
+
+
+
+ release
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+
+ --pinentry-mode
+ loopback
+
+ ${gpg.keyname}
+ ${gpg.keyname}
+
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
diff --git a/src/main/java/io/github/classgraph/AnnotationClassRef.java b/src/main/java/io/github/classgraph/AnnotationClassRef.java
index 50a6d0ccb..3ac4343ac 100644
--- a/src/main/java/io/github/classgraph/AnnotationClassRef.java
+++ b/src/main/java/io/github/classgraph/AnnotationClassRef.java
@@ -28,15 +28,12 @@
*/
package io.github.classgraph;
-import java.util.Set;
-
import nonapi.io.github.classgraph.types.ParseException;
/**
* Stores the type descriptor of a {@code Class>}, as found in an annotation parameter value.
*/
public class AnnotationClassRef extends ScanResultObject {
-
/** The type descriptor str. */
private String typeDescriptorStr;
@@ -78,14 +75,14 @@ public String getName() {
/**
* Get the type signature.
*
- * @return The type signature of the {@code Class>} reference. This will be a {@link ClassRefTypeSignature} or
- * a {@link BaseTypeSignature}.
+ * @return The type signature of the {@code Class>} reference. This will be a {@link ClassRefTypeSignature}, a
+ * {@link BaseTypeSignature}, or an {@link ArrayTypeSignature}.
*/
private TypeSignature getTypeSignature() {
if (typeSignature == null) {
try {
- // There can't be any type variables to resolve in either ClassRefTypeSignature or
- // BaseTypeSignature, so just set definingClassName to null
+ // There can't be any type variables to resolve in ClassRefTypeSignature,
+ // BaseTypeSignature or ArrayTypeSignature, so just set definingClassName to null
typeSignature = TypeSignature.parse(typeDescriptorStr, /* definingClassName = */ null);
typeSignature.setScanResult(scanResult);
} catch (final ParseException e) {
@@ -110,7 +107,9 @@ public Class> loadClass(final boolean ignoreExceptions) {
if (typeSignature instanceof BaseTypeSignature) {
return ((BaseTypeSignature) typeSignature).getType();
} else if (typeSignature instanceof ClassRefTypeSignature) {
- return ((ClassRefTypeSignature) typeSignature).loadClass(ignoreExceptions);
+ return typeSignature.loadClass(ignoreExceptions);
+ } else if (typeSignature instanceof ArrayTypeSignature) {
+ return typeSignature.loadClass(ignoreExceptions);
} else {
throw new IllegalArgumentException("Got unexpected type " + typeSignature.getClass().getName()
+ " for ref type signature: " + typeDescriptorStr);
@@ -139,9 +138,11 @@ protected String getClassName() {
if (className == null) {
getTypeSignature();
if (typeSignature instanceof BaseTypeSignature) {
- className = ((BaseTypeSignature) typeSignature).getType().getName();
+ className = ((BaseTypeSignature) typeSignature).getTypeStr();
} else if (typeSignature instanceof ClassRefTypeSignature) {
className = ((ClassRefTypeSignature) typeSignature).getFullyQualifiedClassName();
+ } else if (typeSignature instanceof ArrayTypeSignature) {
+ className = typeSignature.getClassName();
} else {
throw new IllegalArgumentException("Got unexpected type " + typeSignature.getClass().getName()
+ " for ref type signature: " + typeDescriptorStr);
@@ -160,8 +161,8 @@ protected String getClassName() {
*/
@Override
public ClassInfo getClassInfo() {
- getClassName();
- return super.getClassInfo();
+ getTypeSignature();
+ return typeSignature.getClassInfo();
}
/* (non-Javadoc)
@@ -175,14 +176,6 @@ void setScanResult(final ScanResult scanResult) {
}
}
- /* (non-Javadoc)
- * @see io.github.classgraph.ScanResultObject#findReferencedClassNames(java.util.Set)
- */
- @Override
- protected void findReferencedClassNames(final Set classNames) {
- classNames.add(getClassName());
- }
-
// -------------------------------------------------------------------------------------------------------------
/* (non-Javadoc)
@@ -198,25 +191,30 @@ public int hashCode() {
*/
@Override
public boolean equals(final Object obj) {
- if (!(obj instanceof AnnotationClassRef)) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof AnnotationClassRef)) {
return false;
}
return getTypeSignature().equals(((AnnotationClassRef) obj).getTypeSignature());
}
- /* (non-Javadoc)
- * @see java.lang.Object#toString()
- */
@Override
- public String toString() {
- String prefix = "class ";
- if (scanResult != null) {
- final ClassInfo ci = getClassInfo();
- // The JDK uses "interface" for both interfaces and annotations in Annotation::toString
- if (ci != null && ci.isInterfaceOrAnnotation()) {
- prefix = "interface ";
- }
- }
- return prefix + getTypeSignature().toString();
+ protected void toString(final boolean useSimpleNames, final StringBuilder buf) {
+ // More recent versions of Annotation::toString() have dropped the "class"/"interface" prefix,
+ // and added ".class" to the end of the class reference (which does not actually match the
+ // annotation source syntax...)
+
+ // String prefix = "class ";
+ // if (scanResult != null) {
+ // final ClassInfo ci = getClassInfo();
+ // // The JDK uses "interface" for both interfaces and annotations in Annotation::toString
+ // if (ci != null && ci.isInterfaceOrAnnotation()) {
+ // prefix = "interface ";
+ // }
+ // }
+
+ /* prefix + */
+ buf.append(getTypeSignature().toString(useSimpleNames)).append(".class");
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/classgraph/AnnotationEnumValue.java b/src/main/java/io/github/classgraph/AnnotationEnumValue.java
index 94e05e1a1..4b855a260 100644
--- a/src/main/java/io/github/classgraph/AnnotationEnumValue.java
+++ b/src/main/java/io/github/classgraph/AnnotationEnumValue.java
@@ -29,14 +29,12 @@
package io.github.classgraph;
import java.lang.reflect.Field;
-import java.util.Set;
/**
* Class for wrapping an enum constant value (split into class name and constant name), as used as an annotation
* parameter value.
*/
public class AnnotationEnumValue extends ScanResultObject implements Comparable {
-
/** The class name. */
private String className;
@@ -106,6 +104,13 @@ public String getName() {
*/
public Object loadClassAndReturnEnumValue(final boolean ignoreExceptions) throws IllegalArgumentException {
final Class> classRef = super.loadClass(ignoreExceptions);
+ if (classRef == null) {
+ if (ignoreExceptions) {
+ return null;
+ } else {
+ throw new IllegalArgumentException("Enum class " + className + " could not be loaded");
+ }
+ }
if (!classRef.isEnum()) {
throw new IllegalArgumentException("Class " + className + " is not an enum");
}
@@ -113,15 +118,15 @@ public Object loadClassAndReturnEnumValue(final boolean ignoreExceptions) throws
try {
field = classRef.getDeclaredField(valueName);
} catch (final ReflectiveOperationException | SecurityException e) {
- throw new IllegalArgumentException("Could not find enum constant " + toString(), e);
+ throw new IllegalArgumentException("Could not find enum constant " + this, e);
}
if (!field.isEnumConstant()) {
- throw new IllegalArgumentException("Field " + toString() + " is not an enum constant");
+ throw new IllegalArgumentException("Field " + this + " is not an enum constant");
}
try {
return field.get(null);
} catch (final ReflectiveOperationException | SecurityException e) {
- throw new IllegalArgumentException("Field " + toString() + " is not accessible", e);
+ throw new IllegalArgumentException("Field " + this + " is not accessible", e);
}
}
@@ -139,14 +144,6 @@ public Object loadClassAndReturnEnumValue() throws IllegalArgumentException {
// -------------------------------------------------------------------------------------------------------------
- /* (non-Javadoc)
- * @see io.github.classgraph.ScanResultObject#findReferencedClassNames(java.util.Set)
- */
- @Override
- void findReferencedClassNames(final Set referencedClassNames) {
- referencedClassNames.add(className);
- }
-
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@@ -160,11 +157,13 @@ public int compareTo(final AnnotationEnumValue o) {
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
- public boolean equals(final Object o) {
- if (!(o instanceof AnnotationEnumValue)) {
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof AnnotationEnumValue)) {
return false;
}
- return compareTo((AnnotationEnumValue) o) == 0;
+ return compareTo((AnnotationEnumValue) obj) == 0;
}
/* (non-Javadoc)
@@ -175,11 +174,10 @@ public int hashCode() {
return className.hashCode() * 11 + valueName.hashCode();
}
- /* (non-Javadoc)
- * @see java.lang.Object#toString()
- */
@Override
- public String toString() {
- return className + "." + valueName;
+ protected void toString(final boolean useSimpleNames, final StringBuilder buf) {
+ buf.append(useSimpleNames ? ClassInfo.getSimpleName(className) : className);
+ buf.append('.');
+ buf.append(valueName);
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/classgraph/AnnotationInfo.java b/src/main/java/io/github/classgraph/AnnotationInfo.java
index 4c0c38042..356f1a41a 100644
--- a/src/main/java/io/github/classgraph/AnnotationInfo.java
+++ b/src/main/java/io/github/classgraph/AnnotationInfo.java
@@ -40,11 +40,11 @@
import java.util.Map.Entry;
import java.util.Set;
-import nonapi.io.github.classgraph.utils.ReflectionUtils;
+import nonapi.io.github.classgraph.reflection.ReflectionUtils;
+import nonapi.io.github.classgraph.utils.LogNode;
/** Holds metadata about a specific annotation instance on a class, method, method parameter or field. */
public class AnnotationInfo extends ScanResultObject implements Comparable, HasName {
-
/** The name. */
private String name;
@@ -114,24 +114,28 @@ public AnnotationParameterValueList getDefaultParameterValues() {
/**
* Get the parameter values.
*
+ * @param includeDefaultValues
+ * if true, include default values for any annotation parameter value that is missing.
* @return The parameter values of this annotation, including any default parameter values inherited from the
- * annotation class definition, or the empty list if none.
+ * annotation class definition (if requested), or the empty list if none.
*/
- public AnnotationParameterValueList getParameterValues() {
+ public AnnotationParameterValueList getParameterValues(final boolean includeDefaultValues) {
+ final ClassInfo classInfo = getClassInfo();
+ if (classInfo == null) {
+ // ClassInfo has not yet been set, just return values without defaults
+ // (happens when trying to log AnnotationInfo during scanning, before ScanResult is available)
+ return annotationParamValues == null ? AnnotationParameterValueList.EMPTY_LIST : annotationParamValues;
+ }
+ // Lazily convert any Object[] arrays of boxed types to primitive arrays
+ if (annotationParamValues != null && !annotationParamValuesHasBeenConvertedToPrimitive) {
+ annotationParamValues.convertWrapperArraysToPrimitiveArrays(classInfo);
+ annotationParamValuesHasBeenConvertedToPrimitive = true;
+ }
+ if (!includeDefaultValues) {
+ // Don't include defaults
+ return annotationParamValues == null ? AnnotationParameterValueList.EMPTY_LIST : annotationParamValues;
+ }
if (annotationParamValuesWithDefaults == null) {
- final ClassInfo classInfo = getClassInfo();
- if (classInfo == null) {
- // ClassInfo has not yet been set, just return values without defaults
- // (happens when trying to log AnnotationInfo during scanning, before ScanResult is available)
- return annotationParamValues == null ? AnnotationParameterValueList.EMPTY_LIST
- : annotationParamValues;
- }
-
- // Lazily convert any Object[] arrays of boxed types to primitive arrays
- if (annotationParamValues != null && !annotationParamValuesHasBeenConvertedToPrimitive) {
- annotationParamValues.convertWrapperArraysToPrimitiveArrays(classInfo);
- annotationParamValuesHasBeenConvertedToPrimitive = true;
- }
if (classInfo.annotationDefaultParamValues != null
&& !classInfo.annotationDefaultParamValuesHasBeenConvertedToPrimitive) {
classInfo.annotationDefaultParamValues.convertWrapperArraysToPrimitiveArrays(classInfo);
@@ -193,6 +197,16 @@ public AnnotationParameterValueList getParameterValues() {
return annotationParamValuesWithDefaults;
}
+ /**
+ * Get the parameter values.
+ *
+ * @return The parameter values of this annotation, including any default parameter values inherited from the
+ * annotation class definition, or the empty list if none.
+ */
+ public AnnotationParameterValueList getParameterValues() {
+ return getParameterValues(true);
+ }
+
// -------------------------------------------------------------------------------------------------------------
/**
@@ -205,17 +219,6 @@ protected String getClassName() {
return name;
}
- /**
- * Get the class info.
- *
- * @return The {@link ClassInfo} object for the annotation class.
- */
- @Override
- public ClassInfo getClassInfo() {
- getClassName();
- return super.getClassInfo();
- }
-
/* (non-Javadoc)
* @see io.github.classgraph.ScanResultObject#setScanResult(io.github.classgraph.ScanResult)
*/
@@ -230,23 +233,32 @@ void setScanResult(final ScanResult scanResult) {
}
/**
- * Find the names of any classes referenced in the type descriptors of annotation parameters.
+ * Get {@link ClassInfo} objects for any classes referenced in the type descriptor or type signature.
*
- * @param referencedClassNames
- * the referenced class names
+ * @param classNameToClassInfo
+ * the map from class name to {@link ClassInfo}.
+ * @param refdClassInfo
+ * the referenced class info
*/
@Override
- void findReferencedClassNames(final Set referencedClassNames) {
- referencedClassNames.add(name);
+ protected void findReferencedClassInfo(final Map classNameToClassInfo,
+ final Set refdClassInfo, final LogNode log) {
+ super.findReferencedClassInfo(classNameToClassInfo, refdClassInfo, log);
if (annotationParamValues != null) {
for (final AnnotationParameterValue annotationParamValue : annotationParamValues) {
- annotationParamValue.findReferencedClassNames(referencedClassNames);
+ annotationParamValue.findReferencedClassInfo(classNameToClassInfo, refdClassInfo, log);
}
}
}
// -------------------------------------------------------------------------------------------------------------
+ /** Return the {@link ClassInfo} object for the annotation class. */
+ @Override
+ public ClassInfo getClassInfo() {
+ return super.getClassInfo();
+ }
+
/**
* Load the {@link Annotation} class corresponding to this {@link AnnotationInfo} object, by calling
* {@code getClassInfo().loadClass()}, then create a new instance of the annotation, with the annotation
@@ -276,7 +288,7 @@ void findReferencedClassNames(final Set referencedClassNames) {
public Annotation loadClassAndInstantiate() {
final Class extends Annotation> annotationClass = getClassInfo().loadClass(Annotation.class);
return (Annotation) Proxy.newProxyInstance(annotationClass.getClassLoader(),
- new Class[] { annotationClass }, new AnnotationInvocationHandler(annotationClass, this));
+ new Class>[] { annotationClass }, new AnnotationInvocationHandler(annotationClass, this));
}
/** {@link InvocationHandler} for dynamically instantiating an {@link Annotation} object. */
@@ -311,7 +323,7 @@ private static class AnnotationInvocationHandler implements InvocationHandler {
if (instantiatedValue == null) {
// Annotations cannot contain null values
throw new IllegalArgumentException("Got null value for annotation parameter " + apv.getName()
- + " of annotation " + annotationInfo.getName());
+ + " of annotation " + annotationInfo.name);
}
this.annotationParameterValuesInstantiated.put(apv.getName(), instantiatedValue);
}
@@ -331,7 +343,7 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg
+ (args == null ? 0 : args.length) + ", expected " + paramTypes.length);
}
if (args != null && paramTypes.length == 1) {
- if (methodName.equals("equals") && paramTypes[0] == Object.class) {
+ if ("equals".equals(methodName) && paramTypes[0] == Object.class) {
// equals() needs to function the same as the JDK implementation
// (see src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java in the JDK)
if (this == args[0]) {
@@ -339,11 +351,14 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg
} else if (!annotationClass.isInstance(args[0])) {
return false;
}
+ final ReflectionUtils reflectionUtils = annotationInfo.scanResult == null
+ ? new ReflectionUtils()
+ : annotationInfo.scanResult.reflectionUtils;
for (final Entry ent : annotationParameterValuesInstantiated.entrySet()) {
final String paramName = ent.getKey();
final Object paramVal = ent.getValue();
- final Object otherParamVal = ReflectionUtils.invokeMethod(args[0], paramName,
- /* throwException = */ false);
+ final Object otherParamVal = reflectionUtils.invokeMethod(/* throwException = */ false,
+ args[0], paramName);
if ((paramVal == null) != (otherParamVal == null)) {
// Annotation values should never be null, but just to be safe
return false;
@@ -469,7 +484,7 @@ void convertWrapperArraysToPrimitiveArrays() {
*/
@Override
public int compareTo(final AnnotationInfo o) {
- final int diff = getName().compareTo(o.getName());
+ final int diff = this.name.compareTo(o.name);
if (diff != 0) {
return diff;
}
@@ -480,8 +495,8 @@ public int compareTo(final AnnotationInfo o) {
} else if (o.annotationParamValues == null) {
return 1;
} else {
- for (int i = 0, max = Math.max(annotationParamValues.size(),
- o.annotationParamValues.size()); i < max; i++) {
+ for (int i = 0,
+ max = Math.max(annotationParamValues.size(), o.annotationParamValues.size()); i < max; i++) {
if (i >= annotationParamValues.size()) {
return -1;
} else if (i >= o.annotationParamValues.size()) {
@@ -502,11 +517,13 @@ public int compareTo(final AnnotationInfo o) {
*/
@Override
public boolean equals(final Object obj) {
- if (!(obj instanceof AnnotationInfo)) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof AnnotationInfo)) {
return false;
}
- final AnnotationInfo o = (AnnotationInfo) obj;
- return this.compareTo(o) == 0;
+ final AnnotationInfo other = (AnnotationInfo) obj;
+ return this.compareTo(other) == 0;
}
/* (non-Javadoc)
@@ -514,7 +531,7 @@ public boolean equals(final Object obj) {
*/
@Override
public int hashCode() {
- int h = getName().hashCode();
+ int h = name.hashCode();
if (annotationParamValues != null) {
for (final AnnotationParameterValue e : annotationParamValues) {
h = h * 7 + e.getName().hashCode() * 3 + e.getValue().hashCode();
@@ -523,14 +540,9 @@ public int hashCode() {
return h;
}
- /**
- * Render as a string, into a StringBuilder buffer.
- *
- * @param buf
- * The buffer.
- */
- void toString(final StringBuilder buf) {
- buf.append('@').append(getName());
+ @Override
+ protected void toString(final boolean useSimpleNames, final StringBuilder buf) {
+ buf.append('@').append(useSimpleNames ? ClassInfo.getSimpleName(name) : name);
final AnnotationParameterValueList paramVals = getParameterValues();
if (!paramVals.isEmpty()) {
buf.append('(');
@@ -540,22 +552,12 @@ void toString(final StringBuilder buf) {
}
final AnnotationParameterValue paramVal = paramVals.get(i);
if (paramVals.size() > 1 || !"value".equals(paramVal.getName())) {
- paramVal.toString(buf);
+ paramVal.toString(useSimpleNames, buf);
} else {
- paramVal.toStringParamValueOnly(buf);
+ paramVal.toStringParamValueOnly(useSimpleNames, buf);
}
}
buf.append(')');
}
}
-
- /* (non-Javadoc)
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- final StringBuilder buf = new StringBuilder();
- toString(buf);
- return buf.toString();
- }
}
diff --git a/src/main/java/io/github/classgraph/AnnotationInfoList.java b/src/main/java/io/github/classgraph/AnnotationInfoList.java
index bef4cb9df..79a0151d7 100644
--- a/src/main/java/io/github/classgraph/AnnotationInfoList.java
+++ b/src/main/java/io/github/classgraph/AnnotationInfoList.java
@@ -28,15 +28,18 @@
*/
package io.github.classgraph;
+import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import io.github.classgraph.ClassInfo.RelType;
+import nonapi.io.github.classgraph.utils.Assert;
+import nonapi.io.github.classgraph.utils.CollectionUtils;
+import nonapi.io.github.classgraph.utils.LogNode;
/** A list of {@link AnnotationInfo} objects. */
public class AnnotationInfoList extends MappableInfoList {
@@ -47,30 +50,49 @@ public class AnnotationInfoList extends MappableInfoList {
*/
private AnnotationInfoList directlyRelatedAnnotations;
+ /** serialVersionUID */
+ private static final long serialVersionUID = 1L;
+
+ /** An unmodifiable empty {@link AnnotationInfoList}. */
+ static final AnnotationInfoList EMPTY_LIST = new AnnotationInfoList();
+ static {
+ EMPTY_LIST.makeUnmodifiable();
+ }
+
/**
- * Constructor.
+ * Return an unmodifiable empty {@link AnnotationInfoList}.
+ *
+ * @return the unmodifiable empty {@link AnnotationInfoList}.
+ */
+ public static AnnotationInfoList emptyList() {
+ return EMPTY_LIST;
+ }
+
+ /**
+ * Construct a new modifiable empty list of {@link AnnotationInfo} objects.
*/
- AnnotationInfoList() {
+ public AnnotationInfoList() {
super();
}
/**
- * Constructor.
+ * Construct a new modifiable empty list of {@link AnnotationInfo} objects, given a size hint.
*
* @param sizeHint
* the size hint
*/
- AnnotationInfoList(final int sizeHint) {
+ public AnnotationInfoList(final int sizeHint) {
super(sizeHint);
}
/**
- * Constructor.
+ * Construct a new modifiable empty {@link AnnotationInfoList}, given an initial list of {@link AnnotationInfo}
+ * objects.
*
* @param reachableAnnotations
* the reachable annotations
*/
- AnnotationInfoList(final AnnotationInfoList reachableAnnotations) {
+ public AnnotationInfoList(final AnnotationInfoList reachableAnnotations) {
// If only reachable annotations are given, treat all of them as direct
this(reachableAnnotations, reachableAnnotations);
}
@@ -89,59 +111,6 @@ public class AnnotationInfoList extends MappableInfoList {
this.directlyRelatedAnnotations = directlyRelatedAnnotations;
}
- /** An unmodifiable empty {@link AnnotationInfoList}. */
- static final AnnotationInfoList EMPTY_LIST = new AnnotationInfoList() {
- @Override
- public boolean add(final AnnotationInfo e) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public void add(final int index, final AnnotationInfo element) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean remove(final Object o) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public AnnotationInfo remove(final int index) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean addAll(final Collection extends AnnotationInfo> c) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean addAll(final int index, final Collection extends AnnotationInfo> c) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean removeAll(final Collection> c) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean retainAll(final Collection> c) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public void clear() {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public AnnotationInfo set(final int index, final AnnotationInfo element) {
- throw new IllegalArgumentException("List is immutable");
- }
- };
-
// -------------------------------------------------------------------------------------------------------------
/**
@@ -183,14 +152,19 @@ public AnnotationInfoList filter(final AnnotationInfoFilter filter) {
// -------------------------------------------------------------------------------------------------------------
/**
- * Find the names of any classes referenced in the annotations in this list or their parameters.
+ * Get {@link ClassInfo} objects for any classes referenced in this list.
*
- * @param referencedClassNames
- * the referenced class names
+ * @param classNameToClassInfo
+ * the map from class name to {@link ClassInfo}.
+ * @param refdClassInfo
+ * the referenced class info
+ * @param log
+ * the log
*/
- void findReferencedClassNames(final Set referencedClassNames) {
+ protected void findReferencedClassInfo(final Map classNameToClassInfo,
+ final Set refdClassInfo, final LogNode log) {
for (final AnnotationInfo ai : this) {
- ai.findReferencedClassNames(referencedClassNames);
+ ai.findReferencedClassInfo(classNameToClassInfo, refdClassInfo, log);
}
}
@@ -205,11 +179,14 @@ void findReferencedClassNames(final Set referencedClassNames) {
* the containing class
* @param forwardRelType
* the forward relationship type for linking (or null for none)
- * @param reverseRelType
- * the reverse relationship type for linking (or null for none)
+ * @param reverseRelType0
+ * the first reverse relationship type for linking (or null for none)
+ * @param reverseRelType1
+ * the second reverse relationship type for linking (or null for none)
*/
void handleRepeatableAnnotations(final Set allRepeatableAnnotationNames,
- final ClassInfo containingClassInfo, final RelType forwardRelType, final RelType reverseRelType) {
+ final ClassInfo containingClassInfo, final RelType forwardRelType, final RelType reverseRelType0,
+ final RelType reverseRelType1) {
List repeatableAnnotations = null;
for (int i = size() - 1; i >= 0; --i) {
final AnnotationInfo ai = get(i);
@@ -237,11 +214,19 @@ void handleRepeatableAnnotations(final Set allRepeatableAnnotationNames,
add(ai);
// Link annotation, if necessary
- if (forwardRelType != null && reverseRelType != null) {
+ if (forwardRelType != null
+ && (reverseRelType0 != null || reverseRelType1 != null)) {
final ClassInfo annotationClass = ai.getClassInfo();
if (annotationClass != null) {
containingClassInfo.addRelatedClass(forwardRelType, annotationClass);
- annotationClass.addRelatedClass(reverseRelType, containingClassInfo);
+ if (reverseRelType0 != null) {
+ annotationClass.addRelatedClass(reverseRelType0,
+ containingClassInfo);
+ }
+ if (reverseRelType1 != null) {
+ annotationClass.addRelatedClass(reverseRelType1,
+ containingClassInfo);
+ }
}
}
}
@@ -333,10 +318,10 @@ static AnnotationInfoList getIndirectAnnotations(final AnnotationInfoList direct
final AnnotationInfoList directAnnotationInfoSorted = directAnnotationInfo == null
? AnnotationInfoList.EMPTY_LIST
: new AnnotationInfoList(directAnnotationInfo);
- Collections.sort(directAnnotationInfoSorted);
+ CollectionUtils.sortIfNotEmpty(directAnnotationInfoSorted);
final AnnotationInfoList annotationInfoList = new AnnotationInfoList(reachableAnnotationInfo,
directAnnotationInfoSorted);
- Collections.sort(annotationInfoList);
+ CollectionUtils.sortIfNotEmpty(annotationInfoList);
return annotationInfoList;
}
@@ -361,6 +346,18 @@ public AnnotationInfoList directOnly() {
// -------------------------------------------------------------------------------------------------------------
+ /**
+ * Get the {@link Repeatable} annotation with the given class, or the empty list if none found.
+ *
+ * @param annotationClass
+ * The class to search for.
+ * @return The list of annotations with the given class, or the empty list if none found.
+ */
+ public AnnotationInfoList getRepeatable(final Class extends Annotation> annotationClass) {
+ Assert.isAnnotation(annotationClass);
+ return getRepeatable(annotationClass.getName());
+ }
+
/**
* Get the {@link Repeatable} annotation with the given name, or the empty list if none found.
*
@@ -394,14 +391,13 @@ public AnnotationInfoList getRepeatable(final String name) {
* @see java.util.ArrayList#equals(java.lang.Object)
*/
@Override
- public boolean equals(final Object o) {
- if (this == o) {
+ public boolean equals(final Object obj) {
+ if (this == obj) {
return true;
- }
- if (!(o instanceof AnnotationInfoList)) {
+ } else if (!(obj instanceof AnnotationInfoList)) {
return false;
}
- final AnnotationInfoList other = (AnnotationInfoList) o;
+ final AnnotationInfoList other = (AnnotationInfoList) obj;
if ((directlyRelatedAnnotations == null) != (other.directlyRelatedAnnotations == null)) {
return false;
}
diff --git a/src/main/java/io/github/classgraph/AnnotationParameterValue.java b/src/main/java/io/github/classgraph/AnnotationParameterValue.java
index 76efb2324..caa3db213 100644
--- a/src/main/java/io/github/classgraph/AnnotationParameterValue.java
+++ b/src/main/java/io/github/classgraph/AnnotationParameterValue.java
@@ -29,13 +29,15 @@
package io.github.classgraph;
import java.lang.reflect.Array;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import nonapi.io.github.classgraph.utils.LogNode;
+
/** A wrapper used to pair annotation parameter names with annotation parameter values. */
public class AnnotationParameterValue extends ScanResultObject
implements HasName, Comparable {
-
/** The the parameter name. */
private String name;
@@ -136,15 +138,18 @@ void setScanResult(final ScanResult scanResult) {
}
/**
- * Get the names of any classes referenced in the annotation parameters.
+ * Get {@link ClassInfo} objects for any classes referenced in the annotation parameters.
*
- * @param referencedClassNames
- * the referenced class names
+ * @param classNameToClassInfo
+ * the map from class name to {@link ClassInfo}.
+ * @param refdClassInfo
+ * the referenced class info
*/
@Override
- void findReferencedClassNames(final Set referencedClassNames) {
+ protected void findReferencedClassInfo(final Map classNameToClassInfo,
+ final Set refdClassInfo, final LogNode log) {
if (value != null) {
- value.findReferencedClassNames(referencedClassNames);
+ value.findReferencedClassInfo(classNameToClassInfo, refdClassInfo, log);
}
}
@@ -177,25 +182,78 @@ Object instantiate(final ClassInfo annotationClassInfo) {
// -------------------------------------------------------------------------------------------------------------
/* (non-Javadoc)
- * @see java.lang.Object#toString()
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
- public String toString() {
- final StringBuilder buf = new StringBuilder();
- toString(buf);
- return buf.toString();
+ public int compareTo(final AnnotationParameterValue other) {
+ if (other == this) {
+ return 0;
+ }
+ final int diff = name.compareTo(other.getName());
+ if (diff != 0) {
+ return diff;
+ }
+ if (value.equals(other.value)) {
+ return 0;
+ }
+ // Use toString() order (which can be slow) as a last-ditch effort -- only happens
+ // if the annotation has multiple parameters of the same name but different value.
+ final Object p0 = getValue();
+ final Object p1 = other.getValue();
+ return p0 == null || p1 == null ? (p0 == null ? 0 : 1) - (p1 == null ? 0 : 1)
+ : toStringParamValueOnly().compareTo(other.toStringParamValueOnly());
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof AnnotationParameterValue)) {
+ return false;
+ }
+ final AnnotationParameterValue other = (AnnotationParameterValue) obj;
+ return this.name.equals(other.name) && (value == null) == (other.value == null)
+ && (value == null || value.equals(other.value));
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, value);
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
+ @Override
+ protected void toString(final boolean useSimpleNames, final StringBuilder buf) {
+ buf.append(name);
+ buf.append("=");
+ toStringParamValueOnly(useSimpleNames, buf);
}
/**
- * To string.
+ * Write an annotation parameter value's string representation to the buffer.
*
+ * @param val
+ * the value
+ * @param useSimpleNames
+ * the use simple names
* @param buf
- * the buf
+ * the buffer
*/
- void toString(final StringBuilder buf) {
- buf.append(name);
- buf.append("=");
- toStringParamValueOnly(buf);
+ private static void toString(final Object val, final boolean useSimpleNames, final StringBuilder buf) {
+ if (val == null) {
+ buf.append("null");
+ } else if (val instanceof ScanResultObject) {
+ ((ScanResultObject) val).toString(useSimpleNames, buf);
+ } else {
+ buf.append(val);
+ }
}
/**
@@ -204,22 +262,22 @@ void toString(final StringBuilder buf) {
* @param buf
* the buf
*/
- void toStringParamValueOnly(final StringBuilder buf) {
+ void toStringParamValueOnly(final boolean useSimpleNames, final StringBuilder buf) {
if (value == null) {
buf.append("null");
} else {
final Object paramVal = value.get();
final Class> valClass = paramVal.getClass();
if (valClass.isArray()) {
- buf.append('[');
+ buf.append('{');
for (int j = 0, n = Array.getLength(paramVal); j < n; j++) {
if (j > 0) {
buf.append(", ");
}
final Object elt = Array.get(paramVal, j);
- buf.append(elt == null ? "null" : elt.toString());
+ toString(elt, useSimpleNames, buf);
}
- buf.append(']');
+ buf.append('}');
} else if (paramVal instanceof String) {
buf.append('"');
buf.append(paramVal.toString().replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r"));
@@ -229,46 +287,19 @@ void toStringParamValueOnly(final StringBuilder buf) {
buf.append(paramVal.toString().replace("'", "\\'").replace("\n", "\\n").replace("\r", "\\r"));
buf.append('\'');
} else {
- buf.append(paramVal.toString());
+ toString(paramVal, useSimpleNames, buf);
}
}
}
- /* (non-Javadoc)
- * @see java.lang.Comparable#compareTo(java.lang.Object)
- */
- @Override
- public int compareTo(final AnnotationParameterValue o) {
- final int diff = name.compareTo(o.getName());
- if (diff != 0) {
- return diff;
- }
- // Use toString() order and get() (which can be slow) as a last-ditch effort -- only happens
- // if the annotation has multiple parameters of the same name but different value.
- final Object p0 = getValue();
- final Object p1 = o.getValue();
- return p0 == null || p1 == null ? (p0 == null ? 0 : 1) - (p1 == null ? 0 : 1)
- : p0.toString().compareTo(p1.toString());
- }
-
- /* (non-Javadoc)
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals(final Object obj) {
- if (!(obj instanceof AnnotationParameterValue)) {
- return false;
- }
- final AnnotationParameterValue o = (AnnotationParameterValue) obj;
- return this.compareTo(o) == 0 && (value == null) == (o.value == null)
- && (value == null || value.equals(o.value));
- }
-
- /* (non-Javadoc)
- * @see java.lang.Object#hashCode()
+ /**
+ * To string, param value only.
+ *
+ * @return the string.
*/
- @Override
- public int hashCode() {
- return Objects.hash(name, value);
+ private String toStringParamValueOnly() {
+ final StringBuilder buf = new StringBuilder();
+ toStringParamValueOnly(false, buf);
+ return buf.toString();
}
}
diff --git a/src/main/java/io/github/classgraph/AnnotationParameterValueList.java b/src/main/java/io/github/classgraph/AnnotationParameterValueList.java
index 3fd9d3d5d..f6dd71ef1 100644
--- a/src/main/java/io/github/classgraph/AnnotationParameterValueList.java
+++ b/src/main/java/io/github/classgraph/AnnotationParameterValueList.java
@@ -29,102 +29,76 @@
package io.github.classgraph;
import java.util.Collection;
+import java.util.Map;
import java.util.Set;
+import nonapi.io.github.classgraph.utils.LogNode;
+
/** A list of {@link AnnotationParameterValue} objects. */
public class AnnotationParameterValueList extends MappableInfoList {
+ /** serialVersionUID */
+ private static final long serialVersionUID = 1L;
+
+ /** An unmodifiable empty {@link AnnotationParameterValueList}. */
+ static final AnnotationParameterValueList EMPTY_LIST = new AnnotationParameterValueList();
+ static {
+ EMPTY_LIST.makeUnmodifiable();
+ }
/**
- * Constructor.
+ * Return an unmodifiable empty {@link AnnotationParameterValueList}.
+ *
+ * @return the unmodifiable empty {@link AnnotationParameterValueList}.
*/
- AnnotationParameterValueList() {
+ public static AnnotationParameterValueList emptyList() {
+ return EMPTY_LIST;
+ }
+
+ /**
+ * Construct a new modifiable empty list of {@link AnnotationParameterValue} objects.
+ */
+ public AnnotationParameterValueList() {
super();
}
/**
- * Constructor.
+ * Construct a new modifiable empty list of {@link AnnotationParameterValue} objects, given a size hint.
*
* @param sizeHint
* the size hint
*/
- AnnotationParameterValueList(final int sizeHint) {
+ public AnnotationParameterValueList(final int sizeHint) {
super(sizeHint);
}
/**
- * Constructor.
+ * Construct a new modifiable empty {@link AnnotationParameterValueList}, given an initial list of
+ * {@link AnnotationParameterValue} objects.
*
* @param annotationParameterValueCollection
- * the annotation parameter value collection
+ * the collection of {@link AnnotationParameterValue} objects.
*/
- AnnotationParameterValueList(final Collection annotationParameterValueCollection) {
+ public AnnotationParameterValueList(
+ final Collection annotationParameterValueCollection) {
super(annotationParameterValueCollection);
}
- /** An unmodifiable empty {@link AnnotationParameterValueList}. */
- static final AnnotationParameterValueList EMPTY_LIST = new AnnotationParameterValueList() {
- @Override
- public boolean add(final AnnotationParameterValue e) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public void add(final int index, final AnnotationParameterValue element) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean remove(final Object o) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public AnnotationParameterValue remove(final int index) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean addAll(final Collection extends AnnotationParameterValue> c) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean addAll(final int index, final Collection extends AnnotationParameterValue> c) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean removeAll(final Collection> c) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public boolean retainAll(final Collection> c) {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public void clear() {
- throw new IllegalArgumentException("List is immutable");
- }
-
- @Override
- public AnnotationParameterValue set(final int index, final AnnotationParameterValue element) {
- throw new IllegalArgumentException("List is immutable");
- }
- };
-
// -------------------------------------------------------------------------------------------------------------
/**
- * Find the names of any classes referenced in the methods in this list.
+ * Get {@link ClassInfo} objects for any classes referenced in the methods in this list.
*
- * @param referencedClassNames
- * the referenced class names
+ * @param classNameToClassInfo
+ * the map from class name to {@link ClassInfo}.
+ * @param refdClassInfo
+ * the referenced class info
+ * @param log
+ * the log
*/
- void findReferencedClassNames(final Set referencedClassNames) {
+ protected void findReferencedClassInfo(final Map classNameToClassInfo,
+ final Set refdClassInfo, final LogNode log) {
for (final AnnotationParameterValue apv : this) {
- apv.findReferencedClassNames(referencedClassNames);
+ apv.findReferencedClassInfo(classNameToClassInfo, refdClassInfo, log);
}
}
diff --git a/src/main/java/io/github/classgraph/ArrayClassInfo.java b/src/main/java/io/github/classgraph/ArrayClassInfo.java
new file mode 100644
index 000000000..56b512b94
--- /dev/null
+++ b/src/main/java/io/github/classgraph/ArrayClassInfo.java
@@ -0,0 +1,251 @@
+/*
+ * This file is part of ClassGraph.
+ *
+ * Author: Luke Hutchison
+ *
+ * Hosted at: https://github.com/classgraph/classgraph
+ *
+ * --
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Luke Hutchison
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+ * documentation files (the "Software"), to deal in the Software without restriction, including without
+ * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+ * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
+ * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+ * OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package io.github.classgraph;
+
+import java.util.Map;
+import java.util.Set;
+
+import nonapi.io.github.classgraph.utils.LogNode;
+
+/**
+ * Holds metadata about an array class. This class extends {@link ClassInfo} with additional methods relevant to
+ * array classes, in particular {@link #getArrayTypeSignature()}, {@link #getTypeSignatureStr()},
+ * {@link #getElementTypeSignature()}, {@link #getElementClassInfo()}, {@link #loadElementClass()}, and
+ * {@link #getNumDimensions()}.
+ *
+ *
+ * An {@link ArrayClassInfo} object will not have any methods, fields or annotations.
+ * {@link ClassInfo#isArrayClass()} will return true for this subclass of {@link ClassInfo}.
+ */
+public class ArrayClassInfo extends ClassInfo {
+ /** The array type signature. */
+ private ArrayTypeSignature arrayTypeSignature;
+
+ /** The element class info. */
+ private ClassInfo elementClassInfo;
+
+ /** Default constructor for deserialization. */
+ ArrayClassInfo() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param arrayTypeSignature
+ * the array type signature
+ */
+ ArrayClassInfo(final ArrayTypeSignature arrayTypeSignature) {
+ super(arrayTypeSignature.getClassName(), /* modifiers = */ 0, /* resource = */ null);
+ this.arrayTypeSignature = arrayTypeSignature;
+ // Pre-load fields from element type
+ getElementClassInfo();
+ }
+
+ /* (non-Javadoc)
+ * @see io.github.classgraph.ClassInfo#setScanResult(io.github.classgraph.ScanResult)
+ */
+ @Override
+ void setScanResult(final ScanResult scanResult) {
+ super.setScanResult(scanResult);
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Get the raw type signature string of the array class, e.g. "[[I" for "int[][]".
+ *
+ * @return The raw type signature string of the array class.
+ */
+ @Override
+ public String getTypeSignatureStr() {
+ return arrayTypeSignature.getTypeSignatureStr();
+ }
+
+ /**
+ * Returns null, because array classes do not have a ClassTypeSignature. Call {@link #getArrayTypeSignature()}
+ * instead.
+ *
+ * @return null (always).
+ */
+ @Override
+ public ClassTypeSignature getTypeSignature() {
+ return null;
+ }
+
+ /**
+ * Get the type signature of the class.
+ *
+ * @return The class type signature, if available, otherwise returns null.
+ */
+ public ArrayTypeSignature getArrayTypeSignature() {
+ return arrayTypeSignature;
+ }
+
+ /**
+ * Get the type signature of the array elements.
+ *
+ * @return The type signature of the array elements.
+ */
+ public TypeSignature getElementTypeSignature() {
+ return arrayTypeSignature.getElementTypeSignature();
+ }
+
+ /**
+ * Get the number of dimensions of the array.
+ *
+ * @return The number of dimensions of the array.
+ */
+ public int getNumDimensions() {
+ return arrayTypeSignature.getNumDimensions();
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Get the {@link ClassInfo} instance for the array element type.
+ *
+ * @return the {@link ClassInfo} instance for the array element type. Returns null if the element type was not
+ * found during the scan. In particular, will return null for arrays that have a primitive element type.
+ */
+ public ClassInfo getElementClassInfo() {
+ if (elementClassInfo == null) {
+ final TypeSignature elementTypeSignature = arrayTypeSignature.getElementTypeSignature();
+ if (!(elementTypeSignature instanceof BaseTypeSignature)) {
+ elementClassInfo = arrayTypeSignature.getElementTypeSignature().getClassInfo();
+ if (elementClassInfo != null) {
+ // Copy over relevant fields from array element ClassInfo
+ this.classpathElement = elementClassInfo.classpathElement;
+ this.classfileResource = elementClassInfo.classfileResource;
+ this.classLoader = elementClassInfo.classLoader;
+ this.isScannedClass = elementClassInfo.isScannedClass;
+ this.isExternalClass = elementClassInfo.isExternalClass;
+ this.moduleInfo = elementClassInfo.moduleInfo;
+ this.packageInfo = elementClassInfo.packageInfo;
+ }
+ }
+ }
+ return elementClassInfo;
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Get a {@code Class>} reference for the array element type. Causes the ClassLoader to load the element
+ * class, if it is not already loaded.
+ *
+ * @param ignoreExceptions
+ * Whether or not to ignore exceptions.
+ * @return a {@code Class>} reference for the array element type. Also works for arrays of primitive element
+ * type.
+ */
+ public Class> loadElementClass(final boolean ignoreExceptions) {
+ return arrayTypeSignature.loadElementClass(ignoreExceptions);
+ }
+
+ /**
+ * Get a {@code Class>} reference for the array element type. Causes the ClassLoader to load the element
+ * class, if it is not already loaded.
+ *
+ * @return a {@code Class>} reference for the array element type. Also works for arrays of primitive element
+ * type.
+ */
+ public Class> loadElementClass() {
+ return arrayTypeSignature.loadElementClass();
+ }
+
+ /**
+ * Obtain a {@code Class>} reference for the array class named by this {@link ArrayClassInfo} object. Causes
+ * the ClassLoader to load the element class, if it is not already loaded.
+ *
+ * @param ignoreExceptions
+ * Whether or not to ignore exceptions
+ * @return The class reference, or null, if ignoreExceptions is true and there was an exception or error loading
+ * the class.
+ * @throws IllegalArgumentException
+ * if ignoreExceptions is false and there were problems loading the class.
+ */
+ @Override
+ public Class> loadClass(final boolean ignoreExceptions) {
+ if (classRef == null) {
+ classRef = arrayTypeSignature.loadClass(ignoreExceptions);
+ }
+ return classRef;
+ }
+
+ /**
+ * Obtain a {@code Class>} reference for the array class named by this {@link ArrayClassInfo} object. Causes
+ * the ClassLoader to load the element class, if it is not already loaded.
+ *
+ * @return The class reference.
+ * @throws IllegalArgumentException
+ * if there were problems loading the class.
+ */
+ @Override
+ public Class> loadClass() {
+ if (classRef == null) {
+ classRef = arrayTypeSignature.loadClass();
+ }
+ return classRef;
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Get {@link ClassInfo} objects for any classes referenced in the type descriptor or type signature.
+ *
+ * @param classNameToClassInfo
+ * the map from class name to {@link ClassInfo}.
+ * @param refdClassInfo
+ * the referenced class info
+ */
+ @Override
+ protected void findReferencedClassInfo(final Map classNameToClassInfo,
+ final Set refdClassInfo, final LogNode log) {
+ super.findReferencedClassInfo(classNameToClassInfo, refdClassInfo, log);
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
+ /* (non-Javadoc)
+ * @see io.github.classgraph.ClassInfo#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ return super.equals(obj);
+ }
+
+ /* (non-Javadoc)
+ * @see io.github.classgraph.ClassInfo#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+}
diff --git a/src/main/java/io/github/classgraph/ArrayTypeSignature.java b/src/main/java/io/github/classgraph/ArrayTypeSignature.java
index aa7f6fa32..6565c9e14 100644
--- a/src/main/java/io/github/classgraph/ArrayTypeSignature.java
+++ b/src/main/java/io/github/classgraph/ArrayTypeSignature.java
@@ -28,18 +28,31 @@
*/
package io.github.classgraph;
+import java.lang.reflect.Array;
+import java.util.List;
+import java.util.Objects;
import java.util.Set;
+import io.github.classgraph.Classfile.TypePathNode;
import nonapi.io.github.classgraph.types.ParseException;
import nonapi.io.github.classgraph.types.Parser;
/** An array type signature. */
public class ArrayTypeSignature extends ReferenceTypeSignature {
- /** The array element type signature. */
- private final TypeSignature elementTypeSignature;
+ /** The raw type signature string for the array type. */
+ private final String typeSignatureStr;
- /** The number of array dimensions. */
- private final int numDims;
+ /** Human-readable class name, e.g. "java.lang.String[]". */
+ private String className;
+
+ /** Array class info. */
+ private ArrayClassInfo arrayClassInfo;
+
+ /** The element class. */
+ private Class> elementClassRef;
+
+ /** The nested type (another {@link ArrayTypeSignature}, or the base element type). */
+ private final TypeSignature nestedType;
// -------------------------------------------------------------------------------------------------------------
@@ -50,31 +63,98 @@ public class ArrayTypeSignature extends ReferenceTypeSignature {
* The type signature of the array elements.
* @param numDims
* The number of array dimensions.
+ * @param typeSignatureStr
+ * Raw array type signature string (e.g. "[[I")
*/
- ArrayTypeSignature(final TypeSignature elementTypeSignature, final int numDims) {
+ ArrayTypeSignature(final TypeSignature elementTypeSignature, final int numDims, final String typeSignatureStr) {
super();
- this.elementTypeSignature = elementTypeSignature;
- this.numDims = numDims;
+ final boolean typeSigHasTwoOrMoreDims = typeSignatureStr.startsWith("[[");
+ if (numDims < 1) {
+ throw new IllegalArgumentException("numDims < 1");
+ } else if ((numDims >= 2) != typeSigHasTwoOrMoreDims) {
+ throw new IllegalArgumentException("numDims does not match type signature");
+ }
+ this.typeSignatureStr = typeSignatureStr;
+ this.nestedType = typeSigHasTwoOrMoreDims
+ // Strip one array dimension for nested type
+ ? new ArrayTypeSignature(elementTypeSignature, numDims - 1, typeSignatureStr.substring(1))
+ // Nested type for innermost dimension is element type
+ : elementTypeSignature;
+ }
+
+ /**
+ * Get the raw array type signature string, e.g. "[[I".
+ *
+ * @return the raw array type signature string.
+ */
+ public String getTypeSignatureStr() {
+ return typeSignatureStr;
}
/**
- * Get the element type signature.
+ * Get the type signature of the innermost element type of the array.
*
- * @return The type signature of the array elements.
+ * @return The type signature of the innermost element type.
*/
public TypeSignature getElementTypeSignature() {
- return elementTypeSignature;
+ ArrayTypeSignature curr = this;
+ while (curr.nestedType instanceof ArrayTypeSignature) {
+ curr = (ArrayTypeSignature) curr.nestedType;
+ }
+ return curr.getNestedType();
}
/**
- * Get the number of dimensions.
+ * Get the number of dimensions of the array.
*
* @return The number of dimensions of the array.
*/
public int getNumDimensions() {
+ int numDims = 1;
+ ArrayTypeSignature curr = this;
+ while (curr.nestedType instanceof ArrayTypeSignature) {
+ curr = (ArrayTypeSignature) curr.nestedType;
+ numDims++;
+ }
return numDims;
}
+ /**
+ * Get the nested type, which is another {@link ArrayTypeSignature} with one dimension fewer, if this array has
+ * 2 or more dimensions, otherwise this returns the element type.
+ *
+ * @return The nested type.
+ */
+ public TypeSignature getNestedType() {
+ return nestedType;
+ }
+
+ @Override
+ protected void addTypeAnnotation(final List typePath, final AnnotationInfo annotationInfo) {
+ if (typePath.isEmpty()) {
+ addTypeAnnotation(annotationInfo);
+ } else {
+ final TypePathNode head = typePath.get(0);
+ if (head.typePathKind != 0 || head.typeArgumentIdx != 0) {
+ throw new IllegalArgumentException("typePath element contains bad values: " + head);
+ }
+ nestedType.addTypeAnnotation(typePath.subList(1, typePath.size()), annotationInfo);
+ }
+ }
+
+ /**
+ * Get a list of {@link AnnotationInfo} objects for the type annotations on this array type, or null if none.
+ *
+ * @see #getNestedType() if you want to read for type annotations on inner (nested) dimensions of the array
+ * type.
+ * @return a list of {@link AnnotationInfo} objects for the type annotations of on this array type, or null if
+ * none.
+ */
+ @Override
+ public AnnotationInfoList getTypeAnnotationInfo() {
+ return typeAnnotationInfo;
+ }
+
// -------------------------------------------------------------------------------------------------------------
/* (non-Javadoc)
@@ -82,8 +162,10 @@ public int getNumDimensions() {
*/
@Override
protected String getClassName() {
- // getClassInfo() is not valid for this type, so getClassName() does not need to be implemented
- throw new IllegalArgumentException("getClassName() cannot be called here");
+ if (className == null) {
+ className = toString();
+ }
+ return className;
}
/* (non-Javadoc)
@@ -91,7 +173,30 @@ protected String getClassName() {
*/
@Override
protected ClassInfo getClassInfo() {
- throw new IllegalArgumentException("getClassInfo() cannot be called here");
+ return getArrayClassInfo();
+ }
+
+ /**
+ * Return an {@link ArrayClassInfo} instance for the array class, cast to its superclass.
+ *
+ * @return the {@link ArrayClassInfo} instance.
+ */
+ public ArrayClassInfo getArrayClassInfo() {
+ if (arrayClassInfo == null) {
+ if (scanResult != null) {
+ final String clsName = getClassName();
+ // Cache ArrayClassInfo instances using scanResult.classNameToClassInfo, if scanResult is available
+ arrayClassInfo = (ArrayClassInfo) scanResult.classNameToClassInfo.get(clsName);
+ if (arrayClassInfo == null) {
+ scanResult.classNameToClassInfo.put(clsName, arrayClassInfo = new ArrayClassInfo(this));
+ arrayClassInfo.setScanResult(this.scanResult);
+ }
+ } else {
+ // scanResult is not yet available, create an uncached instance of an ArrayClassInfo for this type
+ arrayClassInfo = new ArrayClassInfo(this);
+ }
+ }
+ return arrayClassInfo;
}
/* (non-Javadoc)
@@ -100,17 +205,119 @@ protected ClassInfo getClassInfo() {
@Override
void setScanResult(final ScanResult scanResult) {
super.setScanResult(scanResult);
- if (elementTypeSignature != null) {
- elementTypeSignature.setScanResult(scanResult);
+ nestedType.setScanResult(scanResult);
+ if (arrayClassInfo != null) {
+ arrayClassInfo.setScanResult(scanResult);
}
}
- /* (non-Javadoc)
- * @see io.github.classgraph.HierarchicalTypeSignature#findReferencedClassNames(java.util.Set)
+ /**
+ * Get the names of any classes referenced in the type signature.
+ *
+ * @param refdClassNames
+ * the referenced class names.
+ */
+ @Override
+ protected void findReferencedClassNames(final Set refdClassNames) {
+ nestedType.findReferencedClassNames(refdClassNames);
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Get a {@code Class>} reference for the innermost array element type. Causes the ClassLoader to load the
+ * class, if it is not already loaded.
+ *
+ * @param ignoreExceptions
+ * Whether or not to ignore exceptions.
+ * @return a {@code Class>} reference for the innermost array element type. Also works for arrays of primitive
+ * element type.
+ */
+ public Class> loadElementClass(final boolean ignoreExceptions) {
+ if (elementClassRef == null) {
+ // Try resolving element type against base types (int, etc.)
+ final TypeSignature elementTypeSignature = getElementTypeSignature();
+ if (elementTypeSignature instanceof BaseTypeSignature) {
+ elementClassRef = ((BaseTypeSignature) elementTypeSignature).getType();
+ } else {
+ if (scanResult != null) {
+ elementClassRef = elementTypeSignature.loadClass(ignoreExceptions);
+ } else {
+ // Fallback, if scanResult is not set
+ final String elementTypeName = elementTypeSignature.getClassName();
+ try {
+ elementClassRef = Class.forName(elementTypeName);
+ } catch (final Throwable t) {
+ if (!ignoreExceptions) {
+ throw new IllegalArgumentException(
+ "Could not load array element class " + elementTypeName, t);
+ }
+ }
+ }
+ }
+ }
+ return elementClassRef;
+ }
+
+ /**
+ * Get a {@code Class>} reference for the array element type. Causes the ClassLoader to load the element
+ * class, if it is not already loaded.
+ *
+ * @return a {@code Class>} reference for the array element type. Also works for arrays of primitive element
+ * type.
+ */
+ public Class> loadElementClass() {
+ return loadElementClass(/* ignoreExceptions = */ false);
+ }
+
+ /**
+ * Obtain a {@code Class>} reference for the array class named by this {@link ArrayClassInfo} object. Causes
+ * the ClassLoader to load the element class, if it is not already loaded.
+ *
+ * @param ignoreExceptions
+ * Whether or not to ignore exceptions.
+ * @return The class reference, or null, if ignoreExceptions is true and there was an exception or error loading
+ * the class.
+ * @throws IllegalArgumentException
+ * if ignoreExceptions is false and there were problems loading the class.
+ */
+ @Override
+ public Class> loadClass(final boolean ignoreExceptions) {
+ if (classRef == null) {
+ // Get the element type
+ Class> eltClassRef = null;
+ if (ignoreExceptions) {
+ try {
+ eltClassRef = loadElementClass();
+ } catch (final IllegalArgumentException e) {
+ return null;
+ }
+ } else {
+ eltClassRef = loadElementClass();
+ }
+ if (eltClassRef == null) {
+ throw new IllegalArgumentException(
+ "Could not load array element class " + getElementTypeSignature());
+ }
+ // Create an array of the target number of dimensions, with size zero in each dimension
+ final Object eltArrayInstance = Array.newInstance(eltClassRef, new int[getNumDimensions()]);
+ // Get the class reference from the array instance
+ classRef = eltArrayInstance.getClass();
+ }
+ return classRef;
+ }
+
+ /**
+ * Obtain a {@code Class>} reference for the array class named by this {@link ArrayClassInfo} object. Causes
+ * the ClassLoader to load the element class, if it is not already loaded.
+ *
+ * @return The class reference.
+ * @throws IllegalArgumentException
+ * if there were problems loading the class.
*/
@Override
- void findReferencedClassNames(final Set referencedClassNames) {
- elementTypeSignature.findReferencedClassNames(referencedClassNames);
+ public Class> loadClass() {
+ return loadClass(/* ignoreExceptions = */ false);
}
// -------------------------------------------------------------------------------------------------------------
@@ -120,7 +327,7 @@ void findReferencedClassNames(final Set referencedClassNames) {
*/
@Override
public int hashCode() {
- return elementTypeSignature.hashCode() + numDims * 15;
+ return 1 + nestedType.hashCode();
}
/* (non-Javadoc)
@@ -128,14 +335,14 @@ public int hashCode() {
*/
@Override
public boolean equals(final Object obj) {
- if (this == obj) {
+ if (obj == this) {
return true;
- }
- if (!(obj instanceof ArrayTypeSignature)) {
+ } else if (!(obj instanceof ArrayTypeSignature)) {
return false;
}
- final ArrayTypeSignature o = (ArrayTypeSignature) obj;
- return o.elementTypeSignature.equals(this.elementTypeSignature) && o.numDims == this.numDims;
+ final ArrayTypeSignature other = (ArrayTypeSignature) obj;
+ return Objects.equals(this.typeAnnotationInfo, other.typeAnnotationInfo)
+ && this.nestedType.equals(other.nestedType);
}
/* (non-Javadoc)
@@ -150,24 +357,41 @@ public boolean equalsIgnoringTypeParams(final TypeSignature other) {
return false;
}
final ArrayTypeSignature o = (ArrayTypeSignature) other;
- return o.elementTypeSignature.equalsIgnoringTypeParams(this.elementTypeSignature)
- && o.numDims == this.numDims;
+ return this.nestedType.equalsIgnoringTypeParams(o.nestedType);
}
- /* (non-Javadoc)
- * @see io.github.classgraph.TypeSignature#toStringInternal(boolean)
- */
+ // -------------------------------------------------------------------------------------------------------------
+
@Override
- protected String toStringInternal(final boolean useSimpleNames) {
- final StringBuilder buf = new StringBuilder();
- buf.append(
- useSimpleNames ? elementTypeSignature.toStringWithSimpleNames() : elementTypeSignature.toString());
- for (int i = 0; i < numDims; i++) {
+ protected void toStringInternal(final boolean useSimpleNames, final AnnotationInfoList annotationsToExclude,
+ final StringBuilder buf) {
+ // Start with innermost array element type
+ getElementTypeSignature().toStringInternal(useSimpleNames, annotationsToExclude, buf);
+
+ // Append array dimensions
+ for (ArrayTypeSignature curr = this;;) {
+ if (curr.typeAnnotationInfo != null && !curr.typeAnnotationInfo.isEmpty()) {
+ for (final AnnotationInfo annotationInfo : curr.typeAnnotationInfo) {
+ if (buf.length() == 0 || buf.charAt(buf.length() - 1) != ' ') {
+ buf.append(' ');
+ }
+ annotationInfo.toString(useSimpleNames, buf);
+ }
+ buf.append(' ');
+ }
+
buf.append("[]");
+
+ if (curr.nestedType instanceof ArrayTypeSignature) {
+ curr = (ArrayTypeSignature) curr.nestedType;
+ } else {
+ break;
+ }
}
- return buf.toString();
}
+ // -------------------------------------------------------------------------------------------------------------
+
/**
* Parses the array type signature.
*
@@ -181,6 +405,7 @@ protected String toStringInternal(final boolean useSimpleNames) {
*/
static ArrayTypeSignature parse(final Parser parser, final String definingClassName) throws ParseException {
int numArrayDims = 0;
+ final int begin = parser.getPosition();
while (parser.peek() == '[') {
numArrayDims++;
parser.next();
@@ -190,7 +415,8 @@ static ArrayTypeSignature parse(final Parser parser, final String definingClassN
if (elementTypeSignature == null) {
throw new ParseException(parser, "elementTypeSignature == null");
}
- return new ArrayTypeSignature(elementTypeSignature, numArrayDims);
+ final String typeSignatureStr = parser.getSubsequence(begin, parser.getPosition()).toString();
+ return new ArrayTypeSignature(elementTypeSignature, numArrayDims, typeSignatureStr);
} else {
return null;
}
diff --git a/src/main/java/io/github/classgraph/BaseTypeSignature.java b/src/main/java/io/github/classgraph/BaseTypeSignature.java
index 1cd4d75cb..db42bbb88 100644
--- a/src/main/java/io/github/classgraph/BaseTypeSignature.java
+++ b/src/main/java/io/github/classgraph/BaseTypeSignature.java
@@ -28,69 +28,179 @@
*/
package io.github.classgraph;
+import java.util.List;
+import java.util.Objects;
import java.util.Set;
+import io.github.classgraph.Classfile.TypePathNode;
import nonapi.io.github.classgraph.types.Parser;
/** A type signature for a base type (byte, char, double, float, int, long, short, boolean, or void). */
public class BaseTypeSignature extends TypeSignature {
- /** A base type (byte, char, double, float, int, long, short, boolean, or void). */
- private final String baseType;
+ /** The type signature character used to represent the base type. */
+ private final char typeSignatureChar;
// -------------------------------------------------------------------------------------------------------------
/**
* Constructor.
- *
- * @param baseType
- * the base type
*/
- BaseTypeSignature(final String baseType) {
+ BaseTypeSignature(final char typeSignatureChar) {
super();
- this.baseType = baseType;
+ switch (typeSignatureChar) {
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'F':
+ case 'I':
+ case 'J':
+ case 'S':
+ case 'Z':
+ case 'V':
+ this.typeSignatureChar = typeSignatureChar;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Illegal " + BaseTypeSignature.class.getSimpleName() + " type: '" + typeSignatureChar + "'");
+ }
}
// -------------------------------------------------------------------------------------------------------------
/**
- * Get the type as a string.
+ * Get the name of the type as a string.
*
- * @return The base type, such as "int", "float", or "void".
+ * @param typeChar
+ * the type character, e.g. 'I'.
+ * @return The name of the type, e.g. "int", or null if there was no match.
*/
- public String getTypeStr() {
- return baseType;
+ static String getTypeStr(final char typeChar) {
+ switch (typeChar) {
+ case 'B':
+ return "byte";
+ case 'C':
+ return "char";
+ case 'D':
+ return "double";
+ case 'F':
+ return "float";
+ case 'I':
+ return "int";
+ case 'J':
+ return "long";
+ case 'S':
+ return "short";
+ case 'Z':
+ return "boolean";
+ case 'V':
+ return "void";
+ default:
+ return null;
+ }
}
/**
- * Get the type.
+ * Get the name of the type as a string.
*
- * @return The class of the base type, such as int.class, float.class, or void.class.
+ * @param typeStr
+ * the type character, e.g. "int".
+ * @return The type, character, e.g. 'I', or '\0' if there was no match.
*/
- public Class> getType() {
- switch (baseType) {
+ static char getTypeChar(final String typeStr) {
+ switch (typeStr) {
case "byte":
- return byte.class;
+ return 'B';
case "char":
- return char.class;
+ return 'C';
case "double":
- return double.class;
+ return 'D';
case "float":
- return float.class;
+ return 'F';
case "int":
- return int.class;
+ return 'I';
case "long":
- return long.class;
+ return 'J';
case "short":
- return short.class;
+ return 'S';
case "boolean":
- return boolean.class;
+ return 'Z';
case "void":
+ return 'V';
+ default:
+ return '\0';
+ }
+ }
+
+ /**
+ * Get the type for a type character.
+ *
+ * @param typeChar
+ * the type character, e.g. 'I'.
+ * @return The type class, e.g. int.class, or null if there was no match.
+ */
+ static Class> getType(final char typeChar) {
+ switch (typeChar) {
+ case 'B':
+ return byte.class;
+ case 'C':
+ return char.class;
+ case 'D':
+ return double.class;
+ case 'F':
+ return float.class;
+ case 'I':
+ return int.class;
+ case 'J':
+ return long.class;
+ case 'S':
+ return short.class;
+ case 'Z':
+ return boolean.class;
+ case 'V':
return void.class;
default:
- throw new IllegalArgumentException("Unknown base type " + baseType);
+ return null;
}
}
+ // -------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Get the type signature char used to represent the type, e.g. 'I' for int.
+ *
+ * @return the type signature char, as a one-char String.
+ */
+ public char getTypeSignatureChar() {
+ return typeSignatureChar;
+ }
+
+ /**
+ * Get the name of the type as a string.
+ *
+ * @return The name of the type, such as "int", "float", or "void".
+ */
+ public String getTypeStr() {
+ return getTypeStr(typeSignatureChar);
+ }
+
+ /**
+ * Get the type.
+ *
+ * @return The class of the base type, such as int.class, float.class, or void.class.
+ */
+ public Class> getType() {
+ return getType(typeSignatureChar);
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
+ @Override
+ protected void addTypeAnnotation(final List typePath, final AnnotationInfo annotationInfo) {
+ addTypeAnnotation(annotationInfo);
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
/* (non-Javadoc)
* @see io.github.classgraph.ScanResultObject#loadClass()
*/
@@ -106,8 +216,8 @@ Class> loadClass() {
Class loadClass(final Class superclassOrInterfaceType) {
final Class> type = getType();
if (!superclassOrInterfaceType.isAssignableFrom(type)) {
- throw new IllegalArgumentException(
- "Primitive class " + baseType + " cannot be cast to " + superclassOrInterfaceType.getName());
+ throw new IllegalArgumentException("Primitive class " + getTypeStr() + " cannot be cast to "
+ + superclassOrInterfaceType.getName());
}
@SuppressWarnings("unchecked")
final Class classT = (Class) type;
@@ -127,31 +237,31 @@ static BaseTypeSignature parse(final Parser parser) {
switch (parser.peek()) {
case 'B':
parser.next();
- return new BaseTypeSignature("byte");
+ return new BaseTypeSignature('B');
case 'C':
parser.next();
- return new BaseTypeSignature("char");
+ return new BaseTypeSignature('C');
case 'D':
parser.next();
- return new BaseTypeSignature("double");
+ return new BaseTypeSignature('D');
case 'F':
parser.next();
- return new BaseTypeSignature("float");
+ return new BaseTypeSignature('F');
case 'I':
parser.next();
- return new BaseTypeSignature("int");
+ return new BaseTypeSignature('I');
case 'J':
parser.next();
- return new BaseTypeSignature("long");
+ return new BaseTypeSignature('J');
case 'S':
parser.next();
- return new BaseTypeSignature("short");
+ return new BaseTypeSignature('S');
case 'Z':
parser.next();
- return new BaseTypeSignature("boolean");
+ return new BaseTypeSignature('Z');
case 'V':
parser.next();
- return new BaseTypeSignature("void");
+ return new BaseTypeSignature('V');
default:
return null;
}
@@ -164,8 +274,7 @@ static BaseTypeSignature parse(final Parser parser) {
*/
@Override
protected String getClassName() {
- // getClassInfo() is not valid for this type, so getClassName() does not need to be implemented
- throw new IllegalArgumentException("getClassName() cannot be called here");
+ return getTypeStr();
}
/* (non-Javadoc)
@@ -173,15 +282,32 @@ protected String getClassName() {
*/
@Override
protected ClassInfo getClassInfo() {
- throw new IllegalArgumentException("getClassInfo() cannot be called here");
+ return null;
+ }
+
+ /**
+ * Get the names of any classes referenced in the type signature.
+ *
+ * @param refdClassNames
+ * the referenced class names.
+ */
+ @Override
+ protected void findReferencedClassNames(final Set refdClassNames) {
+ // Don't add byte.class, int.class, etc.
}
/* (non-Javadoc)
- * @see io.github.classgraph.HierarchicalTypeSignature#findReferencedClassNames(java.util.Set)
+ * @see io.github.classgraph.ScanResultObject#setScanResult(ScanResult)
*/
@Override
- void findReferencedClassNames(final Set classNameListOut) {
- // Don't return byte.class, int.class, etc.
+ void setScanResult(final ScanResult scanResult) {
+ // Don't set ScanResult for BaseTypeSignature objects (#419).
+ // The ScanResult is not needed, since this class does not classload through the ScanResult.
+ // Also, specific instances of BaseTypeSignature for each primitive type are assigned to static fields
+ // in this class, which are shared across all usages of this class, so they should not contain any
+ // values that are specific to a given ScanResult. Setting the ScanResult from different scan processes
+ // would cause the scanResult field to only reflect the result of the most recent scan, and the reference
+ // to that scan would prevent garbage collection.
}
// -------------------------------------------------------------------------------------------------------------
@@ -191,7 +317,7 @@ void findReferencedClassNames(final Set classNameListOut) {
*/
@Override
public int hashCode() {
- return baseType.hashCode();
+ return typeSignatureChar;
}
/* (non-Javadoc)
@@ -199,7 +325,14 @@ public int hashCode() {
*/
@Override
public boolean equals(final Object obj) {
- return obj instanceof BaseTypeSignature && ((BaseTypeSignature) obj).baseType.equals(this.baseType);
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof BaseTypeSignature)) {
+ return false;
+ }
+ final BaseTypeSignature other = (BaseTypeSignature) obj;
+ return Objects.equals(this.typeAnnotationInfo, other.typeAnnotationInfo)
+ && other.typeSignatureChar == this.typeSignatureChar;
}
/* (non-Javadoc)
@@ -210,14 +343,22 @@ public boolean equalsIgnoringTypeParams(final TypeSignature other) {
if (!(other instanceof BaseTypeSignature)) {
return false;
}
- return baseType.equals(((BaseTypeSignature) other).baseType);
+ return typeSignatureChar == ((BaseTypeSignature) other).typeSignatureChar;
}
- /* (non-Javadoc)
- * @see io.github.classgraph.TypeSignature#toStringInternal(boolean)
- */
+ // -------------------------------------------------------------------------------------------------------------
+
@Override
- protected String toStringInternal(final boolean useSimpleNames) {
- return baseType;
+ protected void toStringInternal(final boolean useSimpleNames, final AnnotationInfoList annotationsToExclude,
+ final StringBuilder buf) {
+ if (typeAnnotationInfo != null) {
+ for (final AnnotationInfo annotationInfo : typeAnnotationInfo) {
+ if (annotationsToExclude == null || !annotationsToExclude.contains(annotationInfo)) {
+ annotationInfo.toString(useSimpleNames, buf);
+ buf.append(' ');
+ }
+ }
+ }
+ buf.append(getTypeStr());
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/classgraph/ClassGraph.java b/src/main/java/io/github/classgraph/ClassGraph.java
index 0cba62060..d77c06a8b 100644
--- a/src/main/java/io/github/classgraph/ClassGraph.java
+++ b/src/main/java/io/github/classgraph/ClassGraph.java
@@ -29,8 +29,13 @@
package io.github.classgraph;
import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.AccessibleObject;
import java.net.URI;
import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
@@ -41,11 +46,12 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Pattern;
-import nonapi.io.github.classgraph.ScanSpec;
-import nonapi.io.github.classgraph.WhiteBlackList;
import nonapi.io.github.classgraph.classpath.SystemJarFinder;
import nonapi.io.github.classgraph.concurrency.AutoCloseableExecutorService;
import nonapi.io.github.classgraph.concurrency.InterruptionChecker;
+import nonapi.io.github.classgraph.reflection.ReflectionUtils;
+import nonapi.io.github.classgraph.scanspec.AcceptReject;
+import nonapi.io.github.classgraph.scanspec.ScanSpec;
import nonapi.io.github.classgraph.utils.JarUtils;
import nonapi.io.github.classgraph.utils.LogNode;
import nonapi.io.github.classgraph.utils.VersionFinder;
@@ -59,7 +65,6 @@
* https://github.com/classgraph/classgraph/wiki
*/
public class ClassGraph {
-
/** The scanning specification. */
ScanSpec scanSpec = new ScanSpec();
@@ -67,7 +72,7 @@ public class ClassGraph {
* The default number of worker threads to use while scanning. This number gave the best results on a relatively
* modern laptop with SSD, while scanning a large classpath.
*/
- private static final int DEFAULT_NUM_WORKER_THREADS = Math.max(
+ static final int DEFAULT_NUM_WORKER_THREADS = Math.max(
// Always scan with at least 2 threads
2, //
(int) Math.ceil(
@@ -77,14 +82,54 @@ public class ClassGraph {
Runtime.getRuntime().availableProcessors() * 1.25) //
);
- /** If non-null, log while scanning. */
+ /**
+ * Method to use to attempt to circumvent encapsulation in JDK 16+, in order to get access to a classloader's
+ * private classpath.
+ */
+ public static enum CircumventEncapsulationMethod {
+ /**
+ * Use the reflection API and {@link AccessibleObject#setAccessible(boolean)} to try to gain access to
+ * private classpath fields or methods in order to determine the classpath.
+ */
+ NONE,
+
+ /**
+ * Use the Narcissus library to try to gain access to
+ * private classloader fields or methods in order to determine the classpath.
+ */
+ NARCISSUS,
+ }
+
+ /**
+ * If you are running on JDK 16+, the JDK enforces strong encapsulation, and ClassGraph may be unable to read
+ * the classpath from your classloader if the classloader does not make the classpath available via a public
+ * method or field.
+ *
+ *
+ * To enable a workaround to this, set this static field to {@link CircumventEncapsulationMethod#NARCISSUS}
+ * before interacting with ClassGraph in any other way, and also include the
+ * Narcissus library on the classpath or module path.
+ *
+ *
+ * Narcissus uses JNI to circumvent encapsulation and field/method access controls. Narcissus employs a native
+ * code library, and is currently only compiled for Linux x86/x64, Windows x86/x64, and Mac OS X x64 bit.
+ */
+ public static CircumventEncapsulationMethod CIRCUMVENT_ENCAPSULATION = CircumventEncapsulationMethod.NONE;
+
+ private final ReflectionUtils reflectionUtils;
+
+ /**
+ * If non-null, log while scanning.
+ */
private LogNode topLevelLog;
// -------------------------------------------------------------------------------------------------------------
/** Construct a ClassGraph instance. */
public ClassGraph() {
- // Blank
+ reflectionUtils = new ReflectionUtils();
+ // Initialize ScanResult, if this is the first call to ClassGraph constructor
+ ScanResult.init(reflectionUtils);
}
/**
@@ -110,6 +155,20 @@ public ClassGraph verbose() {
return this;
}
+ /**
+ * Switches on verbose logging to System.err if verbose is true.
+ *
+ * @param verbose
+ * if true, enable verbose logging.
+ * @return this (for method chaining).
+ */
+ public ClassGraph verbose(final boolean verbose) {
+ if (verbose) {
+ verbose();
+ }
+ return this;
+ }
+
// -------------------------------------------------------------------------------------------------------------
/**
@@ -137,12 +196,14 @@ public ClassGraph enableAllInfo() {
}
/**
- * Enables the scanning of classfiles, producing {@link ClassInfo} objects in the {@link ScanResult}.
+ * Enables the scanning of classfiles, producing {@link ClassInfo} objects in the {@link ScanResult}. Implicitly
+ * disables {@link #enableMultiReleaseVersions()}.
*
* @return this (for method chaining).
*/
public ClassGraph enableClassInfo() {
scanSpec.enableClassInfo = true;
+ scanSpec.enableMultiReleaseVersions = false;
return this;
}
@@ -214,7 +275,30 @@ public ClassGraph ignoreFieldVisibility() {
/**
* Enables the saving of static final field constant initializer values. By default, constant initializer values
- * are not scanned. Automatically calls {@link #enableClassInfo()} and {@link #enableFieldInfo()}.
+ * are not scanned. If this is enabled, you can obtain the constant field initializer values from
+ * {@link FieldInfo#getConstantInitializerValue()}.
+ *
+ *
+ * Note that constant initializer values are usually only of primitive type, or String constants (or values that
+ * can be computed and reduced to one of those types at compiletime).
+ *
+ *
+ * Also note that it is up to the compiler as to whether or not a constant-valued field is assigned as a
+ * constant in the field definition itself, or whether it is assigned manually in static class initializer
+ * blocks -- so your mileage may vary in being able to extract constant initializer values.
+ *
+ *
+ * In fact in Kotlin, even constant initializers for non-static / non-final fields are stored in a field
+ * attribute in the classfile (and so these values may be picked up by ClassGraph by calling this method),
+ * although any field initializers for non-static fields are supposed to be ignored by the JVM according to the
+ * classfile spec, so the Kotlin compiler may change in future to stop generating these values, and you probably
+ * shouldn't rely on being able to get the initializers for non-static fields in Kotlin. (As far as non-final
+ * fields, javac simply does not add constant initializer values to the field attributes list for non-final
+ * fields, even if they are static, but the spec doesn't say whether or not the JVM should ignore constant
+ * initializers for non-final fields.)
+ *
+ *
+ * Automatically calls {@link #enableClassInfo()} and {@link #enableFieldInfo()}.
*
* @return this (for method chaining).
*/
@@ -319,9 +403,9 @@ public ClassGraph disableModuleScanning() {
// -------------------------------------------------------------------------------------------------------------
/**
- * Causes ClassGraph to return classes that are not in the whitelisted packages, but that are directly referred
- * to by classes within whitelisted packages as a superclass, implemented interface or annotation.
- * (Automatically calls {@link #enableClassInfo()}.)
+ * Causes ClassGraph to return classes that are not in the accepted packages, but that are directly referred to
+ * by classes within accepted packages as a superclass, implemented interface or annotation. (Automatically
+ * calls {@link #enableClassInfo()}.)
*
* @return this (for method chaining).
*/
@@ -370,7 +454,12 @@ public ClassGraph removeTemporaryFilesAfterScan() {
* @return this (for method chaining).
*/
public ClassGraph overrideClasspath(final String overrideClasspath) {
- scanSpec.overrideClasspath(overrideClasspath);
+ if (overrideClasspath.isEmpty()) {
+ throw new IllegalArgumentException("Can't override classpath with an empty path");
+ }
+ for (final String classpathElement : JarUtils.smartPathSplit(overrideClasspath, scanSpec)) {
+ scanSpec.addClasspathOverride(classpathElement);
+ }
return this;
}
@@ -387,11 +476,12 @@ public ClassGraph overrideClasspath(final String overrideClasspath) {
* @return this (for method chaining).
*/
public ClassGraph overrideClasspath(final Iterable> overrideClasspathElements) {
- final String overrideClasspath = JarUtils.pathElementsToPathStr(overrideClasspathElements);
- if (overrideClasspath.isEmpty()) {
+ if (!overrideClasspathElements.iterator().hasNext()) {
throw new IllegalArgumentException("Can't override classpath with an empty path");
}
- overrideClasspath(overrideClasspath);
+ for (final Object classpathElement : overrideClasspathElements) {
+ scanSpec.addClasspathOverride(classpathElement);
+ }
return this;
}
@@ -408,11 +498,12 @@ public ClassGraph overrideClasspath(final Iterable> overrideClasspathElements)
* @return this (for method chaining).
*/
public ClassGraph overrideClasspath(final Object... overrideClasspathElements) {
- final String overrideClasspath = JarUtils.pathElementsToPathStr(overrideClasspathElements);
- if (overrideClasspath.isEmpty()) {
+ if (overrideClasspathElements.length == 0) {
throw new IllegalArgumentException("Can't override classpath with an empty path");
}
- overrideClasspath(overrideClasspath);
+ for (final Object classpathElement : overrideClasspathElements) {
+ scanSpec.addClasspathOverride(classpathElement);
+ }
return this;
}
@@ -437,6 +528,22 @@ public interface ClasspathElementFilter {
boolean includeClasspathElement(String classpathElementPathStr);
}
+ /**
+ * Add a classpath element URL filter. The includeClasspathElement method should return true if the {@link URL}
+ * passed to it corresponds to a classpath element that you want to scan.
+ */
+ @FunctionalInterface
+ public interface ClasspathElementURLFilter {
+ /**
+ * Whether or not to include a given classpath element in the scan.
+ *
+ * @param classpathElementURL
+ * The {@link URL} of a classpath element.
+ * @return true if you want to scan the {@link URL}.
+ */
+ boolean includeClasspathElement(URL classpathElementURL);
+ }
+
/**
* Add a classpath element filter. The provided ClasspathElementFilter should return true if the path string
* passed to it is a path you want to scan.
@@ -451,6 +558,20 @@ public ClassGraph filterClasspathElements(final ClasspathElementFilter classpath
return this;
}
+ /**
+ * Add a classpath element filter. The provided ClasspathElementFilter should return true if the {@link URL}
+ * passed to it is a URL you want to scan.
+ *
+ * @param classpathElementURLFilter
+ * The filter function to use. This function should return true if the classpath element {@link URL}
+ * should be scanned, and false if not.
+ * @return this (for method chaining).
+ */
+ public ClassGraph filterClasspathElementsByURL(final ClasspathElementURLFilter classpathElementURLFilter) {
+ scanSpec.filterClasspathElements(classpathElementURLFilter);
+ return this;
+ }
+
// -------------------------------------------------------------------------------------------------------------
/**
@@ -471,7 +592,9 @@ public ClassGraph addClassLoader(final ClassLoader classLoader) {
/**
* Completely override (and ignore) system ClassLoaders and the java.class.path system property. Also causes
- * modules not to be scanned.
+ * modules not to be scanned. Note that you may want to use this together with
+ * {@link #ignoreParentClassLoaders()} to extract classpath URLs from only the classloaders you specified in the
+ * parameter to `overrideClassLoaders`, and not their parent classloaders.
*
*
* This call is ignored if {@link #overrideClasspath(String)} is called.
@@ -548,7 +671,7 @@ public ClassGraph ignoreParentModuleLayers() {
* Scan one or more specific packages and their sub-packages.
*
*
- * N.B. Automatically calls {@link #enableClassInfo()} -- call {@link #whitelistPaths(String...)} instead if you
+ * N.B. Automatically calls {@link #enableClassInfo()} -- call {@link #acceptPaths(String...)} instead if you
* only need to scan resources.
*
* @param packageNames
@@ -556,35 +679,45 @@ public ClassGraph ignoreParentModuleLayers() {
* wildcard ({@code '*'}).
* @return this (for method chaining).
*/
- public ClassGraph whitelistPackages(final String... packageNames) {
+ public ClassGraph acceptPackages(final String... packageNames) {
enableClassInfo();
for (final String packageName : packageNames) {
- final String packageNameNormalized = WhiteBlackList.normalizePackageOrClassName(packageName);
- if (packageNameNormalized.startsWith("!") || packageNameNormalized.startsWith("-")) {
- throw new IllegalArgumentException(
- "This style of whitelisting/blacklisting is no longer supported: " + packageNameNormalized);
- }
- // Whitelist package
- scanSpec.packageWhiteBlackList.addToWhitelist(packageNameNormalized);
- final String path = WhiteBlackList.packageNameToPath(packageNameNormalized);
- scanSpec.pathWhiteBlackList.addToWhitelist(path + "/");
+ final String packageNameNormalized = AcceptReject.normalizePackageOrClassName(packageName);
+ // Accept package
+ scanSpec.packageAcceptReject.addToAccept(packageNameNormalized);
+ final String path = AcceptReject.packageNameToPath(packageNameNormalized);
+ scanSpec.pathAcceptReject.addToAccept(path + "/");
if (packageNameNormalized.isEmpty()) {
- scanSpec.pathWhiteBlackList.addToWhitelist("");
+ scanSpec.pathAcceptReject.addToAccept("");
}
if (!packageNameNormalized.contains("*")) {
- // Whitelist sub-packages
+ // Accept sub-packages
if (packageNameNormalized.isEmpty()) {
- scanSpec.packagePrefixWhiteBlackList.addToWhitelist("");
- scanSpec.pathPrefixWhiteBlackList.addToWhitelist("");
+ scanSpec.packagePrefixAcceptReject.addToAccept("");
+ scanSpec.pathPrefixAcceptReject.addToAccept("");
} else {
- scanSpec.packagePrefixWhiteBlackList.addToWhitelist(packageNameNormalized + ".");
- scanSpec.pathPrefixWhiteBlackList.addToWhitelist(path + "/");
+ scanSpec.packagePrefixAcceptReject.addToAccept(packageNameNormalized + ".");
+ scanSpec.pathPrefixAcceptReject.addToAccept(path + "/");
}
}
}
return this;
}
+ /**
+ * Use {@link #acceptPackages(String...)} instead.
+ *
+ * @param packageNames
+ * The fully-qualified names of packages to scan (using '.' as a separator). May include a glob
+ * wildcard ({@code '*'}).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #acceptPackages(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph whitelistPackages(final String... packageNames) {
+ return acceptPackages(packageNames);
+ }
+
/**
* Scan one or more specific paths, and their sub-directories or nested paths.
*
@@ -593,36 +726,50 @@ public ClassGraph whitelistPackages(final String... packageNames) {
* separator). May include a glob wildcard ({@code '*'}).
* @return this (for method chaining).
*/
- public ClassGraph whitelistPaths(final String... paths) {
+ public ClassGraph acceptPaths(final String... paths) {
for (final String path : paths) {
- final String pathNormalized = WhiteBlackList.normalizePath(path);
- // Whitelist path
- final String packageName = WhiteBlackList.pathToPackageName(pathNormalized);
- scanSpec.packageWhiteBlackList.addToWhitelist(packageName);
- scanSpec.pathWhiteBlackList.addToWhitelist(pathNormalized + "/");
+ final String pathNormalized = AcceptReject.normalizePath(path);
+ // Accept path
+ final String packageName = AcceptReject.pathToPackageName(pathNormalized);
+ scanSpec.packageAcceptReject.addToAccept(packageName);
+ scanSpec.pathAcceptReject.addToAccept(pathNormalized + "/");
if (pathNormalized.isEmpty()) {
- scanSpec.pathWhiteBlackList.addToWhitelist("");
+ scanSpec.pathAcceptReject.addToAccept("");
}
if (!pathNormalized.contains("*")) {
- // Whitelist sub-directories / nested paths
+ // Accept sub-directories / nested paths
if (pathNormalized.isEmpty()) {
- scanSpec.packagePrefixWhiteBlackList.addToWhitelist("");
- scanSpec.pathPrefixWhiteBlackList.addToWhitelist("");
+ scanSpec.packagePrefixAcceptReject.addToAccept("");
+ scanSpec.pathPrefixAcceptReject.addToAccept("");
} else {
- scanSpec.packagePrefixWhiteBlackList.addToWhitelist(packageName + ".");
- scanSpec.pathPrefixWhiteBlackList.addToWhitelist(pathNormalized + "/");
+ scanSpec.packagePrefixAcceptReject.addToAccept(packageName + ".");
+ scanSpec.pathPrefixAcceptReject.addToAccept(pathNormalized + "/");
}
}
}
return this;
}
+ /**
+ * Use {@link #acceptPaths(String...)} instead.
+ *
+ * @param paths
+ * The paths to scan, relative to the package root of the classpath element (with '/' as a
+ * separator). May include a glob wildcard ({@code '*'}).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #acceptPaths(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph whitelistPaths(final String... paths) {
+ return acceptPaths(paths);
+ }
+
/**
* Scan one or more specific packages, without recursively scanning sub-packages unless they are themselves
- * whitelisted.
+ * accepted.
*
*
- * N.B. Automatically calls {@link #enableClassInfo()} -- call {@link #whitelistPathsNonRecursive(String...)}
+ * N.B. Automatically calls {@link #enableClassInfo()} -- call {@link #acceptPathsNonRecursive(String...)}
* instead if you only need to scan resources.
*
*
@@ -635,27 +782,40 @@ public ClassGraph whitelistPaths(final String... paths) {
*
* @return this (for method chaining).
*/
- public ClassGraph whitelistPackagesNonRecursive(final String... packageNames) {
+ public ClassGraph acceptPackagesNonRecursive(final String... packageNames) {
enableClassInfo();
for (final String packageName : packageNames) {
- final String packageNameNormalized = WhiteBlackList.normalizePackageOrClassName(packageName);
+ final String packageNameNormalized = AcceptReject.normalizePackageOrClassName(packageName);
if (packageNameNormalized.contains("*")) {
throw new IllegalArgumentException("Cannot use a glob wildcard here: " + packageNameNormalized);
}
- // Whitelist package, but not sub-packages
- scanSpec.packageWhiteBlackList.addToWhitelist(packageNameNormalized);
- scanSpec.pathWhiteBlackList
- .addToWhitelist(WhiteBlackList.packageNameToPath(packageNameNormalized) + "/");
+ // Accept package, but not sub-packages
+ scanSpec.packageAcceptReject.addToAccept(packageNameNormalized);
+ scanSpec.pathAcceptReject.addToAccept(AcceptReject.packageNameToPath(packageNameNormalized) + "/");
if (packageNameNormalized.isEmpty()) {
- scanSpec.pathWhiteBlackList.addToWhitelist("");
+ scanSpec.pathAcceptReject.addToAccept("");
}
}
return this;
}
+ /**
+ * Use {@link #acceptPackagesNonRecursive(String...)} instead.
+ *
+ * @param packageNames
+ * The fully-qualified names of packages to scan (with '.' as a separator). May not include a glob
+ * wildcard ({@code '*'}).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #acceptPackagesNonRecursive(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph whitelistPackagesNonRecursive(final String... packageNames) {
+ return acceptPackagesNonRecursive(packageNames);
+ }
+
/**
* Scan one or more specific paths, without recursively scanning sub-directories or nested paths unless they are
- * themselves whitelisted.
+ * themselves accepted.
*
*
* This may be particularly useful for scanning the package root ("") without recursively scanning everything in
@@ -666,211 +826,299 @@ public ClassGraph whitelistPackagesNonRecursive(final String... packageNames) {
* separator). May not include a glob wildcard ({@code '*'}).
* @return this (for method chaining).
*/
- public ClassGraph whitelistPathsNonRecursive(final String... paths) {
+ public ClassGraph acceptPathsNonRecursive(final String... paths) {
for (final String path : paths) {
if (path.contains("*")) {
throw new IllegalArgumentException("Cannot use a glob wildcard here: " + path);
}
- final String pathNormalized = WhiteBlackList.normalizePath(path);
- // Whitelist path, but not sub-directories / nested paths
- scanSpec.packageWhiteBlackList.addToWhitelist(WhiteBlackList.pathToPackageName(pathNormalized));
- scanSpec.pathWhiteBlackList.addToWhitelist(pathNormalized + "/");
+ final String pathNormalized = AcceptReject.normalizePath(path);
+ // Accept path, but not sub-directories / nested paths
+ scanSpec.packageAcceptReject.addToAccept(AcceptReject.pathToPackageName(pathNormalized));
+ scanSpec.pathAcceptReject.addToAccept(pathNormalized + "/");
if (pathNormalized.isEmpty()) {
- scanSpec.pathWhiteBlackList.addToWhitelist("");
+ scanSpec.pathAcceptReject.addToAccept("");
}
}
return this;
}
+ /**
+ * Use {@link #acceptPathsNonRecursive(String...)} instead.
+ *
+ * @param paths
+ * The paths to scan, relative to the package root of the classpath element (with '/' as a
+ * separator). May not include a glob wildcard ({@code '*'}).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #acceptPathsNonRecursive(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph whitelistPathsNonRecursive(final String... paths) {
+ return acceptPathsNonRecursive(paths);
+ }
+
/**
* Prevent the scanning of one or more specific packages and their sub-packages.
*
*
- * N.B. Automatically calls {@link #enableClassInfo()} -- call {@link #blacklistPaths(String...)} instead if you
+ * N.B. Automatically calls {@link #enableClassInfo()} -- call {@link #rejectPaths(String...)} instead if you
* only need to scan resources.
*
* @param packageNames
- * The fully-qualified names of packages to blacklist (with '.' as a separator). May include a glob
+ * The fully-qualified names of packages to reject (with '.' as a separator). May include a glob
* wildcard ({@code '*'}).
* @return this (for method chaining).
*/
- public ClassGraph blacklistPackages(final String... packageNames) {
+ public ClassGraph rejectPackages(final String... packageNames) {
enableClassInfo();
for (final String packageName : packageNames) {
- final String packageNameNormalized = WhiteBlackList.normalizePackageOrClassName(packageName);
+ final String packageNameNormalized = AcceptReject.normalizePackageOrClassName(packageName);
if (packageNameNormalized.isEmpty()) {
throw new IllegalArgumentException(
- "Blacklisting the root package (\"\") will cause nothing to be scanned");
+ "Rejecting the root package (\"\") will cause nothing to be scanned");
}
- // Blacklisting always prevents further recursion, no need to blacklist sub-packages
- scanSpec.packageWhiteBlackList.addToBlacklist(packageNameNormalized);
- final String path = WhiteBlackList.packageNameToPath(packageNameNormalized);
- scanSpec.pathWhiteBlackList.addToBlacklist(path + "/");
+ // Rejecting always prevents further recursion, no need to reject sub-packages
+ scanSpec.packageAcceptReject.addToReject(packageNameNormalized);
+ final String path = AcceptReject.packageNameToPath(packageNameNormalized);
+ scanSpec.pathAcceptReject.addToReject(path + "/");
if (!packageNameNormalized.contains("*")) {
- // Blacklist sub-packages (zipfile entries can occur in any order)
- scanSpec.packagePrefixWhiteBlackList.addToBlacklist(packageNameNormalized + ".");
- scanSpec.pathPrefixWhiteBlackList.addToBlacklist(path + "/");
+ // Reject sub-packages (zipfile entries can occur in any order)
+ scanSpec.packagePrefixAcceptReject.addToReject(packageNameNormalized + ".");
+ scanSpec.pathPrefixAcceptReject.addToReject(path + "/");
}
}
return this;
}
+ /**
+ * Use {@link #rejectPackages(String...)} instead.
+ *
+ * @param packageNames
+ * The fully-qualified names of packages to reject (with '.' as a separator). May include a glob
+ * wildcard ({@code '*'}).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #rejectPackages(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph blacklistPackages(final String... packageNames) {
+ return rejectPackages(packageNames);
+ }
+
/**
* Prevent the scanning of one or more specific paths and their sub-directories / nested paths.
*
* @param paths
- * The paths to blacklist (with '/' as a separator). May include a glob wildcard ({@code '*'}).
+ * The paths to reject (with '/' as a separator). May include a glob wildcard ({@code '*'}).
* @return this (for method chaining).
*/
- public ClassGraph blacklistPaths(final String... paths) {
+ public ClassGraph rejectPaths(final String... paths) {
for (final String path : paths) {
- final String pathNormalized = WhiteBlackList.normalizePath(path);
+ final String pathNormalized = AcceptReject.normalizePath(path);
if (pathNormalized.isEmpty()) {
throw new IllegalArgumentException(
- "Blacklisting the root package (\"\") will cause nothing to be scanned");
+ "Rejecting the root package (\"\") will cause nothing to be scanned");
}
- // Blacklisting always prevents further recursion, no need to blacklist sub-directories / nested paths
- final String packageName = WhiteBlackList.pathToPackageName(pathNormalized);
- scanSpec.packageWhiteBlackList.addToBlacklist(packageName);
- scanSpec.pathWhiteBlackList.addToBlacklist(pathNormalized + "/");
+ // Rejecting always prevents further recursion, no need to reject sub-directories / nested paths
+ final String packageName = AcceptReject.pathToPackageName(pathNormalized);
+ scanSpec.packageAcceptReject.addToReject(packageName);
+ scanSpec.pathAcceptReject.addToReject(pathNormalized + "/");
if (!pathNormalized.contains("*")) {
- // Blacklist sub-directories / nested paths
- scanSpec.packagePrefixWhiteBlackList.addToBlacklist(packageName + ".");
- scanSpec.pathPrefixWhiteBlackList.addToBlacklist(pathNormalized + "/");
+ // Reject sub-directories / nested paths
+ scanSpec.packagePrefixAcceptReject.addToReject(packageName + ".");
+ scanSpec.pathPrefixAcceptReject.addToReject(pathNormalized + "/");
}
}
return this;
}
+ /**
+ * Use {@link #rejectPaths(String...)} instead.
+ *
+ * @param paths
+ * The paths to reject (with '/' as a separator). May include a glob wildcard ({@code '*'}).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #rejectPaths(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph blacklistPaths(final String... paths) {
+ return rejectPaths(paths);
+ }
+
/**
* Scan one or more specific classes, without scanning other classes in the same package unless the package is
- * itself whitelisted.
+ * itself accepted.
*
*
* N.B. Automatically calls {@link #enableClassInfo()}.
*
*
* @param classNames
- * The fully-qualified names of classes to scan (using '.' as a separator). May not include a glob
- * wildcard ({@code '*'}).
+ * The fully-qualified names of classes to scan (using '.' as a separator). To match a class name by
+ * glob in any package, you must include a package glob too, e.g. {@code "*.*Suffix"}.
* @return this (for method chaining).
*/
- public ClassGraph whitelistClasses(final String... classNames) {
+ public ClassGraph acceptClasses(final String... classNames) {
enableClassInfo();
for (final String className : classNames) {
- if (className.contains("*")) {
- throw new IllegalArgumentException("Cannot use a glob wildcard here: " + className);
- }
- final String classNameNormalized = WhiteBlackList.normalizePackageOrClassName(className);
- // Whitelist the class itself
- scanSpec.classWhiteBlackList.addToWhitelist(classNameNormalized);
- scanSpec.classfilePathWhiteBlackList
- .addToWhitelist(WhiteBlackList.classNameToClassfilePath(classNameNormalized));
+ final String classNameNormalized = AcceptReject.normalizePackageOrClassName(className);
+ // Accept the class itself
+ scanSpec.classAcceptReject.addToAccept(classNameNormalized);
+ scanSpec.classfilePathAcceptReject
+ .addToAccept(AcceptReject.classNameToClassfilePath(classNameNormalized));
final String packageName = PackageInfo.getParentPackageName(classNameNormalized);
// Record the package containing the class, so we can recurse to this point even if the package
- // is not itself whitelisted
- scanSpec.classPackageWhiteBlackList.addToWhitelist(packageName);
- scanSpec.classPackagePathWhiteBlackList
- .addToWhitelist(WhiteBlackList.packageNameToPath(packageName) + "/");
+ // is not itself accepted
+ scanSpec.classPackageAcceptReject.addToAccept(packageName);
+ scanSpec.classPackagePathAcceptReject.addToAccept(AcceptReject.packageNameToPath(packageName) + "/");
}
return this;
}
/**
- * Specifically blacklist one or more specific classes, preventing them from being scanned even if they are in a
- * whitelisted package.
+ * Use {@link #acceptClasses(String...)} instead.
+ *
+ * @param classNames
+ * The fully-qualified names of classes to scan (using '.' as a separator).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #acceptClasses(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph whitelistClasses(final String... classNames) {
+ return acceptClasses(classNames);
+ }
+
+ /**
+ * Specifically reject one or more specific classes, preventing them from being scanned even if they are in a
+ * accepted package.
*
*
* N.B. Automatically calls {@link #enableClassInfo()}.
*
* @param classNames
- * The fully-qualified names of classes to blacklist (using '.' as a separator). May not include a
- * glob wildcard ({@code '*'}).
+ * The fully-qualified names of classes to reject (using '.' as a separator). To match a class name
+ * by glob in any package, you must include a package glob too, e.g. {@code "*.*Suffix"}.
* @return this (for method chaining).
*/
- public ClassGraph blacklistClasses(final String... classNames) {
+ public ClassGraph rejectClasses(final String... classNames) {
enableClassInfo();
for (final String className : classNames) {
- if (className.contains("*")) {
- throw new IllegalArgumentException("Cannot use a glob wildcard here: " + className);
- }
- final String classNameNormalized = WhiteBlackList.normalizePackageOrClassName(className);
- scanSpec.classWhiteBlackList.addToBlacklist(classNameNormalized);
- scanSpec.classfilePathWhiteBlackList
- .addToBlacklist(WhiteBlackList.classNameToClassfilePath(classNameNormalized));
+ final String classNameNormalized = AcceptReject.normalizePackageOrClassName(className);
+ scanSpec.classAcceptReject.addToReject(classNameNormalized);
+ scanSpec.classfilePathAcceptReject
+ .addToReject(AcceptReject.classNameToClassfilePath(classNameNormalized));
}
return this;
}
/**
- * Whitelist one or more jars. This will cause only the whitelisted jars to be scanned.
+ * Use {@link #rejectClasses(String...)} instead.
+ *
+ * @param classNames
+ * The fully-qualified names of classes to reject (using '.' as a separator).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #rejectClasses(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph blacklistClasses(final String... classNames) {
+ return rejectClasses(classNames);
+ }
+
+ /**
+ * Accept one or more jars. This will cause only the accepted jars to be scanned.
*
* @param jarLeafNames
* The leafnames of the jars that should be scanned (e.g. {@code "mylib.jar"}). May contain a
* wildcard glob ({@code "mylib-*.jar"}).
* @return this (for method chaining).
*/
- public ClassGraph whitelistJars(final String... jarLeafNames) {
+ public ClassGraph acceptJars(final String... jarLeafNames) {
for (final String jarLeafName : jarLeafNames) {
final String leafName = JarUtils.leafName(jarLeafName);
if (!leafName.equals(jarLeafName)) {
- throw new IllegalArgumentException("Can only whitelist jars by leafname: " + jarLeafName);
+ throw new IllegalArgumentException("Can only accept jars by leafname: " + jarLeafName);
}
- scanSpec.jarWhiteBlackList.addToWhitelist(leafName);
+ scanSpec.jarAcceptReject.addToAccept(leafName);
}
return this;
}
/**
- * Blacklist one or more jars, preventing them from being scanned.
+ * Use {@link #acceptJars(String...)} instead.
+ *
+ * @param jarLeafNames
+ * The leafnames of the jars that should be scanned (e.g. {@code "mylib.jar"}). May contain a
+ * wildcard glob ({@code "mylib-*.jar"}).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #acceptJars(String...)} instead.
+ */
+ @Deprecated
+ public ClassGraph whitelistJars(final String... jarLeafNames) {
+ return acceptJars(jarLeafNames);
+ }
+
+ /**
+ * Reject one or more jars, preventing them from being scanned.
*
* @param jarLeafNames
* The leafnames of the jars that should be scanned (e.g. {@code "badlib.jar"}). May contain a
* wildcard glob ({@code "badlib-*.jar"}).
* @return this (for method chaining).
*/
- public ClassGraph blacklistJars(final String... jarLeafNames) {
+ public ClassGraph rejectJars(final String... jarLeafNames) {
for (final String jarLeafName : jarLeafNames) {
final String leafName = JarUtils.leafName(jarLeafName);
if (!leafName.equals(jarLeafName)) {
- throw new IllegalArgumentException("Can only blacklist jars by leafname: " + jarLeafName);
+ throw new IllegalArgumentException("Can only reject jars by leafname: " + jarLeafName);
}
- scanSpec.jarWhiteBlackList.addToBlacklist(leafName);
+ scanSpec.jarAcceptReject.addToReject(leafName);
}
return this;
}
/**
- * Add lib or ext jars to whitelist or blacklist.
+ * Use {@link #rejectJars(String...)} instead.
*
- * @param whitelist
- * if true, add to whitelist, otherwise add to blacklist.
* @param jarLeafNames
- * the jar leaf names to whitelist
+ * The leafnames of the jars that should be scanned (e.g. {@code "badlib.jar"}). May contain a
+ * wildcard glob ({@code "badlib-*.jar"}).
+ * @return this (for method chaining).
+ * @deprecated Use {@link #rejectJars(String...)} instead.
*/
- private void whitelistOrBlacklistLibOrExtJars(final boolean whitelist, final String... jarLeafNames) {
+ @Deprecated
+ public ClassGraph blacklistJars(final String... jarLeafNames) {
+ return rejectJars(jarLeafNames);
+ }
+
+ /**
+ * Add lib or ext jars to accept or reject.
+ *
+ * @param accept
+ * if true, add to accept, otherwise add to reject.
+ * @param jarLeafNames
+ * the jar leaf names to accept
+ */
+ private void acceptOrRejectLibOrExtJars(final boolean accept, final String... jarLeafNames) {
if (jarLeafNames.length == 0) {
- // If no jar leafnames are given, whitelist or blacklist all lib or ext jars
+ // If no jar leafnames are given, accept or reject all lib or ext jars
for (final String libOrExtJar : SystemJarFinder.getJreLibOrExtJars()) {
- whitelistOrBlacklistLibOrExtJars(whitelist, JarUtils.leafName(libOrExtJar));
+ acceptOrRejectLibOrExtJars(accept, JarUtils.leafName(libOrExtJar));
}
} else {
for (final String jarLeafName : jarLeafNames) {
final String leafName = JarUtils.leafName(jarLeafName);
if (!leafName.equals(jarLeafName)) {
- throw new IllegalArgumentException("Can only " + (whitelist ? "whitelist" : "blacklist")
- + " jars by leafname: " + jarLeafName);
+ throw new IllegalArgumentException(
+ "Can only " + (accept ? "accept" : "reject") + " jars by leafname: " + jarLeafName);
}
if (jarLeafName.contains("*")) {
// Compare wildcarded pattern against all jars in lib and ext dirs
- final Pattern pattern = WhiteBlackList.globToPattern(jarLeafName);
+ final Pattern pattern = AcceptReject.globToPattern(jarLeafName, /* simpleGlob = */ true);
boolean found = false;
for (final String libOrExtJarPath : SystemJarFinder.getJreLibOrExtJars()) {
final String libOrExtJarLeafName = JarUtils.leafName(libOrExtJarPath);
if (pattern.matcher(libOrExtJarLeafName).matches()) {
// Check for "*" in filename to prevent infinite recursion (shouldn't happen)
if (!libOrExtJarLeafName.contains("*")) {
- whitelistOrBlacklistLibOrExtJars(whitelist, libOrExtJarLeafName);
+ acceptOrRejectLibOrExtJars(accept, libOrExtJarLeafName);
}
found = true;
}
@@ -879,18 +1127,18 @@ private void whitelistOrBlacklistLibOrExtJars(final boolean whitelist, final Str
topLevelLog.log("Could not find lib or ext jar matching wildcard: " + jarLeafName);
}
} else {
- // No wildcards, just whitelist or blacklist the named jar, if present
+ // No wildcards, just accept or reject the named jar, if present
boolean found = false;
for (final String libOrExtJarPath : SystemJarFinder.getJreLibOrExtJars()) {
final String libOrExtJarLeafName = JarUtils.leafName(libOrExtJarPath);
if (jarLeafName.equals(libOrExtJarLeafName)) {
- if (whitelist) {
- scanSpec.libOrExtJarWhiteBlackList.addToWhitelist(jarLeafName);
+ if (accept) {
+ scanSpec.libOrExtJarAcceptReject.addToAccept(jarLeafName);
} else {
- scanSpec.libOrExtJarWhiteBlackList.addToBlacklist(jarLeafName);
+ scanSpec.libOrExtJarAcceptReject.addToReject(jarLeafName);
}
if (topLevelLog != null) {
- topLevelLog.log((whitelist ? "Whitelisting" : "Blacklisting") + " lib or ext jar: "
+ topLevelLog.log((accept ? "Accepting" : "Rejecting") + " lib or ext jar: "
+ libOrExtJarPath);
}
found = true;
@@ -906,105 +1154,210 @@ private void whitelistOrBlacklistLibOrExtJars(final boolean whitelist, final Str
}
/**
- * Whitelist one or more jars in a JRE/JDK "lib/" or "ext/" directory (these directories are not scanned unless
+ * Accept one or more jars in a JRE/JDK "lib/" or "ext/" directory (these directories are not scanned unless
* {@link #enableSystemJarsAndModules()} is called, by association with the JRE/JDK).
*
* @param jarLeafNames
* The leafnames of the lib/ext jar(s) that should be scanned (e.g. {@code "mylib.jar"}). May contain
* a wildcard glob ({@code '*'}). Note that if you call this method with no parameters, all JRE/JDK
- * "lib/" or "ext/" jars will be whitelisted.
+ * "lib/" or "ext/" jars will be accepted.
+ * @return this (for method chaining).
+ */
+ public ClassGraph acceptLibOrExtJars(final String... jarLeafNames) {
+ acceptOrRejectLibOrExtJars(/* accept = */ true, jarLeafNames);
+ return this;
+ }
+
+ /**
+ * Use {@link #acceptLibOrExtJars(String...)} instead.
+ *
+ * @param jarLeafNames
+ * The leafnames of the lib/ext jar(s) that should be scanned (e.g. {@code "mylib.jar"}). May contain
+ * a wildcard glob ({@code '*'}). Note that if you call this method with no parameters, all JRE/JDK
+ * "lib/" or "ext/" jars will be accepted.
* @return this (for method chaining).
+ * @deprecated Use {@link #acceptLibOrExtJars(String...)} instead.
*/
+ @Deprecated
public ClassGraph whitelistLibOrExtJars(final String... jarLeafNames) {
- whitelistOrBlacklistLibOrExtJars(/* whitelist = */ true, jarLeafNames);
+ return acceptLibOrExtJars(jarLeafNames);
+ }
+
+ /**
+ * Reject one or more jars in a JRE/JDK "lib/" or "ext/" directory, preventing them from being scanned.
+ *
+ * @param jarLeafNames
+ * The leafnames of the lib/ext jar(s) that should not be scanned (e.g.
+ * {@code "jre/lib/badlib.jar"}). May contain a wildcard glob ({@code '*'}). If you call this method
+ * with no parameters, all JRE/JDK {@code "lib/"} or {@code "ext/"} jars will be rejected.
+ * @return this (for method chaining).
+ */
+ public ClassGraph rejectLibOrExtJars(final String... jarLeafNames) {
+ acceptOrRejectLibOrExtJars(/* accept = */ false, jarLeafNames);
return this;
}
/**
- * Blacklist one or more jars in a JRE/JDK "lib/" or "ext/" directory, preventing them from being scanned.
+ * Use {@link #rejectLibOrExtJars(String...)} instead.
*
* @param jarLeafNames
* The leafnames of the lib/ext jar(s) that should not be scanned (e.g.
* {@code "jre/lib/badlib.jar"}). May contain a wildcard glob ({@code '*'}). If you call this method
- * with no parameters, all JRE/JDK {@code "lib/"} or {@code "ext/"} jars will be blacklisted.
+ * with no parameters, all JRE/JDK {@code "lib/"} or {@code "ext/"} jars will be rejected.
* @return this (for method chaining).
+ * @deprecated Use {@link #rejectLibOrExtJars(String...)} instead.
*/
+ @Deprecated
public ClassGraph blacklistLibOrExtJars(final String... jarLeafNames) {
- whitelistOrBlacklistLibOrExtJars(/* whitelist = */ false, jarLeafNames);
+ return rejectLibOrExtJars(jarLeafNames);
+ }
+
+ /**
+ * Accept one or more modules for scanning.
+ *
+ * @param moduleNames
+ * The names of the modules that should be scanned. May contain a wildcard glob ({@code '*'}).
+ * @return this (for method chaining).
+ */
+ public ClassGraph acceptModules(final String... moduleNames) {
+ for (final String moduleName : moduleNames) {
+ scanSpec.moduleAcceptReject.addToAccept(AcceptReject.normalizePackageOrClassName(moduleName));
+ }
return this;
}
/**
- * Whitelist one or more modules to scan.
+ * Use {@link #acceptModules(String...)} instead.
*
* @param moduleNames
* The names of the modules that should be scanned. May contain a wildcard glob ({@code '*'}).
* @return this (for method chaining).
+ * @deprecated Use {@link #acceptModules(String...)} instead.
*/
+ @Deprecated
public ClassGraph whitelistModules(final String... moduleNames) {
+ return acceptModules(moduleNames);
+ }
+
+ /**
+ * Reject one or more modules, preventing them from being scanned.
+ *
+ * @param moduleNames
+ * The names of the modules that should not be scanned. May contain a wildcard glob ({@code '*'}).
+ * @return this (for method chaining).
+ */
+ public ClassGraph rejectModules(final String... moduleNames) {
for (final String moduleName : moduleNames) {
- scanSpec.moduleWhiteBlackList.addToWhitelist(WhiteBlackList.normalizePackageOrClassName(moduleName));
+ scanSpec.moduleAcceptReject.addToReject(AcceptReject.normalizePackageOrClassName(moduleName));
}
return this;
}
/**
- * Blacklist one or more modules, preventing them from being scanned.
+ * Use {@link #rejectModules(String...)} instead.
*
* @param moduleNames
* The names of the modules that should not be scanned. May contain a wildcard glob ({@code '*'}).
* @return this (for method chaining).
+ * @deprecated Use {@link #rejectModules(String...)} instead.
*/
+ @Deprecated
public ClassGraph blacklistModules(final String... moduleNames) {
- for (final String moduleName : moduleNames) {
- scanSpec.moduleWhiteBlackList.addToBlacklist(WhiteBlackList.normalizePackageOrClassName(moduleName));
+ return rejectModules(moduleNames);
+ }
+
+ /**
+ * Accept classpath elements based on resource paths. Only classpath elements that contain resources with paths
+ * matching the accept will be scanned.
+ *
+ * @param resourcePaths
+ * The resource paths, any of which must be present in a classpath element for the classpath element
+ * to be scanned. May contain a wildcard glob ({@code '*'}).
+ * @return this (for method chaining).
+ */
+ public ClassGraph acceptClasspathElementsContainingResourcePath(final String... resourcePaths) {
+ for (final String resourcePath : resourcePaths) {
+ final String resourcePathNormalized = AcceptReject.normalizePath(resourcePath);
+ scanSpec.classpathElementResourcePathAcceptReject.addToAccept(resourcePathNormalized);
}
return this;
}
/**
- * Whitelist classpath elements based on resource paths. Only classpath elements that contain resources with
- * paths matching the whitelist will be scanned.
+ * Use {@link #acceptClasspathElementsContainingResourcePath(String...)} instead.
*
* @param resourcePaths
* The resource paths, any of which must be present in a classpath element for the classpath element
* to be scanned. May contain a wildcard glob ({@code '*'}).
* @return this (for method chaining).
+ * @deprecated Use {@link #acceptClasspathElementsContainingResourcePath(String...)} instead.
*/
+ @Deprecated
public ClassGraph whitelistClasspathElementsContainingResourcePath(final String... resourcePaths) {
+ return acceptClasspathElementsContainingResourcePath(resourcePaths);
+ }
+
+ /**
+ * Reject classpath elements based on resource paths. Classpath elements that contain resources with paths
+ * matching the reject will not be scanned.
+ *
+ * @param resourcePaths
+ * The resource paths which cause a classpath not to be scanned if any are present in a classpath
+ * element for the classpath element. May contain a wildcard glob ({@code '*'}).
+ * @return this (for method chaining).
+ */
+ public ClassGraph rejectClasspathElementsContainingResourcePath(final String... resourcePaths) {
for (final String resourcePath : resourcePaths) {
- final String resourcePathNormalized = WhiteBlackList.normalizePath(resourcePath);
- scanSpec.classpathElementResourcePathWhiteBlackList.addToWhitelist(resourcePathNormalized);
+ final String resourcePathNormalized = AcceptReject.normalizePath(resourcePath);
+ scanSpec.classpathElementResourcePathAcceptReject.addToReject(resourcePathNormalized);
}
return this;
}
/**
- * Blacklist classpath elements based on resource paths. Classpath elements that contain resources with paths
- * matching the blacklist will not be scanned.
+ * Use {@link #rejectClasspathElementsContainingResourcePath(String...)} instead.
*
* @param resourcePaths
* The resource paths which cause a classpath not to be scanned if any are present in a classpath
* element for the classpath element. May contain a wildcard glob ({@code '*'}).
* @return this (for method chaining).
+ * @deprecated Use {@link #rejectClasspathElementsContainingResourcePath(String...)} instead.
*/
+ @Deprecated
public ClassGraph blacklistClasspathElementsContainingResourcePath(final String... resourcePaths) {
- for (final String resourcePath : resourcePaths) {
- final String resourcePathNormalized = WhiteBlackList.normalizePath(resourcePath);
- scanSpec.classpathElementResourcePathWhiteBlackList.addToBlacklist(resourcePathNormalized);
- }
- return this;
+ return rejectClasspathElementsContainingResourcePath(resourcePaths);
}
/**
- * Enable classpath elements to be fetched from remote http/https URLs to local temporary files and scanned.
- * This option is disabled by default, as this may present a security vulnerability, since classes from
- * downloaded jars can be subsequently loaded using {@link ClassInfo#loadClass}.
+ * Enable classpath elements to be fetched from remote ("http:"/"https:") URLs (or URLs with custom schemes).
+ * Equivalent to:
+ *
+ *
+ * {@code new ClassGraph().enableURLScheme("http").enableURLScheme("https");}
+ *
+ *
+ * Scanning from http(s) URLs is disabled by default, as this may present a security vulnerability, since
+ * classes from downloaded jars can be subsequently loaded using {@link ClassInfo#loadClass}.
*
* @return this (for method chaining).
*/
public ClassGraph enableRemoteJarScanning() {
- scanSpec.enableRemoteJarScanning = true;
+ scanSpec.enableURLScheme("http");
+ scanSpec.enableURLScheme("https");
+ return this;
+ }
+
+ /**
+ * Enable classpath elements to be fetched from {@link URL} connections with the specified URL scheme (also
+ * works for any custom URL schemes that have been defined, as long as they have more than two characters, in
+ * order to not conflict with Windows drive letters).
+ *
+ * @param scheme
+ * the URL scheme string, e.g. "resource" for a custom "resource:" URL scheme.
+ * @return this (for method chaining).
+ */
+ public ClassGraph enableURLScheme(final String scheme) {
+ scanSpec.enableURLScheme(scheme);
return this;
}
@@ -1023,6 +1376,82 @@ public ClassGraph enableSystemJarsAndModules() {
return this;
}
+ // -------------------------------------------------------------------------------------------------------------
+
+ /**
+ * The maximum size of an inner (nested) jar that has been deflated (i.e. compressed, not stored) within an
+ * outer jar, before it has to be spilled to disk rather than stored in a RAM-backed {@link ByteBuffer} when it
+ * is deflated, in order for the inner jar's entries to be read. (Note that this situation of having to deflate
+ * a nested jar to RAM or disk in order to read it is rare, because normally adding a jarfile to another jarfile
+ * will store the inner jar, rather than deflate it, because deflating a jarfile does not usually produce any
+ * further compression gains. If an inner jar is stored, not deflated, then its zip entries can be read directly
+ * using ClassGraph's own zipfile central directory parser, which can use file slicing to extract entries
+ * directly from stored nested jars.)
+ *
+ *
+ * This is also the maximum size of a jar downloaded from an {@code http://} or {@code https://} classpath
+ * {@link URL} to RAM. Once this many bytes have been read from the {@link URL}'s {@link InputStream}, then the
+ * RAM contents are spilled over to a temporary file on disk, and the rest of the content is downloaded to the
+ * temporary file. (This is also rare, because normally there are no {@code http://} or {@code https://}
+ * classpath entries.)
+ *
+ *
+ * Default: 64MB (i.e. writing to disk is avoided wherever possible). Setting a lower max RAM size value will
+ * decrease ClassGraph's memory usage if either of the above rare situations occurs.
+ *
+ * @param maxBufferedJarRAMSize
+ * The max RAM size to use for deflated inner jars or downloaded jars. This is the limit per jar, not
+ * for the whole classpath.
+ * @return this (for method chaining).
+ */
+ public ClassGraph setMaxBufferedJarRAMSize(final int maxBufferedJarRAMSize) {
+ scanSpec.maxBufferedJarRAMSize = maxBufferedJarRAMSize;
+ return this;
+ }
+
+ /**
+ * If true, use a {@link MappedByteBuffer} rather than the {@link FileChannel} API to open files, which may be
+ * faster for large classpaths consisting of many large jarfiles, but uses up virtual memory space.
+ * Not available on Java 24+ currently, because of the deprecation of the Unsafe API.
+ *
+ * @return this (for method chaining).
+ */
+ public ClassGraph enableMemoryMapping() {
+ if (VersionFinder.JAVA_MAJOR_VERSION > 23) {
+ // See FileUtils.java
+ throw new IllegalArgumentException("enableMemoryMapping() is not supported on Java 24+");
+ }
+ scanSpec.enableMemoryMapping = true;
+ return this;
+ }
+
+ /**
+ * If true, provide all versions of a multi-release resource using their multi-release path prefix, instead of
+ * just the one the running JVM would select. Implicitly disables {@link #enableClassInfo()} and all features
+ * depending on it.
+ *
+ * @return this (for method chaining).
+ */
+ public ClassGraph enableMultiReleaseVersions() {
+ scanSpec.enableMultiReleaseVersions = true;
+
+ scanSpec.enableClassInfo = false;
+ scanSpec.ignoreClassVisibility = false;
+ scanSpec.enableMethodInfo = false;
+ scanSpec.ignoreMethodVisibility = false;
+ scanSpec.enableFieldInfo = false;
+ scanSpec.ignoreFieldVisibility = false;
+ scanSpec.enableStaticFinalFieldConstantInitializerValues = false;
+ scanSpec.enableAnnotationInfo = false;
+ scanSpec.enableInterClassDependencies = false;
+ scanSpec.disableRuntimeInvisibleAnnotations = false;
+ scanSpec.enableExternalClasses = false;
+ scanSpec.enableSystemJarsAndModules = false;
+ return this;
+ }
+
+ // -------------------------------------------------------------------------------------------------------------
+
/**
* Enables logging by calling {@link #verbose()}, and then sets the logger to "realtime logging mode", where log
* entries are written out immediately to stderr, rather than only after the scan has completed. Can help to
@@ -1095,11 +1524,11 @@ public void scanAsync(final ExecutorService executorService, final int numParall
@Override
public void run() {
try {
- new Scanner(scanSpec, executorService, numParallelTasks, scanResultProcessor, failureHandler,
- topLevelLog);
- } catch (final InterruptedException e) {
- // Interrupted during the Scanner constructor's execution (specifically, by getModuleOrder(),
- // which is unlikely to ever actually be interrupted -- but this exception needs to be caught)
+ // Call scanner, but ignore the returned ScanResult
+ new Scanner(/* performScan = */ true, scanSpec, executorService, numParallelTasks,
+ scanResultProcessor, failureHandler, reflectionUtils, topLevelLog).call();
+ } catch (final InterruptedException | CancellationException | ExecutionException e) {
+ // Call failure handler
failureHandler.onFailure(e);
}
}
@@ -1107,8 +1536,12 @@ public void run() {
}
/**
- * Asynchronously scans the classpath for matching files, returning a {@code Future}.
- *
+ * Asynchronously scans the classpath for matching files, returning a {@code Future}. You should
+ * assign the wrapped {@link ScanResult} in a try-with-resources statement, or manually close it when you are
+ * finished with it.
+ *
+ * @param performScan
+ * If true, performing a scan. If false, only fetching the classpath.
* @param executorService
* A custom {@link ExecutorService} to use for scheduling worker tasks.
* @param numParallelTasks
@@ -1117,10 +1550,11 @@ public void run() {
* @return a {@code Future}, that when resolved using get() yields a new {@link ScanResult} object
* representing the result of the scan.
*/
- public Future scanAsync(final ExecutorService executorService, final int numParallelTasks) {
+ private Future scanAsync(final boolean performScan, final ExecutorService executorService,
+ final int numParallelTasks) {
try {
- return executorService.submit(new Scanner(scanSpec, executorService, numParallelTasks,
- /* scanResultProcessor = */ null, /* failureHandler = */ null, topLevelLog));
+ return executorService.submit(new Scanner(performScan, scanSpec, executorService, numParallelTasks,
+ /* scanResultProcessor = */ null, /* failureHandler = */ null, reflectionUtils, topLevelLog));
} catch (final InterruptedException e) {
// Interrupted during the Scanner constructor's execution (specifically, by getModuleOrder(),
// which is unlikely to ever actually be interrupted -- but this exception needs to be caught).
@@ -1133,9 +1567,27 @@ public ScanResult call() throws Exception {
}
}
+ /**
+ * Asynchronously scans the classpath for matching files, returning a {@code Future}. You should
+ * assign the wrapped {@link ScanResult} in a try-with-resources statement, or manually close it when you are
+ * finished with it.
+ *
+ * @param executorService
+ * A custom {@link ExecutorService} to use for scheduling worker tasks.
+ * @param numParallelTasks
+ * The number of parallel tasks to break the work into during the most CPU-intensive stage of
+ * classpath scanning. Ideally the ExecutorService will have at least this many threads available.
+ * @return a {@code Future}, that when resolved using get() yields a new {@link ScanResult} object
+ * representing the result of the scan.
+ */
+ public Future scanAsync(final ExecutorService executorService, final int numParallelTasks) {
+ return scanAsync(/* performScan = */ true, executorService, numParallelTasks);
+ }
+
/**
* Scans the classpath using the requested {@link ExecutorService} and the requested degree of parallelism,
- * blocking until the scan is complete.
+ * blocking until the scan is complete. You should assign the returned {@link ScanResult} in a
+ * try-with-resources statement, or manually close it when you are finished with it.
*
* @param executorService
* A custom {@link ExecutorService} to use for scheduling worker tasks. This {@link ExecutorService}
@@ -1174,15 +1626,16 @@ public ScanResult scan(final ExecutorService executorService, final int numParal
return scanResult;
} catch (final InterruptedException | CancellationException e) {
- throw ClassGraphException.newClassGraphException("Scan interrupted", e);
+ throw new ClassGraphException("Scan interrupted", e);
} catch (final ExecutionException e) {
- throw ClassGraphException.newClassGraphException("Uncaught exception during scan",
- InterruptionChecker.getCause(e));
+ throw new ClassGraphException("Uncaught exception during scan", InterruptionChecker.getCause(e));
}
}
/**
- * Scans the classpath with the requested number of threads, blocking until the scan is complete.
+ * Scans the classpath with the requested number of threads, blocking until the scan is complete. You should
+ * assign the returned {@link ScanResult} in a try-with-resources statement, or manually close it when you are
+ * finished with it.
*
* @param numThreads
* The number of worker threads to start up.
@@ -1197,7 +1650,8 @@ public ScanResult scan(final int numThreads) {
}
/**
- * Scans the classpath, blocking until the scan is complete.
+ * Scans the classpath, blocking until the scan is complete. You should assign the returned {@link ScanResult}
+ * in a try-with-resources statement, or manually close it when you are finished with it.
*
* @return a {@link ScanResult} object representing the result of the scan.
* @throws ClassGraphException
@@ -1209,6 +1663,34 @@ public ScanResult scan() {
// -------------------------------------------------------------------------------------------------------------
+ /**
+ * Get a {@link ScanResult} that can be used for determining the classpath.
+ *
+ * @param executorService
+ * The executor service.
+ * @return a {@link ScanResult} object representing the result of the scan (can only be used for determining
+ * classpath).
+ * @throws ClassGraphException
+ * if any of the worker threads throws an uncaught exception, or the scan was interrupted.
+ */
+ ScanResult getClasspathScanResult(final AutoCloseableExecutorService executorService) {
+ try {
+ final ScanResult scanResult = scanAsync(/* performScan = */ false, executorService,
+ DEFAULT_NUM_WORKER_THREADS).get();
+
+ // The resulting scanResult cannot be null, but check for null to keep SpotBugs happy
+ if (scanResult == null) {
+ throw new NullPointerException();
+ }
+ return scanResult;
+
+ } catch (final InterruptedException | CancellationException e) {
+ throw new ClassGraphException("Scan interrupted", e);
+ } catch (final ExecutionException e) {
+ throw new ClassGraphException("Uncaught exception during scan", InterruptionChecker.getCause(e));
+ }
+ }
+
/**
* Returns the list of all unique File objects representing directories or zip/jarfiles on the classpath, in
* classloader resolution order. Classpath elements that do not exist as a file or directory are not included in
@@ -1220,8 +1702,8 @@ public ScanResult scan() {
* if any of the worker threads throws an uncaught exception, or the scan was interrupted.
*/
public List getClasspathFiles() {
- scanSpec.performScan = false;
- try (ScanResult scanResult = scan()) {
+ try (AutoCloseableExecutorService executorService = new AutoCloseableExecutorService(
+ DEFAULT_NUM_WORKER_THREADS); ScanResult scanResult = getClasspathScanResult(executorService)) {
return scanResult.getClasspathFiles();
}
}
@@ -1253,8 +1735,8 @@ public String getClasspath() {
* if any of the worker threads throws an uncaught exception, or the scan was interrupted.
*/
public List getClasspathURIs() {
- scanSpec.performScan = false;
- try (ScanResult scanResult = scan()) {
+ try (AutoCloseableExecutorService executorService = new AutoCloseableExecutorService(
+ DEFAULT_NUM_WORKER_THREADS); ScanResult scanResult = getClasspathScanResult(executorService)) {
return scanResult.getClasspathURIs();
}
}
@@ -1269,8 +1751,8 @@ public List getClasspathURIs() {
* if any of the worker threads throws an uncaught exception, or the scan was interrupted.
*/
public List getClasspathURLs() {
- scanSpec.performScan = false;
- try (ScanResult scanResult = scan()) {
+ try (AutoCloseableExecutorService executorService = new AutoCloseableExecutorService(
+ DEFAULT_NUM_WORKER_THREADS); ScanResult scanResult = getClasspathScanResult(executorService)) {
return scanResult.getClasspathURLs();
}
}
@@ -1283,8 +1765,8 @@ public List getClasspathURLs() {
* if any of the worker threads throws an uncaught exception, or the scan was interrupted.
*/
public List getModules() {
- scanSpec.performScan = false;
- try (ScanResult scanResult = scan()) {
+ try (AutoCloseableExecutorService executorService = new AutoCloseableExecutorService(
+ DEFAULT_NUM_WORKER_THREADS); ScanResult scanResult = getClasspathScanResult(executorService)) {
return scanResult.getModules();
}
}
@@ -1307,6 +1789,7 @@ public List getModules() {
* @return The {@link ModulePathInfo}.
*/
public ModulePathInfo getModulePathInfo() {
+ scanSpec.modulePathInfo.getRuntimeInfo(reflectionUtils);
return scanSpec.modulePathInfo;
}
}
diff --git a/src/main/java/io/github/classgraph/ClassGraphClassLoader.java b/src/main/java/io/github/classgraph/ClassGraphClassLoader.java
index 8b3fd533a..7067119e5 100644
--- a/src/main/java/io/github/classgraph/ClassGraphClassLoader.java
+++ b/src/main/java/io/github/classgraph/ClassGraphClassLoader.java
@@ -31,16 +31,41 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import nonapi.io.github.classgraph.scanspec.ScanSpec;
import nonapi.io.github.classgraph.utils.JarUtils;
+import nonapi.io.github.classgraph.utils.VersionFinder;
+import nonapi.io.github.classgraph.utils.VersionFinder.OperatingSystem;
/** {@link ClassLoader} for classes found by ClassGraph during scanning. */
-class ClassGraphClassLoader extends ClassLoader {
+public class ClassGraphClassLoader extends ClassLoader {
/** The scan result. */
private final ScanResult scanResult;
+ /** Whether or not to initialize loaded classes. */
+ private final boolean initializeLoadedClasses;
+
+ /** The ordered set of environment classloaders to try delegating to. */
+ private Set environmentClassLoaderDelegationOrder;
+
+ /** Any override classloader(s). */
+ private List overrideClassLoaders;
+
+ /** A {@link URLClassLoader} consisting of URLs on the classpath. */
+ private final ClassLoader classpathClassLoader;
+
+ /** The ordered set of overridden or added classloaders to try delegating to. */
+ private Set addedClassLoaderDelegationOrder;
+
/**
* Constructor.
*
@@ -49,8 +74,60 @@ class ClassGraphClassLoader extends ClassLoader {
*/
ClassGraphClassLoader(final ScanResult scanResult) {
super(null);
- this.scanResult = scanResult;
registerAsParallelCapable();
+
+ this.scanResult = scanResult;
+ final ScanSpec scanSpec = scanResult.scanSpec;
+ initializeLoadedClasses = scanSpec.initializeLoadedClasses;
+
+ final boolean classpathOverridden = scanSpec.overrideClasspath != null
+ && !scanSpec.overrideClasspath.isEmpty();
+ final boolean classloadersOverridden = scanSpec.overrideClassLoaders != null
+ && !scanSpec.overrideClassLoaders.isEmpty();
+ final boolean clasloadersAdded = scanSpec.addedClassLoaders != null
+ && !scanSpec.addedClassLoaders.isEmpty();
+
+ // Only try environment classloaders if classpath and/or classloaders are not overridden
+ if (!classpathOverridden && !classloadersOverridden) {
+ // Try the null classloader first (this will default to the bootstrap class loader)
+ environmentClassLoaderDelegationOrder = new LinkedHashSet<>();
+ environmentClassLoaderDelegationOrder.add(null);
+
+ // Try environment classloaders
+ final ClassLoader[] envClassLoaderOrder = scanResult.getClassLoaderOrderRespectingParentDelegation();
+ if (envClassLoaderOrder != null) {
+ // Try environment classloaders
+ environmentClassLoaderDelegationOrder.addAll(Arrays.asList(envClassLoaderOrder));
+ }
+ }
+
+ // Create classloader from URLs on classpath
+ final List classpathURLs = scanResult.getClasspathURLs();
+ classpathClassLoader = classpathURLs.isEmpty() ? null
+ : new URLClassLoader(classpathURLs.toArray(new URL[0]));
+
+ // If the classloaders were overridden, just use the override classloaders, and then fail if the
+ // class couldn't be found.
+ overrideClassLoaders = classloadersOverridden ? scanSpec.overrideClassLoaders : null;
+
+ // If the classpath is overridden, and classloaders are not overridden, try loading class from
+ // classpath URLs, as the override classloader, then fail if the class couldn't be found.
+ //
+ // N.B. Some classpath URLs might be invalid if the ScanResult has been closed (e.g. in the rare
+ // case that an inner jar had to be extracted to a temporary file on disk).
+ if (overrideClassLoaders == null && classpathOverridden && classpathClassLoader != null) {
+ overrideClassLoaders = Collections.singletonList(classpathClassLoader);
+ }
+
+ // If classloaders were added, try loading through those classloaders
+ if (clasloadersAdded) {
+ addedClassLoaderDelegationOrder = new LinkedHashSet<>();
+ addedClassLoaderDelegationOrder.addAll(scanSpec.addedClassLoaders);
+ // Remove duplicates
+ if (environmentClassLoaderDelegationOrder != null) {
+ addedClassLoaderDelegationOrder.removeAll(environmentClassLoaderDelegationOrder);
+ }
+ }
}
/* (non-Javadoc)
@@ -59,67 +136,181 @@ class ClassGraphClassLoader extends ClassLoader {
@Override
protected Class> findClass(final String className)
throws ClassNotFoundException, LinkageError, SecurityException {
+ // First delegate to outer nested ClassGraphClassLoader, if any (#485)
+ final ClassGraphClassLoader delegateClassGraphClassLoader = scanResult.classpathFinder
+ .getDelegateClassGraphClassLoader();
+ LinkageError linkageError = null;
+ if (delegateClassGraphClassLoader != null) {
+ try {
+ return Class.forName(className, initializeLoadedClasses, delegateClassGraphClassLoader);
+ } catch (final ClassNotFoundException e) {
+ // Ignore
+ } catch (final LinkageError e) {
+ linkageError = e;
+ }
+ }
- // Get ClassInfo for named class
- final ClassInfo classInfo = scanResult.getClassInfo(className);
+ // If overrideClassLoaders is set, only use the override loaders
+ if (overrideClassLoaders != null) {
+ for (final ClassLoader overrideClassLoader : overrideClassLoaders) {
+ try {
+ return Class.forName(className, initializeLoadedClasses, overrideClassLoader);
+ } catch (final ClassNotFoundException e) {
+ // Ignore
+ } catch (final LinkageError e) {
+ if (linkageError == null) {
+ linkageError = e;
+ }
+ }
+ }
+ }
- // Try environment classloaders first, if the classpath was not overridden, or the scan result
- // came from deserialization (since in this case, a new URLClassLoader was created for the
- // classpath entries that were found in the serialized JSON doc)
- boolean triedClassInfoLoader = false;
- if (scanResult.envClassLoaderOrder != null) {
- // Try environment classloaders
- for (final ClassLoader envClassLoader : scanResult.envClassLoaderOrder) {
+ // Try environment classloader(s) first, since this is the usual default
+ if (overrideClassLoaders == null && environmentClassLoaderDelegationOrder != null
+ && !environmentClassLoaderDelegationOrder.isEmpty()) {
+ for (final ClassLoader envClassLoader : environmentClassLoaderDelegationOrder) {
try {
- return Class.forName(className, scanResult.scanSpec.initializeLoadedClasses, envClassLoader);
- } catch (ReflectiveOperationException | LinkageError e) {
+ return Class.forName(className, initializeLoadedClasses, envClassLoader);
+ } catch (final ClassNotFoundException e) {
// Ignore
+ } catch (final LinkageError e) {
+ if (linkageError == null) {
+ linkageError = e;
+ }
}
- if (classInfo != null && envClassLoader == classInfo.classLoader) {
- triedClassInfoLoader = true;
+ }
+ }
+
+ // Try getting the ClassInfo for the named class, then the ClassLoader from the ClassInfo.
+ // This should still be valid if the ScanResult was closed, since ScanResult#close() leaves
+ // the classNameToClassInfo map intact, but still, this is only attempted if all the above
+ // efforts failed, to avoid accessing ClassInfo objects after the ScanResult is closed (#399).
+ ClassLoader classInfoClassLoader = null;
+ final ClassInfo classInfo = scanResult.classNameToClassInfo == null ? null
+ : scanResult.classNameToClassInfo.get(className);
+ if (classInfo != null) {
+ classInfoClassLoader = classInfo.classLoader;
+ // Try specific classloader for the classpath element that the classfile was obtained from,
+ // as long as it wasn't already tried
+ if (classInfoClassLoader != null && (environmentClassLoaderDelegationOrder == null
+ || !environmentClassLoaderDelegationOrder.contains(classInfoClassLoader))) {
+ try {
+ return Class.forName(className, initializeLoadedClasses, classInfoClassLoader);
+ } catch (final ClassNotFoundException e) {
+ // Ignore
+ } catch (final LinkageError e) {
+ if (linkageError == null) {
+ linkageError = e;
+ }
}
}
+
+ // If class came from a module, and it was not able to be loaded by the environment classloader,
+ // then it is probable it was a non-public class, and ClassGraph found it by ignoring class visibility
+ // when reading the resources in exported packages directly. Force ClassGraph to respect JPMS
+ // encapsulation rules by refusing to load modular classes that the context/system classloaders
+ // could not load. (A SecurityException should be thrown above, but this is here for completeness.)
+ if (classInfo.classpathElement instanceof ClasspathElementModule && !classInfo.isPublic()) {
+ throw new ClassNotFoundException("Classfile for class " + className + " was found in a module, "
+ + "but the context and system classloaders could not load the class, probably because "
+ + "the class is not public.");
+ }
}
- // Try specific classloader for the classpath element that the classfile was obtained from
- if (!triedClassInfoLoader && classInfo != null && classInfo.classLoader != null) {
+ // Try loading from classpath URLs
+ if (overrideClassLoaders == null && classpathClassLoader != null) {
try {
- return Class.forName(className, scanResult.scanSpec.initializeLoadedClasses, classInfo.classLoader);
- } catch (final ReflectiveOperationException | LinkageError e) {
+ return Class.forName(className, initializeLoadedClasses, classpathClassLoader);
+ } catch (final ClassNotFoundException e) {
// Ignore
+ } catch (final LinkageError e) {
+ if (linkageError == null) {
+ linkageError = e;
+ }
}
}
- // If class came from a module, and it was not able to be loaded by the environment classloader,
- // then it is possible it was a non-public class, and ClassGraph found it by ignoring class visibility
- // when reading the resources in exported packages directly. Force ClassGraph to respect JPMS
- // encapsulation rules by refusing to load modular classes that the context/system classloaders
- // could not load. (A SecurityException should be thrown above, but this is here for completeness.)
- if (classInfo != null && classInfo.classpathElement instanceof ClasspathElementModule
- && !classInfo.isPublic()) {
- throw new ClassNotFoundException("Classfile for class " + className + " was found in a module, "
- + "but the context and system classloaders could not load the class, probably because "
- + "the class is not public.");
+ // Try any added classloader(s)
+ if (addedClassLoaderDelegationOrder != null && !addedClassLoaderDelegationOrder.isEmpty()) {
+ for (final ClassLoader additionalClassLoader : addedClassLoaderDelegationOrder) {
+ if (additionalClassLoader != classInfoClassLoader) {
+ try {
+ return Class.forName(className, initializeLoadedClasses, additionalClassLoader);
+ } catch (final ClassNotFoundException e) {
+ // Ignore
+ } catch (final LinkageError e) {
+ if (linkageError == null) {
+ linkageError = e;
+ }
+ }
+ }
+ }
}
- // Try obtaining the classfile as a resource, and defining the class from the resource content
+ // As a last-ditch attempt, if the above efforts all failed, try obtaining the classfile as a
+ // resource, and define the class from the resource content. This should be performed after
+ // environment classloading is attempted, so that classes are not loaded by a mix of environment
+ // classloaders and direct manual classloading, otherwise class compatibility issues can arise.
+ // The ScanResult should only be accessed (to fetch resources) as a last resort, so that wherever
+ // possible, linked classes can be loaded after the ScanResult is closed. Otherwise if you load
+ // classes before a ScanResult is closed, then you close the ScanResult, then you try to access
+ // fields of the ScanResult that have a type that has not yet been loaded, this can trigger an
+ // exception that the ScanResult was accessed after it was closed (#399).
final ResourceList classfileResources = scanResult
.getResourcesWithPath(JarUtils.classNameToClassfilePath(className));
if (classfileResources != null) {
for (final Resource resource : classfileResources) {
// Iterate through resources (only loading of first resource in the list will be attempted)
- try {
- // Load the content of the resource, and define a class from it
- final byte[] resourceContent = resource.load();
- return defineClass(className, resourceContent, 0, resourceContent.length);
+ // Load the content of the resource, and define a class from it
+ try (Resource resourceToClose = resource) {
+ // TODO: is there any need to try java.lang.invoke.MethodHandles.Lookup.defineClass
+ // via reflection (it's implemented in JDK 9), if the following fails?
+ // See: https://bugs.openjdk.java.net/browse/JDK-8202999
+ return defineClass(className, resourceToClose.read(), (ProtectionDomain) null);
} catch (final IOException e) {
throw new ClassNotFoundException("Could not load classfile for class " + className + " : " + e);
- } finally {
- resource.close();
+ } catch (final LinkageError e) {
+ if (linkageError == null) {
+ linkageError = e;
+ }
+ }
+ }
+ }
+
+ if (linkageError != null) {
+ if (VersionFinder.OS == OperatingSystem.Windows) {
+ // LinkageError indicates that a classfile was found, but the class couldn't be loaded.
+ // Hackily detect the situation where there are two classfiles with the same case insensitive name
+ // on Windows filesystems (#494).
+ final String msg = linkageError.getMessage();
+ if (msg != null) {
+ final String wrongName = "(wrong name: ";
+ final int wrongNameIdx = msg.indexOf(wrongName);
+ if (wrongNameIdx > -1) {
+ final String theWrongName = msg.substring(wrongNameIdx + wrongName.length(),
+ msg.length() - 1);
+ if (theWrongName.replace('/', '.').equalsIgnoreCase(className)) {
+ throw new LinkageError("You appear to have two classfiles with the same "
+ + "case-insensitive name in the same directory on a case-insensitive "
+ + "filesystem -- this is not allowed on Windows, and therefore your "
+ + "code is not portable. Class name: " + className, linkageError);
+ }
+ }
}
}
+ throw linkageError;
}
- throw new ClassNotFoundException("Could not load classfile for class " + className);
+
+ throw new ClassNotFoundException("Could not find or load classfile for class " + className);
+ }
+
+ /**
+ * Get classpath URLs.
+ *
+ * @return The classpath URLs in the {@link ScanResult} handled by this {@link ClassLoader}.
+ */
+ public URL[] getURLs() {
+ return scanResult.getClasspathURLs().toArray(new URL[0]);
}
/* (non-Javadoc)
@@ -127,6 +318,30 @@ protected Class> findClass(final String className)
*/
@Override
public URL getResource(final String path) {
+ // This order should match the order in findClass(String)
+
+ // Try loading resource from environment classloader(s)
+ if (!environmentClassLoaderDelegationOrder.isEmpty()) {
+ for (final ClassLoader envClassLoader : environmentClassLoaderDelegationOrder) {
+ final URL resource = envClassLoader.getResource(path);
+ if (resource != null) {
+ return resource;
+ }
+ }
+ }
+
+ // Try loading resource from overridden or added classloader(s)
+ if (!addedClassLoaderDelegationOrder.isEmpty()) {
+ for (final ClassLoader additionalClassLoader : addedClassLoaderDelegationOrder) {
+ final URL resource = additionalClassLoader.getResource(path);
+ if (resource != null) {
+ return resource;
+ }
+ }
+ }
+
+ // If the above attempts fail, try retrieving resource from ScanResult.
+ // This will throw an exception if ScanResult has already been closed (#399).
final ResourceList resourceList = scanResult.getResourcesWithPath(path);
if (resourceList == null || resourceList.isEmpty()) {
return super.getResource(path);
@@ -140,9 +355,33 @@ public URL getResource(final String path) {
*/
@Override
public Enumeration getResources(final String path) throws IOException {
+ // This order should match the order in findClass(String)
+
+ // Try loading resources from environment classloader(s)
+ if (!environmentClassLoaderDelegationOrder.isEmpty()) {
+ for (final ClassLoader envClassLoader : environmentClassLoaderDelegationOrder) {
+ final Enumeration resources = envClassLoader.getResources(path);
+ if (resources != null && resources.hasMoreElements()) {
+ return resources;
+ }
+ }
+ }
+
+ // Try loading resources from overridden or added classloader(s)
+ if (!addedClassLoaderDelegationOrder.isEmpty()) {
+ for (final ClassLoader additionalClassLoader : addedClassLoaderDelegationOrder) {
+ final Enumeration resources = additionalClassLoader.getResources(path);
+ if (resources != null && resources.hasMoreElements()) {
+ return resources;
+ }
+ }
+ }
+
+ // If the above attempts fail, try retrieving resource from ScanResult.
+ // This will throw an exception if ScanResult has already been closed (#399).
final ResourceList resourceList = scanResult.getResourcesWithPath(path);
if (resourceList == null || resourceList.isEmpty()) {
- return super.getResources(path);
+ return Collections.emptyEnumeration();
} else {
return new Enumeration() {
/** The idx. */
@@ -166,6 +405,30 @@ public URL nextElement() {
*/
@Override
public InputStream getResourceAsStream(final String path) {
+ // This order should match the order in findClass(String)
+
+ // Try opening resource from environment classloader(s)
+ if (!environmentClassLoaderDelegationOrder.isEmpty()) {
+ for (final ClassLoader envClassLoader : environmentClassLoaderDelegationOrder) {
+ final InputStream inputStream = envClassLoader.getResourceAsStream(path);
+ if (inputStream != null) {
+ return inputStream;
+ }
+ }
+ }
+
+ // Try opening resource from overridden or added classloader(s)
+ if (!addedClassLoaderDelegationOrder.isEmpty()) {
+ for (final ClassLoader additionalClassLoader : addedClassLoaderDelegationOrder) {
+ final InputStream inputStream = additionalClassLoader.getResourceAsStream(path);
+ if (inputStream != null) {
+ return inputStream;
+ }
+ }
+ }
+
+ // If the above attempts fail, try opening resource from ScanResult.
+ // This will throw an exception if ScanResult has already been closed (#399).
final ResourceList resourceList = scanResult.getResourcesWithPath(path);
if (resourceList == null || resourceList.isEmpty()) {
return super.getResourceAsStream(path);
diff --git a/src/main/java/io/github/classgraph/ClassGraphException.java b/src/main/java/io/github/classgraph/ClassGraphException.java
index 5b7ef26e8..d88ea9732 100644
--- a/src/main/java/io/github/classgraph/ClassGraphException.java
+++ b/src/main/java/io/github/classgraph/ClassGraphException.java
@@ -46,7 +46,7 @@ public class ClassGraphException extends IllegalArgumentException {
* @param message
* the message
*/
- private ClassGraphException(final String message) {
+ ClassGraphException(final String message) {
super(message);
}
@@ -58,32 +58,7 @@ private ClassGraphException(final String message) {
* @param cause
* the cause
*/
- private ClassGraphException(final String message, final Throwable cause) {
+ ClassGraphException(final String message, final Throwable cause) {
super(message, cause);
}
-
- /**
- * Static factory method to stop IDEs from auto-completing ClassGraphException after "new ClassGraph".
- *
- * @param message
- * the message
- * @return the ClassGraphException
- */
- public static ClassGraphException newClassGraphException(final String message) {
- return new ClassGraphException(message);
- }
-
- /**
- * Static factory method to stop IDEs from auto-completing ClassGraphException after "new ClassGraph".
- *
- * @param message
- * the message
- * @param cause
- * the cause
- * @return the ClassGraphException
- */
- public static ClassGraphException newClassGraphException(final String message, final Throwable cause)
- throws ClassGraphException {
- return new ClassGraphException(message, cause);
- }
}
diff --git a/src/main/java/io/github/classgraph/ClassInfo.java b/src/main/java/io/github/classgraph/ClassInfo.java
index 824395541..dfc691a2d 100644
--- a/src/main/java/io/github/classgraph/ClassInfo.java
+++ b/src/main/java/io/github/classgraph/ClassInfo.java
@@ -29,10 +29,12 @@
package io.github.classgraph;
import java.io.File;
+import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URL;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
@@ -47,25 +49,30 @@
import java.util.Map.Entry;
import java.util.Set;
-import nonapi.io.github.classgraph.ScanSpec;
+import io.github.classgraph.Classfile.ClassContainment;
+import io.github.classgraph.Classfile.ClassTypeAnnotationDecorator;
+import io.github.classgraph.FieldInfoList.FieldInfoFilter;
import nonapi.io.github.classgraph.json.Id;
+import nonapi.io.github.classgraph.reflection.ReflectionUtils;
+import nonapi.io.github.classgraph.scanspec.ScanSpec;
import nonapi.io.github.classgraph.types.ParseException;
+import nonapi.io.github.classgraph.types.Parser;
import nonapi.io.github.classgraph.types.TypeUtils;
import nonapi.io.github.classgraph.types.TypeUtils.ModifierType;
+import nonapi.io.github.classgraph.utils.Assert;
+import nonapi.io.github.classgraph.utils.LogNode;
/** Holds metadata about a class encountered during a scan. */
public class ClassInfo extends ScanResultObject implements Comparable, HasName {
/** The name of the class. */
- private @Id String name;
+ @Id
+ protected String name;
/** Class modifier flags, e.g. Modifier.PUBLIC */
private int modifiers;
- /** True if the classfile indicated this is an interface (or an annotation, which is an interface). */
- private boolean isInterface;
-
- /** True if the classfile indicated this is an annotation. */
- private boolean isAnnotation;
+ /** True if the class is a record. */
+ private boolean isRecord;
/**
* This annotation has the {@link Inherited} meta-annotation, which means that any class that this annotation is
@@ -73,36 +80,48 @@ public class ClassInfo extends ScanResultObject implements Comparable
*/
boolean isInherited;
+ /** The minor version of the classfile format for this class' classfile. */
+ private int classfileMinorVersion;
+
+ /** The major version of the classfile format for this class' classfile. */
+ private int classfileMajorVersion;
+
/** The class type signature string. */
- private String typeSignatureStr;
+ protected String typeSignatureStr;
/** The class type signature, parsed. */
private transient ClassTypeSignature typeSignature;
+ /** The synthetic class type descriptor. */
+ private transient ClassTypeSignature typeDescriptor;
+
+ /** The name of the source file this class has been compiled from */
+ private String sourceFile;
+
/** The fully-qualified defining method name, for anonymous inner classes. */
private String fullyQualifiedDefiningMethodName;
/**
* If true, this class is only being referenced by another class' classfile as a superclass / implemented
- * interface / annotation, but this class is not itself a whitelisted (non-blacklisted) class, or in a
- * whitelisted (non-blacklisted) package.
+ * interface / annotation, but this class is not itself an accepted (non-rejected) class, or in a accepted
+ * (non-rejected) package.
*
* If false, this classfile was matched during scanning (i.e. its classfile contents read), i.e. this class is a
- * whitelisted (and non-blacklisted) class in a whitelisted (and non-blacklisted) package.
+ * accepted (and non-rejected) class in an accepted (and non-rejected) package.
*/
- private boolean isExternalClass = true;
+ protected boolean isExternalClass = true;
/**
* Set to true when the class is actually scanned (as opposed to just referenced as a superclass, interface or
* annotation of a scanned class).
*/
- private boolean isScannedClass;
+ protected boolean isScannedClass;
/** The classpath element that this class was found within. */
transient ClasspathElement classpathElement;
/** The {@link Resource} for the classfile of this class. */
- private transient Resource classfileResource;
+ protected transient Resource classfileResource;
/** The classloader this class was obtained from. */
transient ClassLoader classLoader;
@@ -125,13 +144,19 @@ public class ClassInfo extends ScanResultObject implements Comparable
/** For annotations, the default values of parameters. */
AnnotationParameterValueList annotationDefaultParamValues;
+ /** The type annotation decorators for the {@link ClassTypeSignature} instance. */
+ transient List typeAnnotationDecorators;
+
/**
* Names of classes referenced by this class in class refs and type signatures in the constant pool of the
* classfile.
*/
private Set referencedClassNames;
- /** A list of ClassInfo objects for classes referenced by this class. */
+ /**
+ * A list of ClassInfo objects for classes referenced by this class. Derived from {@link #referencedClassNames}
+ * when the relevant {@link ClassInfo} objects are created.
+ */
private ClassInfoList referencedClasses;
/**
@@ -149,6 +174,17 @@ public class ClassInfo extends ScanResultObject implements Comparable
*/
private transient List overrideOrder;
+ /**
+ * The override order for a class' methods (base class, followed by superclasses, followed by interfaces).
+ */
+ private transient List methodOverrideOrder;
+
+ /** The annotations, once they are loaded */
+ private ClassInfoList annotationsRef;
+
+ /** The annotation infos, once they are loaded */
+ private AnnotationInfoList annotationInfoRef;
+
// -------------------------------------------------------------------------------------------------------------
/** The modifier bit for annotations. */
@@ -176,7 +212,8 @@ public class ClassInfo extends ScanResultObject implements Comparable
* @param classfileResource
* the classfile resource
*/
- private ClassInfo(final String name, final int classModifiers, final Resource classfileResource) {
+ @SuppressWarnings("null")
+ protected ClassInfo(final String name, final int classModifiers, final Resource classfileResource) {
super();
this.name = name;
if (name.endsWith(";")) {
@@ -184,12 +221,6 @@ private ClassInfo(final String name, final int classModifiers, final Resource cl
throw new IllegalArgumentException("Bad class name");
}
setModifiers(classModifiers);
- if ((classModifiers & ANNOTATION_CLASS_MODIFIER) != 0) {
- isAnnotation = true;
- }
- if ((classModifiers & Modifier.INTERFACE) != 0) {
- isInterface = true;
- }
this.classfileResource = classfileResource;
this.relatedClasses = new EnumMap<>(RelType.class);
}
@@ -255,6 +286,12 @@ enum RelType {
*/
CLASSES_WITH_METHOD_ANNOTATION,
+ /**
+ * Classes that have one or more non-private (inherited) methods annotated with this annotation, if this is
+ * an annotation.
+ */
+ CLASSES_WITH_NONPRIVATE_METHOD_ANNOTATION,
+
/** Annotations on one or more parameters of methods of this class. */
METHOD_PARAMETER_ANNOTATIONS,
@@ -264,6 +301,12 @@ enum RelType {
*/
CLASSES_WITH_METHOD_PARAMETER_ANNOTATION,
+ /**
+ * Classes that have one or more non-private (inherited) methods that have one or more parameters annotated
+ * with this annotation, if this is an annotation.
+ */
+ CLASSES_WITH_NONPRIVATE_METHOD_PARAMETER_ANNOTATION,
+
// Field annotations:
/** Annotations on one or more fields of this class. */
@@ -273,6 +316,12 @@ enum RelType {
* Classes that have one or more fields annotated with this annotation, if this is an annotation.
*/
CLASSES_WITH_FIELD_ANNOTATION,
+
+ /**
+ * Classes that have one or more non-private (inherited) fields annotated with this annotation, if this is
+ * an annotation.
+ */
+ CLASSES_WITH_NONPRIVATE_FIELD_ANNOTATION,
}
/**
@@ -300,23 +349,82 @@ boolean addRelatedClass(final RelType relType, final ClassInfo classInfo) {
*
* @param className
* the class name
- * @param classModifiers
- * the class modifiers
* @param classNameToClassInfo
* the map from class name to class info
- * @return the or create class info
+ * @return the {@link ClassInfo} object.
*/
- static ClassInfo getOrCreateClassInfo(final String className, final int classModifiers,
+ static ClassInfo getOrCreateClassInfo(final String className,
final Map classNameToClassInfo) {
+ // Look for array class names
+ int numArrayDims = 0;
+ String baseClassName = className;
+ while (baseClassName.endsWith("[]")) {
+ numArrayDims++;
+ baseClassName = baseClassName.substring(0, baseClassName.length() - 2);
+ }
+ // Be resilient to the use of class descriptors rather than class names (should not be needed)
+ while (baseClassName.startsWith("[")) {
+ numArrayDims++;
+ baseClassName = baseClassName.substring(1);
+ }
+ if (baseClassName.endsWith(";")) {
+ baseClassName = baseClassName.substring(baseClassName.length() - 1);
+ }
+ baseClassName = baseClassName.replace('/', '.');
+
ClassInfo classInfo = classNameToClassInfo.get(className);
if (classInfo == null) {
- classNameToClassInfo.put(className,
- classInfo = new ClassInfo(className, classModifiers, /* classfileResource = */ null));
+ if (numArrayDims == 0) {
+ classInfo = new ClassInfo(baseClassName, /* classModifiers = */ 0, /* classfileResource = */ null);
+ } else {
+ final StringBuilder arrayTypeSigStrBuf = new StringBuilder();
+ for (int i = 0; i < numArrayDims; i++) {
+ arrayTypeSigStrBuf.append('[');
+ }
+ TypeSignature elementTypeSignature;
+ final char baseTypeChar = BaseTypeSignature.getTypeChar(baseClassName);
+ if (baseTypeChar != '\0') {
+ // Element type is a base (primitive) type
+ arrayTypeSigStrBuf.append(baseTypeChar);
+ elementTypeSignature = new BaseTypeSignature(baseTypeChar);
+ } else {
+ // Element type is not a base (primitive) type -- create a type signature for element type
+ final String eltTypeSigStr = "L" + baseClassName.replace('.', '/') + ";";
+ arrayTypeSigStrBuf.append(eltTypeSigStr);
+ try {
+ elementTypeSignature = ClassRefTypeSignature.parse(new Parser(eltTypeSigStr),
+ // No type variables to resolve for generic types
+ /* definingClassName = */ null);
+ if (elementTypeSignature == null) {
+ throw new IllegalArgumentException(
+ "Could not form array base type signature for class " + baseClassName);
+ }
+ } catch (final ParseException e) {
+ throw new IllegalArgumentException(
+ "Could not form array base type signature for class " + baseClassName);
+ }
+ }
+ classInfo = new ArrayClassInfo(
+ new ArrayTypeSignature(elementTypeSignature, numArrayDims, arrayTypeSigStrBuf.toString()));
+ }
+ classNameToClassInfo.put(className, classInfo);
}
- classInfo.setModifiers(classModifiers);
return classInfo;
}
+ /**
+ * Set classfile version.
+ *
+ * @param minorVersion
+ * the minor version of the classfile format for this class' classfile.
+ * @param majorVersion
+ * the major version of the classfile format for this class' classfile.
+ */
+ void setClassfileVersion(final int minorVersion, final int majorVersion) {
+ this.classfileMinorVersion = minorVersion;
+ this.classfileMajorVersion = majorVersion;
+ }
+
/**
* Set class modifiers.
*
@@ -325,12 +433,6 @@ static ClassInfo getOrCreateClassInfo(final String className, final int classMod
*/
void setModifiers(final int modifiers) {
this.modifiers |= modifiers;
- if ((modifiers & ANNOTATION_CLASS_MODIFIER) != 0) {
- this.isAnnotation = true;
- }
- if ((modifiers & Modifier.INTERFACE) != 0) {
- this.isInterface = true;
- }
}
/**
@@ -340,17 +442,56 @@ void setModifiers(final int modifiers) {
* true if this is an interface
*/
void setIsInterface(final boolean isInterface) {
- this.isInterface |= isInterface;
+ if (isInterface) {
+ this.modifiers |= Modifier.INTERFACE;
+ }
}
/**
- * Set isInterface status.
+ * Set isAnnotation status.
*
* @param isAnnotation
* true if this is an annotation
*/
void setIsAnnotation(final boolean isAnnotation) {
- this.isAnnotation |= isAnnotation;
+ if (isAnnotation) {
+ this.modifiers |= ANNOTATION_CLASS_MODIFIER;
+ }
+ }
+
+ /**
+ * Set isRecord status.
+ *
+ * @param isRecord
+ * true if this is a record
+ */
+ void setIsRecord(final boolean isRecord) {
+ if (isRecord) {
+ this.isRecord = isRecord;
+ }
+ }
+
+ /**
+ * Set source file.
+ *
+ * @param sourceFile
+ * the source file
+ */
+ void setSourceFile(final String sourceFile) {
+ this.sourceFile = sourceFile;
+ }
+
+ /**
+ * Add {@link ClassTypeAnnotationDecorator} instances.
+ *
+ * @param classTypeAnnotationDecorators
+ * {@link ClassTypeAnnotationDecorator} instances.
+ */
+ void addTypeDecorators(final List classTypeAnnotationDecorators) {
+ if (typeAnnotationDecorators == null) {
+ typeAnnotationDecorators = new ArrayList<>();
+ }
+ typeAnnotationDecorators.addAll(classTypeAnnotationDecorators);
}
// -------------------------------------------------------------------------------------------------------------
@@ -365,8 +506,7 @@ void setIsAnnotation(final boolean isAnnotation) {
*/
void addSuperclass(final String superclassName, final Map classNameToClassInfo) {
if (superclassName != null && !superclassName.equals("java.lang.Object")) {
- final ClassInfo superclassClassInfo = getOrCreateClassInfo(superclassName, /* classModifiers = */ 0,
- classNameToClassInfo);
+ final ClassInfo superclassClassInfo = getOrCreateClassInfo(superclassName, classNameToClassInfo);
this.addRelatedClass(RelType.SUPERCLASSES, superclassClassInfo);
superclassClassInfo.addRelatedClass(RelType.SUBCLASSES, this);
}
@@ -381,10 +521,8 @@ void addSuperclass(final String superclassName, final Map cla
* the map from class name to class info
*/
void addImplementedInterface(final String interfaceName, final Map classNameToClassInfo) {
- final ClassInfo interfaceClassInfo = getOrCreateClassInfo(interfaceName,
- /* classModifiers = */ Modifier.INTERFACE, classNameToClassInfo);
- interfaceClassInfo.isInterface = true;
- interfaceClassInfo.modifiers |= Modifier.INTERFACE;
+ final ClassInfo interfaceClassInfo = getOrCreateClassInfo(interfaceName, classNameToClassInfo);
+ interfaceClassInfo.setIsInterface(true);
this.addRelatedClass(RelType.IMPLEMENTED_INTERFACES, interfaceClassInfo);
interfaceClassInfo.addRelatedClass(RelType.CLASSES_IMPLEMENTING, this);
}
@@ -397,15 +535,14 @@ void addImplementedInterface(final String interfaceName, final Map> classContainmentEntries,
+ static void addClassContainment(final List classContainmentEntries,
final Map classNameToClassInfo) {
- for (final SimpleEntry ent : classContainmentEntries) {
- final String innerClassName = ent.getKey();
- final ClassInfo innerClassInfo = ClassInfo.getOrCreateClassInfo(innerClassName,
- /* classModifiers = */ 0, classNameToClassInfo);
- final String outerClassName = ent.getValue();
- final ClassInfo outerClassInfo = ClassInfo.getOrCreateClassInfo(outerClassName,
- /* classModifiers = */ 0, classNameToClassInfo);
+ for (final ClassContainment classContainment : classContainmentEntries) {
+ final ClassInfo innerClassInfo = ClassInfo.getOrCreateClassInfo(classContainment.innerClassName,
+ classNameToClassInfo);
+ innerClassInfo.setModifiers(classContainment.innerClassModifierBits);
+ final ClassInfo outerClassInfo = ClassInfo.getOrCreateClassInfo(classContainment.outerClassName,
+ classNameToClassInfo);
innerClassInfo.addRelatedClass(RelType.CONTAINED_WITHIN_OUTER_CLASS, outerClassInfo);
outerClassInfo.addRelatedClass(RelType.CONTAINS_INNER_CLASS, innerClassInfo);
}
@@ -432,7 +569,8 @@ void addFullyQualifiedDefiningMethodName(final String fullyQualifiedDefiningMeth
void addClassAnnotation(final AnnotationInfo classAnnotationInfo,
final Map classNameToClassInfo) {
final ClassInfo annotationClassInfo = getOrCreateClassInfo(classAnnotationInfo.getName(),
- ANNOTATION_CLASS_MODIFIER, classNameToClassInfo);
+ classNameToClassInfo);
+ annotationClassInfo.setModifiers(ANNOTATION_CLASS_MODIFIER);
if (this.annotationInfo == null) {
this.annotationInfo = new AnnotationInfoList(2);
}
@@ -454,21 +592,29 @@ void addClassAnnotation(final AnnotationInfo classAnnotationInfo,
* the annotation info list
* @param isField
* the is field
+ * @param modifiers
+ * the field or method modifiers
* @param classNameToClassInfo
* the map from class name to class info
*/
private void addFieldOrMethodAnnotationInfo(final AnnotationInfoList annotationInfoList, final boolean isField,
- final Map classNameToClassInfo) {
+ final int modifiers, final Map classNameToClassInfo) {
if (annotationInfoList != null) {
for (final AnnotationInfo fieldAnnotationInfo : annotationInfoList) {
final ClassInfo annotationClassInfo = getOrCreateClassInfo(fieldAnnotationInfo.getName(),
- ANNOTATION_CLASS_MODIFIER, classNameToClassInfo);
+ classNameToClassInfo);
+ annotationClassInfo.setModifiers(ANNOTATION_CLASS_MODIFIER);
// Mark this class as having a field or method with this annotation
this.addRelatedClass(isField ? RelType.FIELD_ANNOTATIONS : RelType.METHOD_ANNOTATIONS,
annotationClassInfo);
annotationClassInfo.addRelatedClass(
isField ? RelType.CLASSES_WITH_FIELD_ANNOTATION : RelType.CLASSES_WITH_METHOD_ANNOTATION,
this);
+ // For non-private methods/fields, also add to nonprivate (inherited) mapping
+ if (!Modifier.isPrivate(modifiers)) {
+ annotationClassInfo.addRelatedClass(isField ? RelType.CLASSES_WITH_NONPRIVATE_FIELD_ANNOTATION
+ : RelType.CLASSES_WITH_NONPRIVATE_METHOD_ANNOTATION, this);
+ }
}
}
}
@@ -483,7 +629,9 @@ private void addFieldOrMethodAnnotationInfo(final AnnotationInfoList annotationI
*/
void addFieldInfo(final FieldInfoList fieldInfoList, final Map classNameToClassInfo) {
for (final FieldInfo fi : fieldInfoList) {
- addFieldOrMethodAnnotationInfo(fi.annotationInfo, /* isField = */ true, classNameToClassInfo);
+ // Index field annotations
+ addFieldOrMethodAnnotationInfo(fi.annotationInfo, /* isField = */ true, fi.getModifiers(),
+ classNameToClassInfo);
}
if (this.fieldInfo == null) {
this.fieldInfo = fieldInfoList;
@@ -502,21 +650,27 @@ void addFieldInfo(final FieldInfoList fieldInfoList, final Map classNameToClassInfo) {
for (final MethodInfo mi : methodInfoList) {
- addFieldOrMethodAnnotationInfo(mi.annotationInfo, /* isField = */ false, classNameToClassInfo);
+ // Index method annotations
+ addFieldOrMethodAnnotationInfo(mi.annotationInfo, /* isField = */ false, mi.getModifiers(),
+ classNameToClassInfo);
- // Currently it is not possible to find methods by method parameter annotation
+ // Index method parameter annotations
if (mi.parameterAnnotationInfo != null) {
for (int i = 0; i < mi.parameterAnnotationInfo.length; i++) {
final AnnotationInfo[] paramAnnotationInfoArr = mi.parameterAnnotationInfo[i];
if (paramAnnotationInfoArr != null) {
- for (int j = 0; j < paramAnnotationInfoArr.length; j++) {
- final AnnotationInfo methodParamAnnotationInfo = paramAnnotationInfoArr[j];
+ for (final AnnotationInfo methodParamAnnotationInfo : paramAnnotationInfoArr) {
final ClassInfo annotationClassInfo = getOrCreateClassInfo(
- methodParamAnnotationInfo.getName(), ANNOTATION_CLASS_MODIFIER,
- classNameToClassInfo);
+ methodParamAnnotationInfo.getName(), classNameToClassInfo);
+ annotationClassInfo.setModifiers(ANNOTATION_CLASS_MODIFIER);
+ this.addRelatedClass(RelType.METHOD_PARAMETER_ANNOTATIONS, annotationClassInfo);
annotationClassInfo.addRelatedClass(RelType.CLASSES_WITH_METHOD_PARAMETER_ANNOTATION,
this);
- this.addRelatedClass(RelType.METHOD_PARAMETER_ANNOTATIONS, annotationClassInfo);
+ // For non-private methods/fields, also add to nonprivate (inherited) mapping
+ if (!Modifier.isPrivate(mi.getModifiers())) {
+ annotationClassInfo.addRelatedClass(
+ RelType.CLASSES_WITH_NONPRIVATE_METHOD_PARAMETER_ANNOTATION, this);
+ }
}
}
}
@@ -547,6 +701,7 @@ void setTypeSignature(final String typeSignatureStr) {
* the default param names and values, if this is an annotation
*/
void addAnnotationParamDefaultValues(final AnnotationParameterValueList paramNamesAndValues) {
+ setIsAnnotation(true);
if (this.annotationDefaultParamValues == null) {
this.annotationDefaultParamValues = paramNamesAndValues;
} else {
@@ -592,12 +747,18 @@ static ClassInfo addScannedClass(final String className, final int classModifier
+ " should not have been encountered more than once due to classpath masking --"
+ " please report this bug at: https://github.com/classgraph/classgraph/issues");
}
+
+ // Set the classfileResource for the placeholder class
+ classInfo.classfileResource = classfileResource;
+
+ // Add any additional modifier bits
+ classInfo.modifiers |= classModifiers;
}
// Mark the class as scanned
classInfo.isScannedClass = true;
- // Mark the class as non-external if it is a whitelisted class
+ // Mark the class as non-external if it is an accepted class
classInfo.isExternalClass = isExternalClass;
// Remember which classpath element (zipfile / classpath root directory / module) the class was found in
@@ -626,6 +787,10 @@ private enum ClassType {
ANNOTATION,
/** An interface or annotation (used since you can actually implement an annotation). */
INTERFACE_OR_ANNOTATION,
+ /** An enum. */
+ ENUM,
+ /** A record type. */
+ RECORD
}
/**
@@ -635,21 +800,23 @@ private enum ClassType {
* the classes
* @param scanSpec
* the scan spec
- * @param strictWhitelist
- * If true, exclude class if it is is external, blacklisted, or a system class.
+ * @param strictAccept
+ * If true, exclude class if it is external, if external classes are not enabled
* @param classTypes
* the class types
* @return the filtered classes.
*/
private static Set filterClassInfo(final Collection classes, final ScanSpec scanSpec,
- final boolean strictWhitelist, final ClassType... classTypes) {
+ final boolean strictAccept, final ClassType... classTypes) {
if (classes == null) {
- return Collections. emptySet();
+ return Collections.emptySet();
}
boolean includeAllTypes = classTypes.length == 0;
boolean includeStandardClasses = false;
boolean includeImplementedInterfaces = false;
boolean includeAnnotations = false;
+ boolean includeEnums = false;
+ boolean includeRecords = false;
for (final ClassType classType : classTypes) {
switch (classType) {
case ALL:
@@ -667,6 +834,12 @@ private static Set filterClassInfo(final Collection classe
case INTERFACE_OR_ANNOTATION:
includeImplementedInterfaces = includeAnnotations = true;
break;
+ case ENUM:
+ includeEnums = true;
+ break;
+ case RECORD:
+ includeRecords = true;
+ break;
default:
throw new IllegalArgumentException("Unknown ClassType: " + classType);
}
@@ -677,17 +850,18 @@ private static Set filterClassInfo(final Collection classe
final Set classInfoSetFiltered = new LinkedHashSet<>(classes.size());
for (final ClassInfo classInfo : classes) {
// Check class type against requested type(s)
- if ((includeAllTypes //
- || includeStandardClasses && classInfo.isStandardClass()
- || includeImplementedInterfaces && classInfo.isImplementedInterface()
- || includeAnnotations && classInfo.isAnnotation()) //
- // Always check blacklist
- && !scanSpec.classOrPackageIsBlacklisted(classInfo.name) //
- // Always return whitelisted classes, or external classes if enableExternalClasses is true
- && (!classInfo.isExternalClass || scanSpec.enableExternalClasses
- // Return external (non-whitelisted) classes if viewing class hierarchy "upwards"
- || !strictWhitelist)) {
- // Class passed strict whitelist criteria
+ final boolean includeType = includeAllTypes //
+ || includeStandardClasses && classInfo.isStandardClass() //
+ || includeImplementedInterfaces && classInfo.isImplementedInterface() //
+ || includeAnnotations && classInfo.isAnnotation() //
+ || includeEnums && classInfo.isEnum() //
+ || includeRecords && classInfo.isRecord();
+ // Return external (non-accepted) classes if viewing class hierarchy "upwards"
+ final boolean acceptClass = !classInfo.isExternalClass || scanSpec.enableExternalClasses
+ || !strictAccept;
+ // If class is of correct type, and class is accepted, and class/package are not explicitly rejected
+ if (includeType && acceptClass && !scanSpec.classOrPackageIsRejected(classInfo.name)) {
+ // Class passed accept criteria
classInfoSetFiltered.add(classInfo);
}
}
@@ -726,14 +900,14 @@ private ReachableAndDirectlyRelatedClasses(final Set reachableClasses
* directly related.
*
* @param relType
- * the rel type
- * @param strictWhitelist
- * the strict whitelist
+ * the relationship type
+ * @param strictAccept
+ * If true, exclude class if it is external, if external classes are not enabled
* @param classTypes
- * the class types
+ * the class types to accept
* @return the reachable and directly related classes
*/
- private ReachableAndDirectlyRelatedClasses filterClassInfo(final RelType relType, final boolean strictWhitelist,
+ private ReachableAndDirectlyRelatedClasses filterClassInfo(final RelType relType, final boolean strictAccept,
final ClassType... classTypes) {
Set directlyRelatedClasses = this.relatedClasses.get(relType);
if (directlyRelatedClasses == null) {
@@ -748,15 +922,18 @@ private ReachableAndDirectlyRelatedClasses filterClassInfo(final RelType relType
// For method and field annotations, need to change the RelType when finding meta-annotations
for (final ClassInfo annotation : directlyRelatedClasses) {
reachableClasses.addAll(
- annotation.filterClassInfo(RelType.CLASS_ANNOTATIONS, strictWhitelist).reachableClasses);
+ annotation.filterClassInfo(RelType.CLASS_ANNOTATIONS, strictAccept).reachableClasses);
}
} else if (relType == RelType.CLASSES_WITH_METHOD_ANNOTATION
+ || relType == RelType.CLASSES_WITH_NONPRIVATE_METHOD_ANNOTATION
|| relType == RelType.CLASSES_WITH_METHOD_PARAMETER_ANNOTATION
- || relType == RelType.CLASSES_WITH_FIELD_ANNOTATION) {
+ || relType == RelType.CLASSES_WITH_NONPRIVATE_METHOD_PARAMETER_ANNOTATION
+ || relType == RelType.CLASSES_WITH_FIELD_ANNOTATION
+ || relType == RelType.CLASSES_WITH_NONPRIVATE_FIELD_ANNOTATION) {
// If looking for meta-annotated methods or fields, need to find all meta-annotated annotations, then
// look for the methods or fields that they annotate
- for (final ClassInfo subAnnotation : this.filterClassInfo(RelType.CLASSES_WITH_ANNOTATION,
- strictWhitelist, ClassType.ANNOTATION).reachableClasses) {
+ for (final ClassInfo subAnnotation : this.filterClassInfo(RelType.CLASSES_WITH_ANNOTATION, strictAccept,
+ ClassType.ANNOTATION).reachableClasses) {
final Set annotatedClasses = subAnnotation.relatedClasses.get(relType);
if (annotatedClasses != null) {
reachableClasses.addAll(annotatedClasses);
@@ -804,8 +981,8 @@ private ReachableAndDirectlyRelatedClasses filterClassInfo(final RelType relType
}
return new ReachableAndDirectlyRelatedClasses(
- filterClassInfo(reachableClasses, scanResult.scanSpec, strictWhitelist, classTypes),
- filterClassInfo(directlyRelatedClasses, scanResult.scanSpec, strictWhitelist, classTypes));
+ filterClassInfo(reachableClasses, scanResult.scanSpec, strictAccept, classTypes),
+ filterClassInfo(directlyRelatedClasses, scanResult.scanSpec, strictAccept, classTypes));
}
@@ -822,7 +999,37 @@ private ReachableAndDirectlyRelatedClasses filterClassInfo(final RelType relType
*/
static ClassInfoList getAllClasses(final Collection classes, final ScanSpec scanSpec) {
return new ClassInfoList(
- ClassInfo.filterClassInfo(classes, scanSpec, /* strictWhitelist = */ true, ClassType.ALL),
+ ClassInfo.filterClassInfo(classes, scanSpec, /* strictAccept = */ true, ClassType.ALL),
+ /* sortByName = */ true);
+ }
+
+ /**
+ * Get all {@link Enum} classes found during the scan.
+ *
+ * @param classes
+ * the classes
+ * @param scanSpec
+ * the scan spec
+ * @return A list of all {@link Enum} classes found during the scan, or the empty list if none.
+ */
+ static ClassInfoList getAllEnums(final Collection classes, final ScanSpec scanSpec) {
+ return new ClassInfoList(
+ ClassInfo.filterClassInfo(classes, scanSpec, /* strictAccept = */ true, ClassType.ENUM),
+ /* sortByName = */ true);
+ }
+
+ /**
+ * Get all {@code record} classes found during the scan.
+ *
+ * @param classes
+ * the classes
+ * @param scanSpec
+ * the scan spec
+ * @return A list of all {@code record} classes found during the scan, or the empty list if none.
+ */
+ static ClassInfoList getAllRecords(final Collection classes, final ScanSpec scanSpec) {
+ return new ClassInfoList(
+ ClassInfo.filterClassInfo(classes, scanSpec, /* strictAccept = */ true, ClassType.RECORD),
/* sortByName = */ true);
}
@@ -836,8 +1043,9 @@ static ClassInfoList getAllClasses(final Collection classes, final Sc
* @return A list of all standard classes found during the scan, or the empty list if none.
*/
static ClassInfoList getAllStandardClasses(final Collection classes, final ScanSpec scanSpec) {
- return new ClassInfoList(ClassInfo.filterClassInfo(classes, scanSpec, /* strictWhitelist = */ true,
- ClassType.STANDARD_CLASS), /* sortByName = */ true);
+ return new ClassInfoList(
+ ClassInfo.filterClassInfo(classes, scanSpec, /* strictAccept = */ true, ClassType.STANDARD_CLASS),
+ /* sortByName = */ true);
}
/**
@@ -851,7 +1059,7 @@ static ClassInfoList getAllStandardClasses(final Collection classes,
*/
static ClassInfoList getAllImplementedInterfaceClasses(final Collection classes,
final ScanSpec scanSpec) {
- return new ClassInfoList(ClassInfo.filterClassInfo(classes, scanSpec, /* strictWhitelist = */ true,
+ return new ClassInfoList(ClassInfo.filterClassInfo(classes, scanSpec, /* strictAccept = */ true,
ClassType.IMPLEMENTED_INTERFACE), /* sortByName = */ true);
}
@@ -867,7 +1075,7 @@ static ClassInfoList getAllImplementedInterfaceClasses(final Collection classes, final ScanSpec scanSpec) {
return new ClassInfoList(
- ClassInfo.filterClassInfo(classes, scanSpec, /* strictWhitelist = */ true, ClassType.ANNOTATION),
+ ClassInfo.filterClassInfo(classes, scanSpec, /* strictAccept = */ true, ClassType.ANNOTATION),
/* sortByName = */ true);
}
@@ -879,11 +1087,11 @@ static ClassInfoList getAllAnnotationClasses(final Collection classes
* the classes
* @param scanSpec
* the scan spec
- * @return A list of all whitelisted interfaces found during the scan, or the empty list if none.
+ * @return A list of all accepted interfaces found during the scan, or the empty list if none.
*/
static ClassInfoList getAllInterfacesOrAnnotationClasses(final Collection classes,
final ScanSpec scanSpec) {
- return new ClassInfoList(ClassInfo.filterClassInfo(classes, scanSpec, /* strictWhitelist = */ true,
+ return new ClassInfoList(ClassInfo.filterClassInfo(classes, scanSpec, /* strictAccept = */ true,
ClassType.INTERFACE_OR_ANNOTATION), /* sortByName = */ true);
}
@@ -901,16 +1109,22 @@ public String getName() {
}
/**
- * Get simple name from fully-qualified class name.
+ * Get simple name from fully-qualified class name. Returns everything after the last '.' or the last '$' in the
+ * class name, or the whole string if the class is in the root package. (Note that this is not the same as the
+ * result of {@link Class#getSimpleName()}, which returns "" for anonymous classes.)
*
+ * @param className
+ * the class name
* @return The simple name of the class.
*/
static String getSimpleName(final String className) {
- return className.substring(className.lastIndexOf('.') + 1);
+ return className.substring(Math.max(className.lastIndexOf('.'), className.lastIndexOf('$')) + 1);
}
/**
- * Get the simple name of the class.
+ * Get the simple name of the class. Returns everything after the last '.' in the class name, or the whole
+ * string if the class is in the root package. (Note that this is not the same as the result of
+ * {@link Class#getSimpleName()}, which returns "" for anonymous classes.)
*
* @return The simple name of the class.
*/
@@ -948,13 +1162,33 @@ public String getPackageName() {
/**
* Checks if this is an external class.
*
- * @return true if this class is an external class, i.e. was referenced by a whitelisted class as a superclass,
- * interface, or annotation, but is not itself a whitelisted class.
+ * @return true if this class is an external class, i.e. was referenced by an accepted class as a superclass,
+ * interface, or annotation, but is not itself an accepted class.
*/
public boolean isExternalClass() {
return isExternalClass;
}
+ /**
+ * Get the minor version of the classfile format for this class' classfile.
+ *
+ * @return The minor version of the classfile format for this class' classfile, or 0 if this {@link ClassInfo}
+ * object is a placeholder for a referenced class that was not found or not accepted during the scan.
+ */
+ public int getClassfileMinorVersion() {
+ return classfileMinorVersion;
+ }
+
+ /**
+ * Get the major version of the classfile format for this class' classfile.
+ *
+ * @return The major version of the classfile format for this class' classfile, or 0 if this {@link ClassInfo}
+ * object is a placeholder for a referenced class that was not found or not accepted during the scan.
+ */
+ public int getClassfileMajorVersion() {
+ return classfileMajorVersion;
+ }
+
/**
* Get the class modifier bits.
*
@@ -982,7 +1216,34 @@ public String getModifiersStr() {
* @return true if this class is a public class.
*/
public boolean isPublic() {
- return (modifiers & Modifier.PUBLIC) != 0;
+ return Modifier.isPublic(modifiers);
+ }
+
+ /**
+ * Checks if the class is private.
+ *
+ * @return true if this class is a private class.
+ */
+ public boolean isPrivate() {
+ return Modifier.isPrivate(modifiers);
+ }
+
+ /**
+ * Checks if the class is protected.
+ *
+ * @return true if this class is a protected class.
+ */
+ public boolean isProtected() {
+ return Modifier.isProtected(modifiers);
+ }
+
+ /**
+ * Checks if the class has default (package) visibility.
+ *
+ * @return true if this class is only visible within its package.
+ */
+ public boolean isPackageVisible() {
+ return !isPublic() && !isPrivate() && !isProtected();
}
/**
@@ -991,7 +1252,7 @@ public boolean isPublic() {
* @return true if this class is an abstract class.
*/
public boolean isAbstract() {
- return (modifiers & 0x400) != 0;
+ return Modifier.isAbstract(modifiers);
}
/**
@@ -1009,7 +1270,7 @@ public boolean isSynthetic() {
* @return true if this class is a final class.
*/
public boolean isFinal() {
- return (modifiers & Modifier.FINAL) != 0;
+ return Modifier.isFinal(modifiers);
}
/**
@@ -1027,7 +1288,7 @@ public boolean isStatic() {
* @return true if this class is an annotation class.
*/
public boolean isAnnotation() {
- return isAnnotation;
+ return (modifiers & ANNOTATION_CLASS_MODIFIER) != 0;
}
/**
@@ -1037,7 +1298,7 @@ public boolean isAnnotation() {
* implemented).
*/
public boolean isInterface() {
- return isInterface && !isAnnotation;
+ return isInterfaceOrAnnotation() && !isAnnotation();
}
/**
@@ -1047,7 +1308,7 @@ public boolean isInterface() {
* implemented).
*/
public boolean isInterfaceOrAnnotation() {
- return isInterface;
+ return (modifiers & Modifier.INTERFACE) != 0;
}
/**
@@ -1059,13 +1320,43 @@ public boolean isEnum() {
return (modifiers & 0x4000) != 0;
}
+ /**
+ * Checks if is the class is a record (JDK 14+).
+ *
+ * @return true if this class is a record.
+ */
+ public boolean isRecord() {
+ return isRecord;
+ }
+
/**
* Checks if this class is a standard class.
*
* @return true if this class is a standard class (i.e. is not an annotation or interface).
*/
public boolean isStandardClass() {
- return !(isAnnotation || isInterface);
+ return !(isAnnotation() || isInterface());
+ }
+
+ /**
+ * Checks if this class is an array class. Returns false unless this {@link ClassInfo} is an instance of
+ * {@link ArrayClassInfo}.
+ *
+ * @return true if this is an array class.
+ */
+ public boolean isArrayClass() {
+ return this instanceof ArrayClassInfo;
+ }
+
+ /**
+ * Checks if this class extends the superclass.
+ *
+ * @param superclass
+ * A superclass.
+ * @return true if this class extends the superclass.
+ */
+ public boolean extendsSuperclass(final Class> superclass) {
+ return extendsSuperclass(superclass.getName());
}
/**
@@ -1076,7 +1367,8 @@ public boolean isStandardClass() {
* @return true if this class extends the named superclass.
*/
public boolean extendsSuperclass(final String superclassName) {
- return getSuperclasses().containsName(superclassName);
+ return (superclassName.equals("java.lang.Object") && isStandardClass())
+ || getSuperclasses().containsName(superclassName);
}
/**
@@ -1121,7 +1413,19 @@ public boolean isAnonymousInnerClass() {
* @return true if this class is an implemented interface.
*/
public boolean isImplementedInterface() {
- return relatedClasses.get(RelType.CLASSES_IMPLEMENTING) != null || (isInterface && !isAnnotation);
+ return relatedClasses.get(RelType.CLASSES_IMPLEMENTING) != null || isInterface();
+ }
+
+ /**
+ * Checks whether this class implements the interface.
+ *
+ * @param interfaceClazz
+ * An interface.
+ * @return true if this class implements the interface.
+ */
+ public boolean implementsInterface(final Class> interfaceClazz) {
+ Assert.isInterface(interfaceClazz);
+ return implementsInterface(interfaceClazz.getName());
}
/**
@@ -1135,6 +1439,18 @@ public boolean implementsInterface(final String interfaceName) {
return getInterfaces().containsName(interfaceName);
}
+ /**
+ * Checks whether this class has the annotation.
+ *
+ * @param annotation
+ * An annotation.
+ * @return true if this class has the annotation.
+ */
+ public boolean hasAnnotation(final Class extends Annotation> annotation) {
+ Assert.isAnnotation(annotation);
+ return hasAnnotation(annotation.getName());
+ }
+
/**
* Checks whether this class has the named annotation.
*
@@ -1165,7 +1481,7 @@ public boolean hasDeclaredField(final String fieldName) {
* @return true if this class or one of its superclasses declares a field of the given name.
*/
public boolean hasField(final String fieldName) {
- for (final ClassInfo ci : getOverrideOrder()) {
+ for (final ClassInfo ci : getFieldOverrideOrder()) {
if (ci.hasDeclaredField(fieldName)) {
return true;
}
@@ -1173,6 +1489,18 @@ public boolean hasField(final String fieldName) {
return false;
}
+ /**
+ * Checks whether this class declares a field with the annotation.
+ *
+ * @param annotation
+ * A field annotation.
+ * @return true if this class declares a field with the annotation.
+ */
+ public boolean hasDeclaredFieldAnnotation(final Class extends Annotation> annotation) {
+ Assert.isAnnotation(annotation);
+ return hasDeclaredFieldAnnotation(annotation.getName());
+ }
+
/**
* Checks whether this class declares a field with the named annotation.
*
@@ -1189,6 +1517,18 @@ public boolean hasDeclaredFieldAnnotation(final String fieldAnnotationName) {
return false;
}
+ /**
+ * Checks whether this class or one of its superclasses declares a field with the annotation.
+ *
+ * @param fieldAnnotation
+ * A field annotation.
+ * @return true if this class or one of its superclasses declares a field with the annotation.
+ */
+ public boolean hasFieldAnnotation(final Class extends Annotation> fieldAnnotation) {
+ Assert.isAnnotation(fieldAnnotation);
+ return hasFieldAnnotation(fieldAnnotation.getName());
+ }
+
/**
* Checks whether this class or one of its superclasses declares a field with the named annotation.
*
@@ -1197,7 +1537,7 @@ public boolean hasDeclaredFieldAnnotation(final String fieldAnnotationName) {
* @return true if this class or one of its superclasses declares a field with the named annotation.
*/
public boolean hasFieldAnnotation(final String fieldAnnotationName) {
- for (final ClassInfo ci : getOverrideOrder()) {
+ for (final ClassInfo ci : getFieldOverrideOrder()) {
if (ci.hasDeclaredFieldAnnotation(fieldAnnotationName)) {
return true;
}
@@ -1206,11 +1546,11 @@ public boolean hasFieldAnnotation(final String fieldAnnotationName) {
}
/**
- * Checks whether this class declares a field of the given name.
+ * Checks whether this class declares a method of the given name.
*
* @param methodName
* The name of a method.
- * @return true if this class declares a field of the given name.
+ * @return true if this class declares a method of the given name.
*/
public boolean hasDeclaredMethod(final String methodName) {
return getDeclaredMethodInfo().containsName(methodName);
@@ -1224,7 +1564,7 @@ public boolean hasDeclaredMethod(final String methodName) {
* @return true if this class or one of its superclasses or interfaces declares a method of the given name.
*/
public boolean hasMethod(final String methodName) {
- for (final ClassInfo ci : getOverrideOrder()) {
+ for (final ClassInfo ci : getMethodOverrideOrder()) {
if (ci.hasDeclaredMethod(methodName)) {
return true;
}
@@ -1232,6 +1572,18 @@ public boolean hasMethod(final String methodName) {
return false;
}
+ /**
+ * Checks whether this class declares a method with the annotation.
+ *
+ * @param methodAnnotation
+ * A method annotation.
+ * @return true if this class declares a method with the annotation.
+ */
+ public boolean hasDeclaredMethodAnnotation(final Class extends Annotation> methodAnnotation) {
+ Assert.isAnnotation(methodAnnotation);
+ return hasDeclaredMethodAnnotation(methodAnnotation.getName());
+ }
+
/**
* Checks whether this class declares a method with the named annotation.
*
@@ -1248,6 +1600,18 @@ public boolean hasDeclaredMethodAnnotation(final String methodAnnotationName) {
return false;
}
+ /**
+ * Checks whether this class or one of its superclasses or interfaces declares a method with the annotation.
+ *
+ * @param methodAnnotation
+ * A method annotation.
+ * @return true if this class or one of its superclasses or interfaces declares a method with the annotation.
+ */
+ public boolean hasMethodAnnotation(final Class extends Annotation> methodAnnotation) {
+ Assert.isAnnotation(methodAnnotation);
+ return hasMethodAnnotation(methodAnnotation.getName());
+ }
+
/**
* Checks whether this class or one of its superclasses or interfaces declares a method with the named
* annotation.
@@ -1258,7 +1622,7 @@ public boolean hasDeclaredMethodAnnotation(final String methodAnnotationName) {
* annotation.
*/
public boolean hasMethodAnnotation(final String methodAnnotationName) {
- for (final ClassInfo ci : getOverrideOrder()) {
+ for (final ClassInfo ci : getMethodOverrideOrder()) {
if (ci.hasDeclaredMethodAnnotation(methodAnnotationName)) {
return true;
}
@@ -1266,6 +1630,19 @@ public boolean hasMethodAnnotation(final String methodAnnotationName) {
return false;
}
+ /**
+ * Checks whether this class declares a method with the annotation.
+ *
+ * @param methodParameterAnnotation
+ * A method annotation.
+ * @return true if this class declares a method with the annotation.
+ */
+ public boolean hasDeclaredMethodParameterAnnotation(
+ final Class extends Annotation> methodParameterAnnotation) {
+ Assert.isAnnotation(methodParameterAnnotation);
+ return hasDeclaredMethodParameterAnnotation(methodParameterAnnotation.getName());
+ }
+
/**
* Checks whether this class declares a method with the named annotation.
*
@@ -1282,6 +1659,18 @@ public boolean hasDeclaredMethodParameterAnnotation(final String methodParameter
return false;
}
+ /**
+ * Checks whether this class or one of its superclasses or interfaces has a method with the annotation.
+ *
+ * @param methodParameterAnnotation
+ * A method annotation.
+ * @return true if this class or one of its superclasses or interfaces has a method with the annotation.
+ */
+ public boolean hasMethodParameterAnnotation(final Class extends Annotation> methodParameterAnnotation) {
+ Assert.isAnnotation(methodParameterAnnotation);
+ return hasMethodParameterAnnotation(methodParameterAnnotation.getName());
+ }
+
/**
* Checks whether this class or one of its superclasses or interfaces has a method with the named annotation.
*
@@ -1290,7 +1679,7 @@ public boolean hasDeclaredMethodParameterAnnotation(final String methodParameter
* @return true if this class or one of its superclasses or interfaces has a method with the named annotation.
*/
public boolean hasMethodParameterAnnotation(final String methodParameterAnnotationName) {
- for (final ClassInfo ci : getOverrideOrder()) {
+ for (final ClassInfo ci : getMethodOverrideOrder()) {
if (ci.hasDeclaredMethodParameterAnnotation(methodParameterAnnotationName)) {
return true;
}
@@ -1301,7 +1690,7 @@ public boolean hasMethodParameterAnnotation(final String methodParameterAnnotati
// -------------------------------------------------------------------------------------------------------------
/**
- * Recurse to interfaces and superclasses to get the order that fields and methods are overridden in.
+ * Recurse to interfaces and superclasses to get the order that fields are overridden in.
*
* @param visited
* visited
@@ -1309,61 +1698,140 @@ public boolean hasMethodParameterAnnotation(final String methodParameterAnnotati
* the override order
* @return the override order
*/
- private List getOverrideOrder(final Set visited, final List overrideOrderOut) {
+ private List getFieldOverrideOrder(final Set visited,
+ final List overrideOrderOut) {
if (visited.add(this)) {
overrideOrderOut.add(this);
for (final ClassInfo iface : getInterfaces()) {
- iface.getOverrideOrder(visited, overrideOrderOut);
+ iface.getFieldOverrideOrder(visited, overrideOrderOut);
}
final ClassInfo superclass = getSuperclass();
if (superclass != null) {
- superclass.getOverrideOrder(visited, overrideOrderOut);
+ superclass.getFieldOverrideOrder(visited, overrideOrderOut);
}
}
return overrideOrderOut;
}
/**
- * Get the order that fields and methods are overridden in (base class first).
+ * Get the order that fields are overridden in (base class first).
*
* @return the override order
*/
- private List getOverrideOrder() {
+ private List getFieldOverrideOrder() {
if (overrideOrder == null) {
- overrideOrder = getOverrideOrder(new HashSet(), new ArrayList());
+ overrideOrder = getFieldOverrideOrder(new HashSet(), new ArrayList());
}
return overrideOrder;
}
+ /**
+ * Recurse to collect classes and interfaces in the order of overridden methods, in descending priority.
+ *
+ * First collects all direct super classes, as their methods always have a higher priority than any method
+ * declared by an interface. Iterates over interfaces and inserts those extending already found interfaces
+ * before them in the output. The order of unrelated interfaces is unspecified.
+ *
+ * See Java Language Specification 8.4.8 for details.
+ *
+ * @param visited
+ * non-null set of already visited ClassInfos
+ * @param overrideOrderOut
+ * non-null outgoing list of ClassInfos in descending override order.
+ * @return the overrideOrderOut instance
+ */
+ private List getMethodOverrideOrder(final Set visited,
+ final List overrideOrderOut) {
+ if (!visited.add(this)) {
+ return overrideOrderOut;
+ }
+ //collect concrete super classes first, simply add to overrideOrder
+ if (!isInterfaceOrAnnotation()) {
+ overrideOrderOut.add(this);
+ //iterate over direct super classes first, they have the highest priority regarding method overrides
+ final ClassInfo superclass = getSuperclass();
+ if (superclass != null) {
+ superclass.getMethodOverrideOrder(visited, overrideOrderOut);
+ }
+ for (final ClassInfo iface : getInterfaces()) {
+ iface.getMethodOverrideOrder(visited, overrideOrderOut);
+ }
+ return overrideOrderOut;
+ }
+ // overrideOrderOut already contains all concrete classes now.
+ // This is an interface. If one of the extended interfaces is already in the output, then this needs to be
+ // added before it.
+ // Otherwise, this is unrelated to all collected ClassInfo so far and can simply be added to the result.
+ // The compiler should've prevented inheriting unrelated interfaces with methods having the same signature.
+ // Can still happen thanks to dynamically linking a different interface during runtime, for which the
+ // returned order is undefined.
+ final ClassInfoList interfaces = getInterfaces();
+ int minIndex = Integer.MAX_VALUE;
+ for (final ClassInfo iface : interfaces) {
+ if (!visited.contains(iface)) {
+ continue;
+ }
+ final int currIdx = overrideOrderOut.indexOf(iface);
+ minIndex = currIdx >= 0 && currIdx < minIndex ? currIdx : minIndex;
+ }
+ if (minIndex == Integer.MAX_VALUE) {
+ overrideOrderOut.add(this);
+ } else {
+ overrideOrderOut.add(minIndex, this);
+ }
+ // Add interfaces to end of override order
+ for (final ClassInfo iface : interfaces) {
+ iface.getMethodOverrideOrder(visited, overrideOrderOut);
+ }
+ return overrideOrderOut;
+ }
+
+ /**
+ * Get the order that methods are overridden in.
+ *
+ * @return the override order
+ */
+ private List getMethodOverrideOrder() {
+ if (methodOverrideOrder == null) {
+ methodOverrideOrder = getMethodOverrideOrder(new HashSet(), new ArrayList());
+ }
+ return methodOverrideOrder;
+ }
+
// -------------------------------------------------------------------------------------------------------------
// Standard classes
/**
* Get the subclasses of this class, sorted in order of name. Call {@link ClassInfoList#directOnly()} to get
* direct subclasses.
+ *
+ * If this class represents {@link Object}, then returns only standard classes, not interfaces, since interfaces
+ * don't extend {@link Object}.
*
* @return the list of subclasses of this class, or the empty list if none.
*/
public ClassInfoList getSubclasses() {
if (getName().equals("java.lang.Object")) {
// Make an exception for querying all subclasses of java.lang.Object
- return scanResult.getAllClasses();
+ return scanResult.getAllStandardClasses();
} else {
return new ClassInfoList(
- this.filterClassInfo(RelType.SUBCLASSES, /* strictWhitelist = */ !isExternalClass),
+ this.filterClassInfo(RelType.SUBCLASSES, /* strictAccept = */ !isExternalClass),
/* sortByName = */ true);
}
}
/**
- * Get all superclasses of this class, in ascending order in the class hierarchy. Does not include
- * superinterfaces, if this is an interface (use {@link #getInterfaces()} to get superinterfaces of an
- * interface.}
+ * Get all superclasses of this class, in ascending order in the class hierarchy, not including {@link Object}
+ * for simplicity, since that is the superclass of all classes.
+ *
+ * Also does not include superinterfaces, if this is an interface (use {@link #getInterfaces()} to get
+ * superinterfaces of an interface.}
*
* @return the list of all superclasses of this class, or the empty list if none.
*/
public ClassInfoList getSuperclasses() {
- return new ClassInfoList(this.filterClassInfo(RelType.SUPERCLASSES, /* strictWhitelist = */ false),
+ return new ClassInfoList(this.filterClassInfo(RelType.SUPERCLASSES, /* strictAccept = */ false),
/* sortByName = */ false);
}
@@ -1398,7 +1866,7 @@ public ClassInfo getSuperclass() {
*/
public ClassInfoList getOuterClasses() {
return new ClassInfoList(
- this.filterClassInfo(RelType.CONTAINED_WITHIN_OUTER_CLASS, /* strictWhitelist = */ false),
+ this.filterClassInfo(RelType.CONTAINED_WITHIN_OUTER_CLASS, /* strictAccept = */ false),
/* sortByName = */ false);
}
@@ -1408,7 +1876,7 @@ public ClassInfoList getOuterClasses() {
* @return A list of the inner classes contained within this class, or the empty list if none.
*/
public ClassInfoList getInnerClasses() {
- return new ClassInfoList(this.filterClassInfo(RelType.CONTAINS_INNER_CLASS, /* strictWhitelist = */ false),
+ return new ClassInfoList(this.filterClassInfo(RelType.CONTAINS_INNER_CLASS, /* strictAccept = */ false),
/* sortByName = */ true);
}
@@ -1437,16 +1905,17 @@ public String getFullyQualifiedDefiningMethodName() {
public ClassInfoList getInterfaces() {
// Classes also implement the interfaces of their superclasses
final ReachableAndDirectlyRelatedClasses implementedInterfaces = this
- .filterClassInfo(RelType.IMPLEMENTED_INTERFACES, /* strictWhitelist = */ false);
+ .filterClassInfo(RelType.IMPLEMENTED_INTERFACES, /* strictAccept = */ false);
final Set allInterfaces = new LinkedHashSet<>(implementedInterfaces.reachableClasses);
for (final ClassInfo superclass : this.filterClassInfo(RelType.SUPERCLASSES,
- /* strictWhitelist = */ false).reachableClasses) {
- final Set superclassImplementedInterfaces = superclass.filterClassInfo(
- RelType.IMPLEMENTED_INTERFACES, /* strictWhitelist = */ false).reachableClasses;
+ /* strictAccept = */ false).reachableClasses) {
+ final Set superclassImplementedInterfaces = superclass
+ .filterClassInfo(RelType.IMPLEMENTED_INTERFACES, /* strictAccept = */ false).reachableClasses;
allInterfaces.addAll(superclassImplementedInterfaces);
}
+ // Can't sort interfaces by name, since their order is significant in the definition of inheritance
return new ClassInfoList(allInterfaces, implementedInterfaces.directlyRelatedClasses,
- /* sortByName = */ true);
+ /* sortByName = */ false);
}
/**
@@ -1456,16 +1925,13 @@ public ClassInfoList getInterfaces() {
* interface, otherwise returns the empty list.
*/
public ClassInfoList getClassesImplementing() {
- if (!isInterface) {
- throw new IllegalArgumentException("Class is not an interface: " + getName());
- }
// Subclasses of implementing classes also implement the interface
final ReachableAndDirectlyRelatedClasses implementingClasses = this
- .filterClassInfo(RelType.CLASSES_IMPLEMENTING, /* strictWhitelist = */ !isExternalClass);
+ .filterClassInfo(RelType.CLASSES_IMPLEMENTING, /* strictAccept = */ !isExternalClass);
final Set allImplementingClasses = new LinkedHashSet<>(implementingClasses.reachableClasses);
for (final ClassInfo implementingClass : implementingClasses.reachableClasses) {
final Set implementingSubclasses = implementingClass.filterClassInfo(RelType.SUBCLASSES,
- /* strictWhitelist = */ !implementingClass.isExternalClass).reachableClasses;
+ /* strictAccept = */ !implementingClass.isExternalClass).reachableClasses;
allImplementingClasses.addAll(implementingSubclasses);
}
return new ClassInfoList(allImplementingClasses, implementingClasses.directlyRelatedClasses,
@@ -1489,38 +1955,45 @@ public ClassInfoList getClassesImplementing() {
* @return the list of annotations and meta-annotations on this class.
*/
public ClassInfoList getAnnotations() {
- if (!scanResult.scanSpec.enableAnnotationInfo) {
- throw new IllegalArgumentException("Please call ClassGraph#enableAnnotationInfo() before #scan()");
- }
+ synchronized (this) {
+ if (annotationsRef != null) {
+ return annotationsRef;
+ }
+
+ if (!scanResult.scanSpec.enableAnnotationInfo) {
+ throw new IllegalArgumentException("Please call ClassGraph#enableAnnotationInfo() before #scan()");
+ }
- // Get all annotations on this class
- final ReachableAndDirectlyRelatedClasses annotationClasses = this.filterClassInfo(RelType.CLASS_ANNOTATIONS,
- /* strictWhitelist = */ false);
- // Check for any @Inherited annotations on superclasses
- Set inheritedSuperclassAnnotations = null;
- for (final ClassInfo superclass : getSuperclasses()) {
- for (final ClassInfo superclassAnnotation : superclass.filterClassInfo(RelType.CLASS_ANNOTATIONS,
- /* strictWhitelist = */ false).reachableClasses) {
- // Check if any of the meta-annotations on this annotation are @Inherited,
- // which causes an annotation to annotate a class and all of its subclasses.
- if (superclassAnnotation != null && superclassAnnotation.isInherited) {
- // superclassAnnotation has an @Inherited meta-annotation
- if (inheritedSuperclassAnnotations == null) {
- inheritedSuperclassAnnotations = new LinkedHashSet<>();
+ // Get all annotations on this class
+ final ReachableAndDirectlyRelatedClasses annotationClasses = this
+ .filterClassInfo(RelType.CLASS_ANNOTATIONS, /* strictAccept = */ false);
+ // Check for any @Inherited annotations on superclasses
+ Set inheritedSuperclassAnnotations = null;
+ for (final ClassInfo superclass : getSuperclasses()) {
+ for (final ClassInfo superclassAnnotation : superclass.filterClassInfo(RelType.CLASS_ANNOTATIONS,
+ /* strictAccept = */ false).reachableClasses) {
+ // Check if any of the meta-annotations on this annotation are @Inherited,
+ // which causes an annotation to annotate a class and all of its subclasses.
+ if (superclassAnnotation != null && superclassAnnotation.isInherited) {
+ // superclassAnnotation has an @Inherited meta-annotation
+ if (inheritedSuperclassAnnotations == null) {
+ inheritedSuperclassAnnotations = new LinkedHashSet<>();
+ }
+ inheritedSuperclassAnnotations.add(superclassAnnotation);
}
- inheritedSuperclassAnnotations.add(superclassAnnotation);
}
}
- }
- if (inheritedSuperclassAnnotations == null) {
- // No inherited superclass annotations
- return new ClassInfoList(annotationClasses, /* sortByName = */ true);
- } else {
- // Merge inherited superclass annotations and annotations on this class
- inheritedSuperclassAnnotations.addAll(annotationClasses.reachableClasses);
- return new ClassInfoList(inheritedSuperclassAnnotations, annotationClasses.directlyRelatedClasses,
- /* sortByName = */ true);
+ if (inheritedSuperclassAnnotations == null) {
+ // No inherited superclass annotations
+ annotationsRef = new ClassInfoList(annotationClasses, /* sortByName = */ true);
+ } else {
+ // Merge inherited superclass annotations and annotations on this class
+ inheritedSuperclassAnnotations.addAll(annotationClasses.reachableClasses);
+ annotationsRef = new ClassInfoList(inheritedSuperclassAnnotations,
+ annotationClasses.directlyRelatedClasses, /* sortByName = */ true);
+ }
+ return annotationsRef;
}
}
@@ -1543,7 +2016,7 @@ private ClassInfoList getFieldOrMethodAnnotations(final RelType relType) {
+ "Info() and " + "#enableAnnotationInfo() before #scan()");
}
final ReachableAndDirectlyRelatedClasses fieldOrMethodAnnotations = this.filterClassInfo(relType,
- /* strictWhitelist = */ false, ClassType.ANNOTATION);
+ /* strictAccept = */ false, ClassType.ANNOTATION);
final Set fieldOrMethodAnnotationsAndMetaAnnotations = new LinkedHashSet<>(
fieldOrMethodAnnotations.reachableClasses);
return new ClassInfoList(fieldOrMethodAnnotationsAndMetaAnnotations,
@@ -1555,22 +2028,26 @@ private ClassInfoList getFieldOrMethodAnnotations(final RelType relType) {
*
* @param relType
* One of {@link RelType#CLASSES_WITH_FIELD_ANNOTATION},
- * {@link RelType#CLASSES_WITH_METHOD_ANNOTATION} or
- * {@link RelType#CLASSES_WITH_METHOD_PARAMETER_ANNOTATION}.
+ * {@link RelType#CLASSES_WITH_NONPRIVATE_FIELD_ANNOTATION},
+ * {@link RelType#CLASSES_WITH_METHOD_ANNOTATION},
+ * {@link RelType#CLASSES_WITH_NONPRIVATE_METHOD_ANNOTATION},
+ * {@link RelType#CLASSES_WITH_METHOD_PARAMETER_ANNOTATION}, or
+ * {@link RelType#CLASSES_WITH_NONPRIVATE_METHOD_PARAMETER_ANNOTATION}.
* @return A list of classes that have a declared method with this annotation or meta-annotation, or the empty
* list if none.
*/
private ClassInfoList getClassesWithFieldOrMethodAnnotation(final RelType relType) {
- final boolean isField = relType == RelType.CLASSES_WITH_FIELD_ANNOTATION;
+ final boolean isField = relType == RelType.CLASSES_WITH_FIELD_ANNOTATION
+ || relType == RelType.CLASSES_WITH_NONPRIVATE_FIELD_ANNOTATION;
if (!(isField ? scanResult.scanSpec.enableFieldInfo : scanResult.scanSpec.enableMethodInfo)
|| !scanResult.scanSpec.enableAnnotationInfo) {
throw new IllegalArgumentException("Please call ClassGraph#enable" + (isField ? "Field" : "Method")
+ "Info() and " + "#enableAnnotationInfo() before #scan()");
}
final ReachableAndDirectlyRelatedClasses classesWithDirectlyAnnotatedFieldsOrMethods = this
- .filterClassInfo(relType, /* strictWhitelist = */ !isExternalClass);
+ .filterClassInfo(relType, /* strictAccept = */ !isExternalClass);
final ReachableAndDirectlyRelatedClasses annotationsWithThisMetaAnnotation = this.filterClassInfo(
- RelType.CLASSES_WITH_ANNOTATION, /* strictWhitelist = */ !isExternalClass, ClassType.ANNOTATION);
+ RelType.CLASSES_WITH_ANNOTATION, /* strictAccept = */ !isExternalClass, ClassType.ANNOTATION);
if (annotationsWithThisMetaAnnotation.reachableClasses.isEmpty()) {
// This annotation does not meta-annotate another annotation that annotates a method
return new ClassInfoList(classesWithDirectlyAnnotatedFieldsOrMethods, /* sortByName = */ true);
@@ -1582,7 +2059,7 @@ private ClassInfoList getClassesWithFieldOrMethodAnnotation(final RelType relTyp
for (final ClassInfo metaAnnotatedAnnotation : annotationsWithThisMetaAnnotation.reachableClasses) {
allClassesWithAnnotatedOrMetaAnnotatedFieldsOrMethods
.addAll(metaAnnotatedAnnotation.filterClassInfo(relType,
- /* strictWhitelist = */ !metaAnnotatedAnnotation.isExternalClass).reachableClasses);
+ /* strictAccept = */ !metaAnnotatedAnnotation.isExternalClass).reachableClasses);
}
return new ClassInfoList(allClassesWithAnnotatedOrMetaAnnotatedFieldsOrMethods,
classesWithDirectlyAnnotatedFieldsOrMethods.directlyRelatedClasses, /* sortByName = */ true);
@@ -1600,25 +2077,56 @@ private ClassInfoList getClassesWithFieldOrMethodAnnotation(final RelType relTyp
* none.
*/
public AnnotationInfoList getAnnotationInfo() {
- if (!scanResult.scanSpec.enableAnnotationInfo) {
- throw new IllegalArgumentException("Please call ClassGraph#enableAnnotationInfo() before #scan()");
+ synchronized (this) {
+ if (annotationInfoRef != null) {
+ return annotationInfoRef;
+ }
+
+ if (!scanResult.scanSpec.enableAnnotationInfo) {
+ throw new IllegalArgumentException("Please call ClassGraph#enableAnnotationInfo() before #scan()");
+ }
+
+ annotationInfoRef = AnnotationInfoList.getIndirectAnnotations(annotationInfo, this);
+ return annotationInfoRef;
}
- return AnnotationInfoList.getIndirectAnnotations(annotationInfo, this);
}
/**
- * Get a the named non-{@link Repeatable} annotation on this class, or null if the class does not have the named
- * annotation. (Use {@link #getAnnotationInfoRepeatable(String)} for {@link Repeatable} annotations.)
+ * Get a the non-{@link Repeatable} annotation on this class, or null if the class does not have the annotation.
+ * (Use {@link #getAnnotationInfoRepeatable(String)} for {@link Repeatable} annotations.)
*
*
* Also handles the {@link Inherited} meta-annotation, which causes an annotation to annotate a class and all of
* its subclasses.
*
*
+ * Note that if you need to get multiple annotations, it is faster to call {@link #getAnnotationInfo()}, and
+ * then get the annotations from the returned {@link AnnotationInfoList}, so that the returned list doesn't have
+ * to be built multiple times.
+ *
+ * @param annotation
+ * The annotation.
+ * @return An {@link AnnotationInfo} object representing the annotation on this class, or null if the class does
+ * not have the annotation.
+ */
+ public AnnotationInfo getAnnotationInfo(final Class extends Annotation> annotation) {
+ Assert.isAnnotation(annotation);
+ return getAnnotationInfo(annotation.getName());
+ }
+
+ /**
+ * Get a the named non-{@link Repeatable} annotation on this class, or null if the class does not have the named
+ * annotation. (Use {@link #getAnnotationInfoRepeatable(String)} for {@link Repeatable} annotations.)
+ *
+ *
+ * Also handles the {@link Inherited} meta-annotation, which causes an annotation to annotate a class and all of
+ * its subclasses.
+ *
+ *
* Note that if you need to get multiple named annotations, it is faster to call {@link #getAnnotationInfo()},
* and then get the named annotations from the returned {@link AnnotationInfoList}, so that the returned list
* doesn't have to be built multiple times.
- *
+ *
* @param annotationName
* The annotation name.
* @return An {@link AnnotationInfo} object representing the named annotation on this class, or null if the
@@ -1629,18 +2137,41 @@ public AnnotationInfo getAnnotationInfo(final String annotationName) {
}
/**
- * Get a the named {@link Repeatable} annotation on this class, or the empty list if the class does not have the
- * named annotation.
+ * Get a the {@link Repeatable} annotation on this class, or the empty list if the class does not have the
+ * annotation.
*
*
* Also handles the {@link Inherited} meta-annotation, which causes an annotation to annotate a class and all of
* its subclasses.
*
*
+ * Note that if you need to get multiple annotations, it is faster to call {@link #getAnnotationInfo()}, and
+ * then get the annotations from the returned {@link AnnotationInfoList}, so that the returned list doesn't have
+ * to be built multiple times.
+ *
+ * @param annotation
+ * The annotation.
+ * @return An {@link AnnotationInfoList} of all instances of the annotation on this class, or the empty list if
+ * the class does not have the annotation.
+ */
+ public AnnotationInfoList getAnnotationInfoRepeatable(final Class extends Annotation> annotation) {
+ Assert.isAnnotation(annotation);
+ return getAnnotationInfoRepeatable(annotation.getName());
+ }
+
+ /**
+ * Get a the named {@link Repeatable} annotation on this class, or the empty list if the class does not have the
+ * named annotation.
+ *
+ *
+ * Also handles the {@link Inherited} meta-annotation, which causes an annotation to annotate a class and all of
+ * its subclasses.
+ *
+ *
* Note that if you need to get multiple named annotations, it is faster to call {@link #getAnnotationInfo()},
* and then get the named annotations from the returned {@link AnnotationInfoList}, so that the returned list
* doesn't have to be built multiple times.
- *
+ *
* @param annotationName
* The annotation name.
* @return An {@link AnnotationInfoList} of all instances of the named annotation on this class, or the empty
@@ -1660,17 +2191,19 @@ public AnnotationParameterValueList getAnnotationDefaultParameterValues() {
if (!scanResult.scanSpec.enableAnnotationInfo) {
throw new IllegalArgumentException("Please call ClassGraph#enableAnnotationInfo() before #scan()");
}
- if (!isAnnotation) {
+ if (!isAnnotation()) {
throw new IllegalArgumentException("Class is not an annotation: " + getName());
}
- if (annotationDefaultParamValues == null) {
- return AnnotationParameterValueList.EMPTY_LIST;
- }
- if (!annotationDefaultParamValuesHasBeenConvertedToPrimitive) {
- annotationDefaultParamValues.convertWrapperArraysToPrimitiveArrays(this);
- annotationDefaultParamValuesHasBeenConvertedToPrimitive = true;
+ synchronized (this) {
+ if (annotationDefaultParamValues == null) {
+ return AnnotationParameterValueList.EMPTY_LIST;
+ }
+ if (!annotationDefaultParamValuesHasBeenConvertedToPrimitive) {
+ annotationDefaultParamValues.convertWrapperArraysToPrimitiveArrays(this);
+ annotationDefaultParamValuesHasBeenConvertedToPrimitive = true;
+ }
+ return annotationDefaultParamValues;
}
- return annotationDefaultParamValues;
}
/**
@@ -1684,13 +2217,10 @@ public ClassInfoList getClassesWithAnnotation() {
if (!scanResult.scanSpec.enableAnnotationInfo) {
throw new IllegalArgumentException("Please call ClassGraph#enableAnnotationInfo() before #scan()");
}
- if (!isAnnotation) {
- throw new IllegalArgumentException("Class is not an annotation: " + getName());
- }
// Get classes that have this annotation
final ReachableAndDirectlyRelatedClasses classesWithAnnotation = this
- .filterClassInfo(RelType.CLASSES_WITH_ANNOTATION, /* strictWhitelist = */ !isExternalClass);
+ .filterClassInfo(RelType.CLASSES_WITH_ANNOTATION, /* strictAccept = */ !isExternalClass);
if (isInherited) {
// If this is an inherited annotation, add into the result all subclasses of the annotated classes.
@@ -1715,7 +2245,7 @@ public ClassInfoList getClassesWithAnnotation() {
*/
ClassInfoList getClassesWithAnnotationDirectOnly() {
return new ClassInfoList(
- this.filterClassInfo(RelType.CLASSES_WITH_ANNOTATION, /* strictWhitelist = */ !isExternalClass),
+ this.filterClassInfo(RelType.CLASSES_WITH_ANNOTATION, /* strictAccept = */ !isExternalClass),
/* sortByName = */ true);
}
@@ -1801,13 +2331,14 @@ private MethodInfoList getMethodInfo(final String methodName, final boolean getN
// Implement method/constructor overriding
final MethodInfoList methodInfoList = new MethodInfoList();
final Set> nameAndTypeDescriptorSet = new HashSet<>();
- for (final ClassInfo ci : getOverrideOrder()) {
- for (final MethodInfo mi : ci.getDeclaredMethodInfo(methodName, getNormalMethods, getConstructorMethods,
+ for (final ClassInfo ci : getMethodOverrideOrder()) {
+ // Constructors are not inherited from superclasses
+ boolean shouldGetConstructorMethods = ci == this && getConstructorMethods;
+ for (final MethodInfo mi : ci.getDeclaredMethodInfo(methodName, getNormalMethods, shouldGetConstructorMethods,
getStaticInitializerMethods)) {
- // If method/constructor has not been overridden by method of same name and type descriptor
- if (nameAndTypeDescriptorSet
- .add(new SimpleEntry<>(mi.getName(), mi.getTypeDescriptor().toString()))) {
- // Add method/constructor to output order
+ // If method has not been overridden by method of same name and type descriptor
+ if (nameAndTypeDescriptorSet.add(new SimpleEntry<>(mi.getName(), mi.getTypeDescriptorStr()))) {
+ // Add method to output order
methodInfoList.add(mi);
}
}
@@ -2129,23 +2660,46 @@ public ClassInfoList getMethodParameterAnnotations() {
}
/**
- * Get all classes that have this class as a method annotation.
+ * Get all classes that have this class as a method annotation, and their subclasses, if the method is
+ * non-private.
*
* @return A list of classes that have a declared method with this annotation or meta-annotation, or the empty
* list if none.
*/
public ClassInfoList getClassesWithMethodAnnotation() {
- return getClassesWithFieldOrMethodAnnotation(RelType.CLASSES_WITH_METHOD_ANNOTATION);
+ // Get all classes that have a method annotated or meta-annotated with this annotation
+ final Set classesWithMethodAnnotation = new HashSet<>(
+ getClassesWithFieldOrMethodAnnotation(RelType.CLASSES_WITH_METHOD_ANNOTATION));
+ // Add subclasses of all classes with a method that is non-privately annotated or meta-annotated with
+ // this annotation (non-private methods are inherited)
+ for (final ClassInfo classWithNonprivateMethodAnnotationOrMetaAnnotation : //
+ getClassesWithFieldOrMethodAnnotation(RelType.CLASSES_WITH_NONPRIVATE_METHOD_ANNOTATION)) {
+ classesWithMethodAnnotation.addAll(classWithNonprivateMethodAnnotationOrMetaAnnotation.getSubclasses());
+ }
+ return new ClassInfoList(classesWithMethodAnnotation,
+ new HashSet<>(getClassesWithMethodAnnotationDirectOnly()), /* sortByName = */ true);
}
/**
- * Get all classes that have this class as a method parameter annotation.
+ * Get all classes that have this class as a method parameter annotation, and their subclasses, if the method is
+ * non-private.
*
* @return A list of classes that have a declared method with a parameter that is annotated with this annotation
* or meta-annotation, or the empty list if none.
*/
public ClassInfoList getClassesWithMethodParameterAnnotation() {
- return getClassesWithFieldOrMethodAnnotation(RelType.CLASSES_WITH_METHOD_PARAMETER_ANNOTATION);
+ // Get all classes that have a method annotated or meta-annotated with this annotation
+ final Set classesWithMethodParameterAnnotation = new HashSet<>(
+ getClassesWithFieldOrMethodAnnotation(RelType.CLASSES_WITH_METHOD_PARAMETER_ANNOTATION));
+ // Add subclasses of all classes with a method that is non-privately annotated or meta-annotated with
+ // this annotation (non-private methods are inherited)
+ for (final ClassInfo classWithNonprivateMethodParameterAnnotationOrMetaAnnotation : //
+ getClassesWithFieldOrMethodAnnotation(RelType.CLASSES_WITH_NONPRIVATE_METHOD_PARAMETER_ANNOTATION)) {
+ classesWithMethodParameterAnnotation
+ .addAll(classWithNonprivateMethodParameterAnnotationOrMetaAnnotation.getSubclasses());
+ }
+ return new ClassInfoList(classesWithMethodParameterAnnotation,
+ new HashSet<>(getClassesWithMethodParameterAnnotationDirectOnly()), /* sortByName = */ true);
}
/**
@@ -2155,8 +2709,20 @@ public ClassInfoList getClassesWithMethodParameterAnnotation() {
* the requested method annotation, or the empty list if none.
*/
ClassInfoList getClassesWithMethodAnnotationDirectOnly() {
- return new ClassInfoList(this.filterClassInfo(RelType.CLASSES_WITH_METHOD_ANNOTATION,
- /* strictWhitelist = */ !isExternalClass), /* sortByName = */ true);
+ return new ClassInfoList(
+ this.filterClassInfo(RelType.CLASSES_WITH_METHOD_ANNOTATION, /* strictAccept = */ !isExternalClass),
+ /* sortByName = */ true);
+ }
+
+ /**
+ * Get the classes that have this class as a direct method parameter annotation.
+ *
+ * @return A list of classes that declare methods with parameters that are directly annotated (i.e. are not
+ * meta-annotated) with the requested method annotation, or the empty list if none.
+ */
+ ClassInfoList getClassesWithMethodParameterAnnotationDirectOnly() {
+ return new ClassInfoList(this.filterClassInfo(RelType.CLASSES_WITH_METHOD_PARAMETER_ANNOTATION,
+ /* strictAccept = */ !isExternalClass), /* sortByName = */ true);
}
// -------------------------------------------------------------------------------------------------------------
@@ -2176,7 +2742,7 @@ ClassInfoList getClassesWithMethodAnnotationDirectOnly() {
* {@link IllegalArgumentException}.
*
*
- * By default only returns information for public methods, unless {@link ClassGraph#ignoreFieldVisibility()} was
+ * By default only returns information for public fields, unless {@link ClassGraph#ignoreFieldVisibility()} was
* called before the scan.
*
* @return the list of FieldInfo objects for visible fields declared by this class, or the empty list if no
@@ -2205,7 +2771,7 @@ public FieldInfoList getDeclaredFieldInfo() {
* {@link IllegalArgumentException}.
*
*
- * By default only returns information for public methods, unless {@link ClassGraph#ignoreFieldVisibility()} was
+ * By default only returns information for public fields, unless {@link ClassGraph#ignoreFieldVisibility()} was
* called before the scan.
*
* @return the list of FieldInfo objects for visible fields of this class or its superclases, or the empty list
@@ -2220,7 +2786,7 @@ public FieldInfoList getFieldInfo() {
// Implement field overriding
final FieldInfoList fieldInfoList = new FieldInfoList();
final Set fieldNameSet = new HashSet<>();
- for (final ClassInfo ci : getOverrideOrder()) {
+ for (final ClassInfo ci : getFieldOverrideOrder()) {
for (final FieldInfo fi : ci.getDeclaredFieldInfo()) {
// If field has not been overridden by field of same name
if (fieldNameSet.add(fi.getName())) {
@@ -2232,6 +2798,48 @@ public FieldInfoList getFieldInfo() {
return fieldInfoList;
}
+ /**
+ * Get the enum constants of an enum class.
+ *
+ * @return All enum constants of an enum class as a list of {@link FieldInfo} objects (enum constants are stored
+ * as fields in Java classes).
+ */
+ public FieldInfoList getEnumConstants() {
+ if (!isEnum()) {
+ throw new IllegalArgumentException("Class " + getName() + " is not an enum");
+ }
+ return getFieldInfo().filter(new FieldInfoFilter() {
+ @Override
+ public boolean accept(final FieldInfo fieldInfo) {
+ return fieldInfo.isEnum();
+ }
+ });
+ }
+
+ /**
+ * Get the enum constants of an enum class.
+ *
+ * @return All enum constants of an enum class as a list of objects of the same type as the enum.
+ */
+ public List