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/workflows/ci.yml b/.github/workflows/ci.yml
index 3ef408cde..d87baada3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,11 +15,11 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
- java: [ '8', '11', '12', '13', '14', '15', '16' ]
+ java: [ '8', '11', '13', '15', '16', '17', '18', '19' ]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up JDK
- uses: actions/setup-java@v2
+ uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: ${{ matrix.java }}
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/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
deleted file mode 100644
index c32394f14..000000000
--- a/.mvn/wrapper/MavenWrapperDownloader.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2007-present the original author or authors.
- *
- * Licensed 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.
- */
-import java.net.*;
-import java.io.*;
-import java.nio.channels.*;
-import java.util.Properties;
-
-public class MavenWrapperDownloader {
-
- private static final String WRAPPER_VERSION = "0.5.5";
- /**
- * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
- */
- private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
- + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
-
- /**
- * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
- * use instead of the default one.
- */
- private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
- ".mvn/wrapper/maven-wrapper.properties";
-
- /**
- * Path where the maven-wrapper.jar will be saved to.
- */
- private static final String MAVEN_WRAPPER_JAR_PATH =
- ".mvn/wrapper/maven-wrapper.jar";
-
- /**
- * Name of the property which should be used to override the default download url for the wrapper.
- */
- private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
-
- public static void main(String args[]) {
- System.out.println("- Downloader started");
- File baseDirectory = new File(args[0]);
- System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
-
- // If the maven-wrapper.properties exists, read it and check if it contains a custom
- // wrapperUrl parameter.
- File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
- String url = DEFAULT_DOWNLOAD_URL;
- if(mavenWrapperPropertyFile.exists()) {
- FileInputStream mavenWrapperPropertyFileInputStream = null;
- try {
- mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
- Properties mavenWrapperProperties = new Properties();
- mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
- url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
- } catch (IOException e) {
- System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
- } finally {
- try {
- if(mavenWrapperPropertyFileInputStream != null) {
- mavenWrapperPropertyFileInputStream.close();
- }
- } catch (IOException e) {
- // Ignore ...
- }
- }
- }
- System.out.println("- Downloading from: " + url);
-
- File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
- if(!outputFile.getParentFile().exists()) {
- if(!outputFile.getParentFile().mkdirs()) {
- System.out.println(
- "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
- }
- }
- System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
- try {
- downloadFileFromURL(url, outputFile);
- System.out.println("Done");
- System.exit(0);
- } catch (Throwable e) {
- System.out.println("- Error downloading");
- e.printStackTrace();
- System.exit(1);
- }
- }
-
- private static void downloadFileFromURL(String urlString, File destination) throws Exception {
- if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
- String username = System.getenv("MVNW_USERNAME");
- char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
- Authenticator.setDefault(new Authenticator() {
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(username, password);
- }
- });
- }
- URL website = new URL(urlString);
- ReadableByteChannel rbc;
- rbc = Channels.newChannel(website.openStream());
- FileOutputStream fos = new FileOutputStream(destination);
- fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
- fos.close();
- rbc.close();
- }
-
-}
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100644
index 0d5e64988..000000000
Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 642d572ce..44f3cf2c1 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,2 +1,2 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
+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 0e6b5e6db..8b3ccf423 100644
--- a/.project
+++ b/.project
@@ -1,23 +1,34 @@
- ClassGraph
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.m2e.core.maven2Builder
-
-
-
-
-
- org.eclipse.m2e.core.maven2Nature
- org.eclipse.jdt.core.javanature
-
+ ClassGraph
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jdt.core.javanature
+
+
+
+ 1700088758021
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
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/README.md b/README.md
index f2293fffb..7f48fb220 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# ClassGraph
-
+
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,7 +15,7 @@ ClassGraph is an uber-fast parallelized classpath scanner and module scanner for
[](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)
@@ -28,7 +28,7 @@ ClassGraph is an uber-fast parallelized classpath scanner and module scanner for
[](https://github.com/classgraph/classgraph/blob/master/LICENSE)
-| ClassGraph is now fully stable. This project adheres to the **[Zero Bugs Commitment](https://github.com/classgraph/classgraph/blob/master/Zero-Bugs-Commitment.md)**. |
+| ClassGraph is stable and mature, and has a low bug report rate, despite being used by hundreds of projects. |
|-----------------------------|
### ClassGraph vs. Java Introspection
@@ -104,6 +104,35 @@ Replace `X.Y.Z` below with the latest [release number](https://github.com/classg
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.**
+
+To use one of these libraries:
+
+* 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).
+
+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.**
+
### 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).
@@ -171,12 +200,13 @@ Some other classpath scanning mechanisms include:
* [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) 2020 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:
diff --git a/Zero-Bugs-Commitment.md b/Zero-Bugs-Commitment.md
deleted file mode 100644
index 399949ea3..000000000
--- a/Zero-Bugs-Commitment.md
+++ /dev/null
@@ -1,77 +0,0 @@
-# The Zero Bugs Commitment
-
-This project adheres to the **Zero Bugs Commitment** (`#ZeroBugs`).
-This is a commitment to practice *responsible software engineering*,
-*proactive community participation*, and *positive community engagement*,
-with a goal of keeping the count of known or open bugs at zero, while
-respecting and cultivating contributions.
-
-**As developers of this project, we pledge that:**
-
-## (1) We will prioritize fixing bugs over implementing new features.
-
-*(Motivation: It is human nature to be much more interested in building new
-things than doing the hard work to fix old, broken things.)*
-
-💡 We pledge, wherever reasonable, to prioritize fixing known bugs above
-implementing new features, with the goal of **keeping the count of known or
-open bugs at zero**.
-
-## (2) We will take responsibility for code we have written or contributed to.
-
-*(Motivation: As attention shifts between projects, it is difficult to return
-to work on old code. This can lead to bit rot.)*
-
-💡 We pledge to take long-term responsibility for any significant code we
-create or contribute to, fixing problems and updating code as necessary to
-prevent bit rot. If we can no longer fulfill this responsibility, we will
-find someone else who can assume the responsibility for our code.
-
-Note that this is about taking *personal responsibility* for our own work, not
-about who has *official maintainership* for a project or piece of code.
-
-## (3) We will be responsive during the bugfixing process.
-
-*(Motivation: It is easy to delay responding to a bug report, a bug comment or
-a request until the issue becomes forgotten or obsolete. This is the unfortunate
-end state of a significant proportion of bug reports filed across the open
-source ecosystem.)*
-
-💡 We pledge to be responsive to bug reports, comments and requests currently
-open in our project's bug tracker, and to be proactive in resolving problems
-as quickly as practical.
-
-## (4) We will be respectful and inclusive.
-
-*(Motivation: Open source communities have been known to reject halting but
-earnest efforts of new contributors. Bug trackers are also full of pet
-complaints and bugs closed as `#WONTFIX`, without a significant attempt to
-understand core issues, or to find a solution or compromise.)*
-
-💡 We pledge to cultivate contributions and growth in our community by
-welcoming, encouraging, and helping users who offer contributions;
-by striving to listen to the needs and requests of community members;
-and by trying to find a reasonable solution or middle ground when there is
-a disagreement.
-
----
-
-**THIS DOCUMENT IS IN THE PUBLIC DOMAIN**
-
-You are strongly encouraged to share this commitment, to make this commitment
-yourself for software that you develop or maintain, and to encourage others to
-do the same.
-
-**To sign this pledge**, you can add the following wording to your project
-homepage, linking to this document, or to your own copy or your own version of
-this document:
-
-| **This project adheres to the [Zero Bugs Commitment](https://github.com/classgraph/classgraph/blob/master/Zero-Bugs-Commitment.md).** |
-|-----------------------------|
-
-You may modify or redistribute this document at will without restriction.
-However, please leave a record of your changes below.
-
-#### Version history:
-
-0.1: Original version (author: Luke Hutchison)
diff --git a/mvnw b/mvnw
index 41c0f0c23..e9cf8d330 100755
--- a/mvnw
+++ b/mvnw
@@ -19,292 +19,277 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven Start Up Batch script
-#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
+# Apache Maven Wrapper startup batch script, version 3.3.3
#
# Optional ENV vars
# -----------------
-# M2_HOME - location of maven2's installed home dir
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# 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
# ----------------------------------------------------------------------------
-if [ -z "$MAVEN_SKIP_RC" ] ; then
-
- if [ -f /etc/mavenrc ] ; then
- . /etc/mavenrc
- fi
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
- if [ -f "$HOME/.mavenrc" ] ; then
- . "$HOME/.mavenrc"
- fi
+# 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
-fi
+# 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"
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-mingw=false
-case "`uname`" in
- CYGWIN*) cygwin=true ;;
- MINGW*) mingw=true;;
- Darwin*) darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
- else
- export JAVA_HOME="/Library/Java/Home"
+ 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
- ;;
-esac
-
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
+ 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
-fi
-
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
+}
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- 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
+}
- saveddir=`pwd`
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
- M2_HOME=`dirname "$PRG"`/..
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
+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:]'
+}
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
+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
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
+# 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"
+}
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
fi
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
- if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
- else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
- fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-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
-if [ -z "$JAVACMD" ] ; then
- 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"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- else
- JAVACMD="`which java`"
- fi
+# 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
-if [ ! -x "$JAVACMD" ] ; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
+mkdir -p -- "${MAVEN_HOME%/*}"
-if [ -z "$JAVA_HOME" ] ; then
- echo "Warning: JAVA_HOME environment variable is not set."
+# 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
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+# 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
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
+# 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 "$1" ]
- then
- echo "Path not specified to find_maven_basedir"
- return 1
- fi
+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
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ] ; do
- if [ -d "$wdir"/.mvn ] ; then
- basedir=$wdir
- break
+# 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
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
+ 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
- # end of workaround
- done
- echo "${basedir}"
-}
-
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
+ 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
-}
-
-BASE_DIR=`find_maven_basedir "$(pwd)"`
-if [ -z "$BASE_DIR" ]; then
- exit 1;
fi
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found .mvn/wrapper/maven-wrapper.jar"
- 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
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
- fi
- if [ -n "$MVNW_REPOURL" ]; then
- jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
- else
- jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
- fi
- while IFS="=" read key value; do
- case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
- esac
- done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Downloading from: $jarUrl"
- fi
- wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
- if $cygwin; then
- wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
- fi
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
- if command -v wget > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found wget ... using wget"
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget "$jarUrl" -O "$wrapperJarPath"
- else
- wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
- fi
- elif command -v curl > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found curl ... using curl"
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl -o "$wrapperJarPath" "$jarUrl" -f
- else
- curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
- fi
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
- else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Falling back to using Java to download"
- fi
- javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
- # For Cygwin, switch paths to Windows format before running javac
- if $cygwin; then
- javaClass=`cygpath --path --windows "$javaClass"`
- fi
- if [ -e "$javaClass" ]; then
- if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Compiling MavenWrapperDownloader.java ..."
- fi
- # Compiling the Java class
- ("$JAVA_HOME/bin/javac" "$javaClass")
- fi
- if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- # Running the downloader
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Running MavenWrapperDownloader.java ..."
- fi
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
- fi
- fi
- fi
+# 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
-##########################################################################################
-# End of extension
-##########################################################################################
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-if [ "$MVNW_VERBOSE" = true ]; then
- echo $MAVEN_PROJECTBASEDIR
-fi
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
- [ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+# 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
-# Provide a "standardized" way to retrieve the CLI args that will
-# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
-export MAVEN_CMD_LINE_ARGS
+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
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+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"
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index 86115719e..3fd2be860 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -1,182 +1,189 @@
-@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 Maven Start Up Batch script
-@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
-@REM
-@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
-@REM ----------------------------------------------------------------------------
-
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
-if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo.
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo.
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
-
-FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
-)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- if "%MVNW_VERBOSE%" == "true" (
- echo Found %WRAPPER_JAR%
- )
-) else (
- if not "%MVNW_REPOURL%" == "" (
- SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
- )
- if "%MVNW_VERBOSE%" == "true" (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %DOWNLOAD_URL%
- )
-
- powershell -Command "&{"^
- "$webclient = new-object System.Net.WebClient;"^
- "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
- "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
- "}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
- "}"
- if "%MVNW_VERBOSE%" == "true" (
- echo Finished downloading %WRAPPER_JAR%
- )
-)
-@REM End of extension
-
-@REM Provide a "standardized" way to retrieve the CLI args that will
-@REM work with both Windows and non-Windows executions.
-set MAVEN_CMD_LINE_ARGS=%*
-
-%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
-if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%" == "on" pause
-
-if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
-
-exit /B %ERROR_CODE%
+<# : 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 5fa4b55c9..f19d6afaf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,576 +1,566 @@
- 4.0.0
+ 4.0.0
- io.github.classgraph
- classgraph
- 4.8.106-SNAPSHOT
- ClassGraph
+ io.github.classgraph
+ classgraph
+ 4.8.184
+ ClassGraph
- The uber-fast, ultra-lightweight classpath and module 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
- ClassGraph
- https://github.com/classgraph
-
-
+
+
+ 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
- classgraph-4.8.105
-
+
+ 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
+
+
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+ io.github.toolfactory
+ narcissus
+ 1.0.11
+ true
+
-
-
-
-
- org.junit.jupiter
- junit-jupiter
- 5.7.1
- test
-
-
- org.openjdk.jmh
- jmh-core
- 1.29
- test
-
-
- org.openjdk.jmh
- jmh-generator-annprocess
- 1.29
- test
-
-
- org.assertj
- assertj-core
- 3.19.0
- test
-
-
- javax.enterprise
- cdi-api
- 2.0
- test
-
-
- org.ops4j.pax.url
- pax-url-aether
- 2.6.1
- test
-
-
- org.slf4j
- slf4j-api
- 2.0.0-alpha1
- test
-
-
- org.slf4j
- slf4j-jdk14
- 2.0.0-alpha1
- test
-
-
- org.hibernate.javax.persistence
- hibernate-jpa-2.1-api
- 1.0.2.Final
- test
-
-
- com.google.jimfs
- jimfs
- 1.2
- test
-
+
+
+ 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.eclipse.jdt
+ org.eclipse.jdt.annotation
+ 2.3.0
+ provided
+
+
-
-
-
-
-
- org.eclipse.jdt
- org.eclipse.jdt.annotation
- 2.2.600
- provided
-
-
+
+
+
+
+
+
+
+ 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.maven.plugins
- maven-enforcer-plugin
- 3.0.0-M3
-
-
- org.apache.maven.plugins
- maven-resources-plugin
- 3.2.0
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.8.1
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.0.0-M5
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
- 3.2.0
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.2.0
-
-
- org.apache.maven.plugins
- maven-antrun-plugin
- 3.0.0
-
-
- org.apache.maven.plugins
- maven-source-plugin
- 3.2.1
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 3.2.0
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.6
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.8
-
-
- org.apache.maven.plugins
- maven-release-plugin
- 3.0.0-M4
-
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.3.2
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 3.1.1
+
-
-
- org.apache.maven.plugins
- maven-clean-plugin
- 3.1.0
-
-
- org.apache.maven.plugins
- maven-deploy-plugin
- 3.0.0-M1
-
-
- org.apache.maven.plugins
- maven-install-plugin
- 3.0.0-M1
-
-
- org.apache.maven.plugins
- maven-site-plugin
- 3.9.1
-
-
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.8.0
+ true
+
+ central
+ true
+
+
+
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
-
-
- org.codehaus.mojo
- animal-sniffer-enforcer-rule
- 1.20
-
-
-
-
-
- enforce-versions
- validate
-
- enforce
-
-
-
-
- [3.6.3,)
-
-
-
-
-
-
- check-signatures
- compile
-
- enforce
-
-
-
-
-
- org.codehaus.mojo.signature
- java17
- 1.0
-
-
-
-
-
-
-
+
+
+
+
+ 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-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-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-antrun-plugin
-
-
-
-
-
-
-
-
-
-
- add-module-info-to-jar
- package
-
- run
-
-
-
-
-
-
-
-
-
-
-
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+
+
+
+
+
+
+
+
+
+
+ add-module-info-to-jar
+ package
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ add-modular-javadoc
+ verify
+
+ run
+
+
+
+
+
+
+
+
+
+
+
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- UTF-8
-
-
-
-
-
-
-
-
-
-
-
- 7
- 7
-
- 8
- 8
- false
-
-
-
- default-testCompile
- test-compile
-
- testCompile
-
-
- UTF-8
-
- 8
- 8
-
-
-
-
+
+
+ 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.apache.maven.plugins
- maven-surefire-plugin
-
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ ${surefireArgLine}
+
+
-
-
- org.codehaus.mojo
- build-helper-maven-plugin
-
-
- add-test-source
- generate-test-sources
-
- add-test-source
-
-
-
- src/test/perf
-
-
-
-
-
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-test-source
+ generate-test-sources
+
+ add-test-source
+
+
+
+ src/test/perf
+
+
+
+
+
-
-
- 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,jdk.internal.misc;resolution:="optional",sun.misc;resolution:="optional",sun.nio.ch;resolution:="optional"
-
-
- true
-
-
-
-
+
+
+ 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
+
+
+
+
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- attach-sources
- package
-
- jar-no-fork
-
-
-
-
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+ package
+
+ jar-no-fork
+
+
+
+
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
- attach-javadocs
- package
-
- jar
-
-
- 8
- ${javadoc.html.version}
- all
- nonapi.*
- public
-
-
-
-
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+ package
+
+ jar
+
+
+ 8
+ ${javadoc.html.version}
+ all
+ nonapi.*
+ public
+
+
+
+
+
+
+
-
-
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- true
-
- ossrh
- https://oss.sonatype.org/
- true
- 10
-
-
+
+
+
+ jdk9plus
+
+ [9,)
+
+
+ -html5
+
+
+ true
+
+
+
+ jdk17plus
+
+ [17,)
+
+
+ --enable-native-access=ALL-UNNAMED
+
+
-
-
- org.apache.maven.plugins
- maven-release-plugin
-
- deploy
- deploy
- true
-
- release
-
- -Prelease
-
-
-
-
-
-
-
-
-
- jdk9plus
-
- -html5
-
-
- true
-
-
- [9,)
-
-
-
-
-
- release
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
-
-
- --pinentry-mode
- loopback
-
- ${gpg.keyname}
- ${gpg.keyname}
-
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
-
-
-
+
+
+ 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 d3b3392e4..3ac4343ac 100644
--- a/src/main/java/io/github/classgraph/AnnotationClassRef.java
+++ b/src/main/java/io/github/classgraph/AnnotationClassRef.java
@@ -107,9 +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 ((ArrayTypeSignature) typeSignature).loadClass(ignoreExceptions);
+ return typeSignature.loadClass(ignoreExceptions);
} else {
throw new IllegalArgumentException("Got unexpected type " + typeSignature.getClass().getName()
+ " for ref type signature: " + typeDescriptorStr);
@@ -142,7 +142,7 @@ protected String getClassName() {
} else if (typeSignature instanceof ClassRefTypeSignature) {
className = ((ClassRefTypeSignature) typeSignature).getFullyQualifiedClassName();
} else if (typeSignature instanceof ArrayTypeSignature) {
- className = ((ArrayTypeSignature) typeSignature).getClassName();
+ className = typeSignature.getClassName();
} else {
throw new IllegalArgumentException("Got unexpected type " + typeSignature.getClass().getName()
+ " for ref type signature: " + typeDescriptorStr);
@@ -214,6 +214,7 @@ protected void toString(final boolean useSimpleNames, final StringBuilder buf) {
// }
// }
- buf.append(/* prefix + */ getTypeSignature().toString(useSimpleNames) + ".class");
+ /* 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 104526630..4b855a260 100644
--- a/src/main/java/io/github/classgraph/AnnotationEnumValue.java
+++ b/src/main/java/io/github/classgraph/AnnotationEnumValue.java
@@ -118,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);
}
}
diff --git a/src/main/java/io/github/classgraph/AnnotationInfo.java b/src/main/java/io/github/classgraph/AnnotationInfo.java
index a619edaa2..356f1a41a 100644
--- a/src/main/java/io/github/classgraph/AnnotationInfo.java
+++ b/src/main/java/io/github/classgraph/AnnotationInfo.java
@@ -40,8 +40,8 @@
import java.util.Map.Entry;
import java.util.Set;
+import nonapi.io.github.classgraph.reflection.ReflectionUtils;
import nonapi.io.github.classgraph.utils.LogNode;
-import nonapi.io.github.classgraph.utils.ReflectionUtils;
/** Holds metadata about a specific annotation instance on a class, method, method parameter or field. */
public class AnnotationInfo extends ScanResultObject implements Comparable, HasName {
@@ -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);
+ }
+
// -------------------------------------------------------------------------------------------------------------
/**
@@ -274,7 +288,7 @@ public ClassInfo getClassInfo() {
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. */
@@ -337,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;
diff --git a/src/main/java/io/github/classgraph/AnnotationInfoList.java b/src/main/java/io/github/classgraph/AnnotationInfoList.java
index 03b60fb5b..79a0151d7 100644
--- a/src/main/java/io/github/classgraph/AnnotationInfoList.java
+++ b/src/main/java/io/github/classgraph/AnnotationInfoList.java
@@ -28,6 +28,7 @@
*/
package io.github.classgraph;
+import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.util.ArrayList;
import java.util.HashSet;
@@ -36,6 +37,7 @@
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;
@@ -344,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.
*
diff --git a/src/main/java/io/github/classgraph/AnnotationParameterValue.java b/src/main/java/io/github/classgraph/AnnotationParameterValue.java
index ddbbec91d..caa3db213 100644
--- a/src/main/java/io/github/classgraph/AnnotationParameterValue.java
+++ b/src/main/java/io/github/classgraph/AnnotationParameterValue.java
@@ -252,7 +252,7 @@ private static void toString(final Object val, final boolean useSimpleNames, fin
} else if (val instanceof ScanResultObject) {
((ScanResultObject) val).toString(useSimpleNames, buf);
} else {
- buf.append(val.toString());
+ buf.append(val);
}
}
diff --git a/src/main/java/io/github/classgraph/ArrayTypeSignature.java b/src/main/java/io/github/classgraph/ArrayTypeSignature.java
index 1774904f1..6565c9e14 100644
--- a/src/main/java/io/github/classgraph/ArrayTypeSignature.java
+++ b/src/main/java/io/github/classgraph/ArrayTypeSignature.java
@@ -244,7 +244,7 @@ public Class> loadElementClass(final boolean ignoreExceptions) {
elementClassRef = elementTypeSignature.loadClass(ignoreExceptions);
} else {
// Fallback, if scanResult is not set
- final String elementTypeName = ((ClassRefTypeSignature) elementTypeSignature).getClassName();
+ final String elementTypeName = elementTypeSignature.getClassName();
try {
elementClassRef = Class.forName(elementTypeName);
} catch (final Throwable t) {
diff --git a/src/main/java/io/github/classgraph/ClassGraph.java b/src/main/java/io/github/classgraph/ClassGraph.java
index b712ab5eb..d77c06a8b 100644
--- a/src/main/java/io/github/classgraph/ClassGraph.java
+++ b/src/main/java/io/github/classgraph/ClassGraph.java
@@ -30,6 +30,7 @@
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;
@@ -48,6 +49,7 @@
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;
@@ -80,15 +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() {
+ reflectionUtils = new ReflectionUtils();
// Initialize ScanResult, if this is the first call to ClassGraph constructor
- ScanResult.init();
+ ScanResult.init(reflectionUtils);
}
/**
@@ -155,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;
}
@@ -640,10 +683,6 @@ public ClassGraph acceptPackages(final String... packageNames) {
enableClassInfo();
for (final String packageName : packageNames) {
final String packageNameNormalized = AcceptReject.normalizePackageOrClassName(packageName);
- if (packageNameNormalized.startsWith("!") || packageNameNormalized.startsWith("-")) {
- throw new IllegalArgumentException(
- "This style of accepting/rejecting is no longer supported: " + packageNameNormalized);
- }
// Accept package
scanSpec.packageAcceptReject.addToAccept(packageNameNormalized);
final String path = AcceptReject.packageNameToPath(packageNameNormalized);
@@ -913,16 +952,13 @@ public ClassGraph blacklistPaths(final String... paths) {
*
*
* @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 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 = AcceptReject.normalizePackageOrClassName(className);
// Accept the class itself
scanSpec.classAcceptReject.addToAccept(classNameNormalized);
@@ -941,8 +977,7 @@ public ClassGraph acceptClasses(final String... classNames) {
* Use {@link #acceptClasses(String...)} instead.
*
* @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).
* @return this (for method chaining).
* @deprecated Use {@link #acceptClasses(String...)} instead.
*/
@@ -959,16 +994,13 @@ public ClassGraph whitelistClasses(final String... classNames) {
* N.B. Automatically calls {@link #enableClassInfo()}.
*
* @param classNames
- * The fully-qualified names of classes to reject (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 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 = AcceptReject.normalizePackageOrClassName(className);
scanSpec.classAcceptReject.addToReject(classNameNormalized);
scanSpec.classfilePathAcceptReject
@@ -981,8 +1013,7 @@ public ClassGraph rejectClasses(final String... classNames) {
* Use {@link #rejectClasses(String...)} instead.
*
* @param classNames
- * The fully-qualified names of classes to reject (using '.' as a separator). May not include a glob
- * wildcard ({@code '*'}).
+ * The fully-qualified names of classes to reject (using '.' as a separator).
* @return this (for method chaining).
* @deprecated Use {@link #rejectClasses(String...)} instead.
*/
@@ -1080,7 +1111,7 @@ private void acceptOrRejectLibOrExtJars(final boolean accept, final String... ja
}
if (jarLeafName.contains("*")) {
// Compare wildcarded pattern against all jars in lib and ext dirs
- final Pattern pattern = AcceptReject.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);
@@ -1381,14 +1412,44 @@ public ClassGraph setMaxBufferedJarRAMSize(final int maxBufferedJarRAMSize) {
/**
* 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;
+ }
+
// -------------------------------------------------------------------------------------------------------------
/**
@@ -1465,7 +1526,7 @@ public void run() {
try {
// Call scanner, but ignore the returned ScanResult
new Scanner(/* performScan = */ true, scanSpec, executorService, numParallelTasks,
- scanResultProcessor, failureHandler, topLevelLog).call();
+ scanResultProcessor, failureHandler, reflectionUtils, topLevelLog).call();
} catch (final InterruptedException | CancellationException | ExecutionException e) {
// Call failure handler
failureHandler.onFailure(e);
@@ -1493,7 +1554,7 @@ private Future scanAsync(final boolean performScan, final ExecutorSe
final int numParallelTasks) {
try {
return executorService.submit(new Scanner(performScan, scanSpec, executorService, numParallelTasks,
- /* scanResultProcessor = */ null, /* failureHandler = */ null, topLevelLog));
+ /* 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).
@@ -1728,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 67fa18e2a..7067119e5 100644
--- a/src/main/java/io/github/classgraph/ClassGraphClassLoader.java
+++ b/src/main/java/io/github/classgraph/ClassGraphClassLoader.java
@@ -32,8 +32,8 @@
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
-import java.nio.ByteBuffer;
import java.security.ProtectionDomain;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
@@ -89,8 +89,7 @@ public class ClassGraphClassLoader extends ClassLoader {
// 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 context classloader of the class
- // that called ClassGraph)
+ // Try the null classloader first (this will default to the bootstrap class loader)
environmentClassLoaderDelegationOrder = new LinkedHashSet<>();
environmentClassLoaderDelegationOrder.add(null);
@@ -98,9 +97,7 @@ public class ClassGraphClassLoader extends ClassLoader {
final ClassLoader[] envClassLoaderOrder = scanResult.getClassLoaderOrderRespectingParentDelegation();
if (envClassLoaderOrder != null) {
// Try environment classloaders
- for (final ClassLoader envClassLoader : envClassLoaderOrder) {
- environmentClassLoaderDelegationOrder.add(envClassLoader);
- }
+ environmentClassLoaderDelegationOrder.addAll(Arrays.asList(envClassLoaderOrder));
}
}
@@ -264,25 +261,18 @@ protected Class> findClass(final String 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
- try {
- final ByteBuffer resourceByteBuffer = resource.read();
- // 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, resourceByteBuffer, (ProtectionDomain) null);
- } finally {
- resource.close();
- }
+ // 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);
} catch (final LinkageError e) {
if (linkageError == null) {
linkageError = e;
}
- } finally {
- resource.close();
}
}
}
@@ -350,7 +340,7 @@ public URL getResource(final String path) {
}
}
- // Finally if the above attempts fail, try retrieving resource from ScanResult.
+ // 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()) {
@@ -387,11 +377,11 @@ public Enumeration getResources(final String path) throws IOException {
}
}
- // Finally if the above attempts fail, try retrieving resource from ScanResult.
+ // 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 Collections. emptyEnumeration();
+ return Collections.emptyEnumeration();
} else {
return new Enumeration() {
/** The idx. */
@@ -437,7 +427,7 @@ public InputStream getResourceAsStream(final String path) {
}
}
- // Finally if the above attempts fail, try opening resource from ScanResult.
+ // 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()) {
diff --git a/src/main/java/io/github/classgraph/ClassInfo.java b/src/main/java/io/github/classgraph/ClassInfo.java
index 271c5aa7c..dfc691a2d 100644
--- a/src/main/java/io/github/classgraph/ClassInfo.java
+++ b/src/main/java/io/github/classgraph/ClassInfo.java
@@ -29,6 +29,7 @@
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;
@@ -50,12 +51,15 @@
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. */
@@ -88,6 +92,12 @@ public class ClassInfo extends ScanResultObject implements Comparable
/** 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;
@@ -135,7 +145,7 @@ public class ClassInfo extends ScanResultObject implements Comparable
AnnotationParameterValueList annotationDefaultParamValues;
/** The type annotation decorators for the {@link ClassTypeSignature} instance. */
- List typeAnnotationDecorators;
+ transient List typeAnnotationDecorators;
/**
* Names of classes referenced by this class in class refs and type signatures in the constant pool of the
@@ -164,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. */
@@ -450,6 +471,16 @@ void setIsRecord(final boolean isRecord) {
}
}
+ /**
+ * Set source file.
+ *
+ * @param sourceFile
+ * the source file
+ */
+ void setSourceFile(final String sourceFile) {
+ this.sourceFile = sourceFile;
+ }
+
/**
* Add {@link ClassTypeAnnotationDecorator} instances.
*
@@ -628,8 +659,7 @@ void addMethodInfo(final MethodInfoList methodInfoList, final Map filterClassInfo(final Collection classes, final ScanSpec scanSpec,
final boolean strictAccept, final ClassType... classTypes) {
if (classes == null) {
- return Collections. emptySet();
+ return Collections.emptySet();
}
boolean includeAllTypes = classTypes.length == 0;
boolean includeStandardClasses = false;
@@ -1186,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();
}
/**
@@ -1195,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);
}
/**
@@ -1213,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);
}
/**
@@ -1291,6 +1348,17 @@ 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());
+ }
+
/**
* Checks if this class extends the named superclass.
*
@@ -1348,6 +1416,18 @@ public boolean isImplementedInterface() {
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());
+ }
+
/**
* Checks whether this class implements the named interface.
*
@@ -1359,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.
*
@@ -1389,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;
}
@@ -1397,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.
*
@@ -1413,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.
*
@@ -1421,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;
}
@@ -1430,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);
@@ -1448,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;
}
@@ -1456,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.
*
@@ -1472,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.
@@ -1482,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;
}
@@ -1490,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.
*
@@ -1506,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.
*
@@ -1514,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;
}
@@ -1525,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
@@ -1533,32 +1698,106 @@ 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
@@ -1674,8 +1913,9 @@ public ClassInfoList getInterfaces() {
.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);
}
/**
@@ -1685,9 +1925,6 @@ 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, /* strictAccept = */ !isExternalClass);
@@ -1718,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;
+ }
- // 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<>();
+ 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, /* 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;
}
}
@@ -1833,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
@@ -1862,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
@@ -1896,14 +2194,16 @@ public AnnotationParameterValueList getAnnotationDefaultParameterValues() {
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;
}
/**
@@ -1917,9 +2217,6 @@ 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
@@ -2034,12 +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 method has not been overridden by method of same name and type descriptor
if (nameAndTypeDescriptorSet.add(new SimpleEntry<>(mi.getName(), mi.getTypeDescriptorStr()))) {
- // Add method/constructor to output order
+ // Add method to output order
methodInfoList.add(mi);
}
}
@@ -2487,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())) {
@@ -2499,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