Jandex is a space efficient Java class file indexer and offline reflection library. See the documentation.
Issues can be reported in GitHub Issues.
For discussions, use the SmallRye Google Group or the Quarkus Zulip chat.
Java Annotation Indexer
License: Apache License 2.0
Jandex is a space efficient Java class file indexer and offline reflection library. See the documentation.
Issues can be reported in GitHub Issues.
For discussions, use the SmallRye Google Group or the Quarkus Zulip chat.
Calling the same sequence of Jandex methods for the same set of JARs should yeld the same results (incl. the ordering of entries).
A PR follows.
When indexing multiple classes, during class signature parsing/resolution, the wrong bounds can be resolved for a type variable, because the indexer reuses the signature parser and the signature parser reuses resolved type variables between consecutive calls to the index method.
Reproduce with:
package com.example;
public interface WithMethodSignature {
public <E extends Runnable> E myMethod(E arg);
}
package com.example;
import java.util.Iterator;
public interface WithClassSignature<E extends Exception> extends Iterator<E /*what's the bound?*/ > {
}
package com.example;
import java.io.InputStream;
import org.assertj.core.api.Assertions;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import org.junit.jupiter.api.Test;
public class JandexTest {
@Test //passes
public void classSignatureOnly()
throws Exception {
verifyIndex(createIndex(WithClassSignature.class), WithClassSignature.class);
}
@Test //passes
public void classSignatureBeforeMethodSignature()
throws Exception {
verifyIndex(createIndex(WithClassSignature.class, WithMethodSignature.class), WithClassSignature.class);
}
@Test //fails
public void classSignatureAfterMethodSignature()
throws Exception {
verifyIndex(createIndex(WithMethodSignature.class, WithClassSignature.class), WithClassSignature.class);
}
private Index createIndex(Class<?>... types)
throws Exception {
Indexer indexer = new Indexer();
for (Class<?> type : types) { //order matters
String classNameAsResource = type.getName().replace('.', '/') + ".class";
try (InputStream fis = type.getClassLoader().getResourceAsStream(classNameAsResource);) {
indexer.index(fis);
}
}
Index index = indexer.complete();
return index;
}
private void verifyIndex(Index index, Class<?> generic) {
DotName bound = DotName.createSimple(((Class<?>) generic.getTypeParameters()[0].getBounds()[0]).getName());
ClassInfo indexedGeneric = index.getClassByName(DotName.createSimple(generic.getName()));
DotName indexedInterfaceBound = indexedGeneric.interfaceTypes().get(0).asParameterizedType().arguments().get(0).asTypeVariable()
.bounds().get(0).asClassType().name();
Assertions.assertThat(indexedInterfaceBound).isEqualTo(bound);
}
}
Hi,
I found this error while trying jruby + wildfly-swarm
https://github.com/helio-frota/wildfly-swarm-jruby-sinatra
2015-12-08 00:08:48,378 WARN [org.jboss.as.server.deployment] (MSC service thread 1-4) WFLYSRV0003: Could not index class jnr/x86asm/SEGMENT.class at /wildfly-swarm-jruby-sinatra.war/WEB-INF/lib/jnr-x86asm-1.0.2.jar: java.lang.NullPointerException
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.Type.equals(Type.java:358)
at org.jboss.jandex.StrongInternPool.eq(StrongInternPool.java:139)
at org.jboss.jandex.StrongInternPool.intern(StrongInternPool.java:247)
at org.jboss.jandex.NameTable.intern(NameTable.java:84)
at org.jboss.jandex.Indexer.parseType(Indexer.java:1317)
at org.jboss.jandex.Indexer.parseType(Indexer.java:1296)
at org.jboss.jandex.Indexer.processFieldInfo(Indexer.java:287)
at org.jboss.jandex.Indexer.index(Indexer.java:1445)
at org.jboss.as.server.deployment.annotation.ResourceRootIndexer.indexResourceRoot(ResourceRootIndexer.java:99)
at org.jboss.as.server.deployment.annotation.AnnotationIndexProcessor.deploy(AnnotationIndexProcessor.java:51)
at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:147)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
2015-12-08 00:08:48,380 WARN [org.jboss.as.server.deployment] (MSC service thread 1-4) WFLYSRV0003: Could not index class jnr/x86asm/Mem.class at /wildfly-swarm-jruby-sinatra.war/WEB-INF/lib/jnr-x86asm-1.0.2.jar: java.lang.NullPointerException
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.Type.equals(Type.java:358)
at java.util.Arrays.equals(Arrays.java:2829)
at org.jboss.jandex.StrongInternPool.eq(StrongInternPool.java:132)
at org.jboss.jandex.StrongInternPool.intern(StrongInternPool.java:247)
at org.jboss.jandex.NameTable.intern(NameTable.java:88)
at org.jboss.jandex.Indexer.intern(Indexer.java:1038)
at org.jboss.jandex.Indexer.processMethodInfo(Indexer.java:263)
at org.jboss.jandex.Indexer.index(Indexer.java:1446)
at org.jboss.as.server.deployment.annotation.ResourceRootIndexer.indexResourceRoot(ResourceRootIndexer.java:99)
at org.jboss.as.server.deployment.annotation.AnnotationIndexProcessor.deploy(AnnotationIndexProcessor.java:51)
at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:147)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
2015-12-08 00:08:48,382 WARN [org.jboss.as.server.deployment] (MSC service thread 1-4) WFLYSRV0003: Could not index class jnr/x86asm/CpuInfo$Vendor.class at /wildfly-swarm-jruby-sinatra.war/WEB-INF/lib/jnr-x86asm-1.0.2.jar: java.lang.NullPointerException
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.Type.equals(Type.java:358)
at java.util.Arrays.equals(Arrays.java:2829)
at org.jboss.jandex.StrongInternPool.eq(StrongInternPool.java:132)
at org.jboss.jandex.StrongInternPool.intern(StrongInternPool.java:247)
at org.jboss.jandex.NameTable.intern(NameTable.java:88)
at org.jboss.jandex.GenericSignatureParser.parseTypeList(GenericSignatureParser.java:365)
at org.jboss.jandex.GenericSignatureParser.parseTypeArguments(GenericSignatureParser.java:340)
at org.jboss.jandex.GenericSignatureParser.parseClassTypeSignature(GenericSignatureParser.java:311)
at org.jboss.jandex.GenericSignatureParser.parseClassSignature(GenericSignatureParser.java:249)
at org.jboss.jandex.Indexer.parseClassSignature(Indexer.java:944)
at org.jboss.jandex.Indexer.applySignatures(Indexer.java:960)
at org.jboss.jandex.Indexer.index(Indexer.java:1449)
at org.jboss.as.server.deployment.annotation.ResourceRootIndexer.indexResourceRoot(ResourceRootIndexer.java:99)
at org.jboss.as.server.deployment.annotation.AnnotationIndexProcessor.deploy(AnnotationIndexProcessor.java:51)
at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:147)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
2015-12-08 00:08:48,384 WARN [org.jboss.as.server.deployment] (MSC service thread 1-4) WFLYSRV0003: Could not index class jnr/x86asm/SerializerIntrinsics.class at /wildfly-swarm-jruby-sinatra.war/WEB-INF/lib/jnr-x86asm-1.0.2.jar: java.lang.NullPointerException
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.DotName.equals(DotName.java:293)
at org.jboss.jandex.Type.equals(Type.java:358)
at java.util.Arrays.equals(Arrays.java:2829)
at org.jboss.jandex.StrongInternPool.eq(StrongInternPool.java:132)
at org.jboss.jandex.StrongInternPool.intern(StrongInternPool.java:247)
at org.jboss.jandex.NameTable.intern(NameTable.java:88)
at org.jboss.jandex.Indexer.intern(Indexer.java:1038)
at org.jboss.jandex.Indexer.processMethodInfo(Indexer.java:263)
at org.jboss.jandex.Indexer.index(Indexer.java:1446)
at org.jboss.as.server.deployment.annotation.ResourceRootIndexer.indexResourceRoot(ResourceRootIndexer.java:99)
at org.jboss.as.server.deployment.annotation.AnnotationIndexProcessor.deploy(AnnotationIndexProcessor.java:51)
at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:147)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Current code throws:
java.lang.IllegalStateException: Unknown tag! pos=156 poolCount = 174
at org.jboss.jandex.Indexer.processConstantPool(Indexer.java:1563)
at org.jboss.jandex.Indexer.index(Indexer.java:1597)
This is the output of javap -v <classfile>
#157 = Dynamic #1:#156 // #1:$jacocoData:Ljava/lang/Object;
I have the following maven project: pom.xml
is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.mvysny.vaadin-jandex</groupId>
<artifactId>vaadin-jandex</artifactId>
<version>14.4.6-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<vaadin.version>14.4.6</vaadin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin</artifactId>
<optional>true</optional>
<exclusions>
<!-- Webjars are only needed when running in Vaadin 13 compatibility mode -->
<exclusion>
<groupId>com.vaadin.webjar</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.webjars.bowergithub.insites</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.webjars.bowergithub.polymer</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.webjars.bowergithub.polymerelements</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.webjars.bowergithub.vaadin</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.webjars.bowergithub.webcomponents</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<!-- in order to build Jandex index for Vaadin jars, we need to unpack them first -->
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>process-sources</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
</execution>
</executions>
<configuration>
<includeGroupIds>com.vaadin</includeGroupIds>
<!-- <outputDirectory>${project.build.directory}/classes</outputDirectory>-->
</configuration>
</plugin>
<plugin>
<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>1.0.8</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
<!-- phase is 'process-classes by default' -->
</execution>
</executions>
<configuration>
<fileSets>
<fileSet>
<directory>${project.build.directory}/dependency</directory>
</fileSet>
</fileSets>
</configuration>
</plugin>
</plugins>
</build>
</project>
Running mvn clean package
on this project will result in an empty Jandex index being produced (the jandex.idx
only has 18 bytes).
Running with mvn clean package -X
shows that Jandex plugin actually walks over the Vaadin classes, it simply won't include those in the resulting index:
...
[INFO] Indexed com.vaadin.flow.internal.BrowserLiveReload$Backend (0 annotations)
[INFO] Indexed com.vaadin.flow.internal.UsageStatistics$1 (0 annotations)
[INFO] Indexed com.vaadin.flow.internal.StateNode$1 (0 annotations)
[INFO] Indexed com.vaadin.flow.internal.UsageStatistics (0 annotations)
[INFO] Indexed com.vaadin.flow.internal.UsageStatistics$UsageEntry (0 annotations)
[INFO] Indexed com.vaadin.flow.internal.StateNode$FeatureSetKey (0 annotations)
[INFO] Indexed com.vaadin.flow.internal.ResponseWriter (1 annotations)
...
Issue #50 was flawed because we didn't want to index all methods calls, so I thought that clients could specify which calls we wanted to flag during indexing, but this doesn't work with packaged indexes.
Now, in Quarkus we have a double step to run transformers conditionally:
META-INF
Foo,Bar,Gee
This allows us to only transform classes that use a certain special feature.
Now, in quarkusio/quarkus#814 I want to add a transformer based on the usage of a Router
class, and could take the same road, but it would still require scanning the constant pool of every class. I would either create a new extension just for this, or add it to the existing resteasy extension, but every user would pay the ConstPool scanning.
And in quarkusio/quarkus#10929 I definitely don't want to create an extension for a single class, which should be provided by Quarkus Core. And I don't want to add it to Quarkus Core if it means scanning every application class ConstPool even if nobody uses this class. I can't use the marker file in this case.
Therefore, I think we can solve all issues if Jandex indexes CONSTANT_Class (7)
entries. Ultimately all method calls reference Fieldref/Methodref/InterfaceMethodref (https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.4.2) which reference CONSTANT_Class_info (https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.4.1).
I don't think this would add a huge size to the index, and would allow us to scan efficiently for classes which make calls to special classes, which would work for ORM with Panache, as well as the Reverse-Router and Logging with Panache use-cases without having to use marker files or even creating extensions for single classes just to have the marker.
I can work on this if you think this is the right idea.
WDYT @n1hility @stuartwdouglas @mkouba ?
For the 3.0 release, we want to move Jandex to SmallRye (and use the automated release process SmallRye has). Ideally, all Jandex repos (this one, the typeannotation-test
, and the Maven plugin) would be brought into one (covered by #122).
The groupId
would change from org.jboss[.jandex]
to io.smallrye
(or io.smallrye.jandex
, but I'd probably prefer the first one :-) ). The artifactId
s or packages wouldn't change (so packages would still be org.jboss.jandex
), as that would be too big of a break.
EDIT: as part of this, also rename the master
branch to main
.
I know Jandex is mostly about annotations but I was wondering if we could extend it a bit to keep an index of packages, subpackages and classes in these packages.
When building native applications with external libraries, it can happen that all the content of a package needs to be registered for reflection and that it is the only way to really be sure we do not forget anything and future-proof enough.
Listing classes manually is very cumbersome and brittle for large APIs.
Given the index is a closed world, it sounds feasible, right?
Thoughts?
We had an issue a few weeks back smallrye/smallrye-open-api#716 requesting information to be utilized from a @Retention(RetentionPolicy.CLASS)
annotation. I'm curious if there is any interest in having runtime invisible annotations present in an index. I suppose the original reason for leaving them out was to more closely match reflection APIs, correct? Is this something that could be a candidate for version 3?
I have found this error in the Wildfly server logs while deploying my application. Finally, I was able to create a simple test that reproduces this issue. Note that I use Lombok in my project, so it is likely that this is not an issue of Jandex per se, but it might be a problem of Lombok code generation, or some other problem.
To demonstrate, let's assume we have the following class:
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public class SampleEntity {
private Set<@TypeAnnotation String> userRoles;
public Set<String> getUserRoles() {
return userRoles;
}
}
where the @TypeAnnotation
is just an empty annotation. The test project is avaliable here: jandex-sample-project.zip. A simple test I have created just tries to index this class by Jandex.
When running mvn clean verify
, the following exception occurs:
java.lang.IllegalArgumentException: Not a parameterized type!
at org.jboss.jandex.Type.asParameterizedType(Type.java:228)
at org.jboss.jandex.Indexer.resolveTypePath(Indexer.java:767)
at org.jboss.jandex.Indexer.resolveTypeAnnotation(Indexer.java:727)
at org.jboss.jandex.Indexer.resolveTypeAnnotations(Indexer.java:612)
at org.jboss.jandex.Indexer.index(Indexer.java:1604)
at sk.tosix.sample.IndexerTest.test(IndexerTest.java:17)
I have played a bit with this, and it seems that the following causes the error to disappear:
@TypeAnnotation
from the userRoles declaration makes the error disappear.@AllArgsConstructor
annotation and creating the constructor by hand (basically the same as delomboking the class) makes the error disappear.It is possible that it is Lombok to blame here, but as I am no expert in bytecode interpretation and as it is Jandex that throws the error, I am asking you guys for help :)
As a result, the boolean values is ignored in equals()
, i.e. it returns always true
for two boolean annotation values with the same name.
The following code fails with IllegalArgumentException:
public class Main {
public static <T> T[] producer(T... ts){
return ts;
}
public static <T> void consumer(Supplier<? extends T> objs){
}
public static void main(String[] args) throws IOException {
consumer(Main::producer);
new Indexer().index(Main.class.getClassLoader().getResourceAsStream("com/test/Main.class"));
}
}
The full stack trace is:
Exception in thread "main" java.lang.IllegalArgumentException: Expected character '^' at position 2
at org.jboss.jandex.GenericSignatureParser.expect(GenericSignatureParser.java:262)
at org.jboss.jandex.GenericSignatureParser.parseMethodSignature(GenericSignatureParser.java:298)
at org.jboss.jandex.Indexer.parseMethodSignature(Indexer.java:971)
at org.jboss.jandex.Indexer.applySignatures(Indexer.java:958)
at org.jboss.jandex.Indexer.index(Indexer.java:1449)
at com.test.Main.main(Main.java:29)
Here is a simple test project, hope this helps.
I didn't find an easy and reliable way to mimic these methods from java.lang.Class
. Note that java.lang.Class.ANNOTATION
and friends are private.
Caused by: java.io.UTFDataFormatException: encoded string too long: 68271 bytes
at java.io.DataOutputStream.writeUTF(DataOutputStream.java:364)
at java.io.DataOutputStream.writeUTF(DataOutputStream.java:323)
at org.jboss.jandex.IndexWriterV2.writeStringTable(IndexWriterV2.java:216)
at org.jboss.jandex.IndexWriterV2.write(IndexWriterV2.java:193)
at org.jboss.jandex.IndexWriter.write(IndexWriter.java:107)
at org.jboss.jandex.IndexWriter.write(IndexWriter.java:74)
Neither the pom.xml
not the README
state what the license of this software is.
My wildlfy 10.0.0.0 server (which I can not change since an application called i2b2 needs to work with) is sending me the next error:
[javax.enterprise.resource.webcontainer.jsf.application] (ServerService Thread Pool -- 73) java.lang.NoSuchMethodException: org.jboss.as.jsf.injection.JandexAnnotationProvider.(javax.servlet.ServletContext, com.sun.faces.spi.AnnotationProvider): java.lang.NoSuchMethodException: org.jboss.as.jsf.injection.JandexAnnotationProvider.(javax.servlet.ServletContext, com.sun.faces.spi.AnnotationProvider)
at java.lang.Class.getConstructor0(Class.java:3082) [rt.jar:1.8.0_192]
at java.lang.Class.getDeclaredConstructor(Class.java:2178) [rt.jar:1.8.0_192]
at com.sun.faces.spi.ServiceFactoryUtils.getProviderFromEntry(ServiceFactory.java:83) [jsf-impl-2.2.12-jbossorg-2.jar:2.2.12-jbossorg-2]
at com.sun.faces.spi.AnnotationProviderFactory.createAnnotationProvider(AnnotationProviderFactory.java:70) [jsf-impl-2.2.12-jbossorg-2.jar:2.2.12-jbossorg-2]
at com.sun.faces.config.ConfigManager$AnnotationScanTask.<init>(ConfigManager.java:931) [jsf-impl-2.2.12-jbossorg-2.jar:2.2.12-jbossorg-2]
at com.sun.faces.config.ConfigManager.initialize(ConfigManager.java:385) [jsf-impl-2.2.12-jbossorg-2.jar:2.2.12-jbossorg-2]
at com.sun.faces.config.ConfigureListener.contextInitialized(ConfigureListener.java:227) [jsf-impl-2.2.12-jbossorg-2.jar:2.2.12-jbossorg-2]
at io.undertow.servlet.core.ApplicationListeners.contextInitialized(ApplicationListeners.java:187) [undertow-servlet-1.3.23.Final.jar:1.3.23.Final]
at io.undertow.servlet.core.DeploymentManagerImpl.deploy(DeploymentManagerImpl.java:198) [undertow-servlet-1.3.23.Final.jar:1.3.23.Final]
at org.wildfly.extension.undertow.deployment.UndertowDeploymentService.startContext(UndertowDeploymentService.java:100)
at org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:82)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [rt.jar:1.8.0_192]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) [rt.jar:1.8.0_192]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [rt.jar:1.8.0_192]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [rt.jar:1.8.0_192]
at java.lang.Thread.run(Thread.java:748) [rt.jar:1.8.0_192]
at org.jboss.threads.JBossThread.run(JBossThread.java:320)
Can you please tell me what version of jandex I need to upload into my wildfly server to avoid this error? My server has jandex so I will truly need to upgrade it. If you can provide some instructions also this could be great (I am kind of new to wildfly and jandex)
thanks a lot
Jandex needs to be built and distributed on an older JVM than the bytecode it can read and needs to be tested with. Explore some form of setup where the test data classes (potentially including the tests if required) are in a separate module with a more recent JVM, the main jandex jar itself.
Ran into a case where scanning some libraries found an ArrayIndexOutOfBoundsException. This was based on 2.1.3.Final.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
at org.jboss.jandex.Indexer.updateTypeTarget(Indexer.java:841)
at org.jboss.jandex.Indexer.updateTypeTargets(Indexer.java:624)
at org.jboss.jandex.Indexer.index(Indexer.java:1603)
at test.Main.main(Main.java:14)
This occurred using a simple test program:
package test;
import java.io.IOException;
import java.io.InputStream;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
public class Main {
public static void main(String[] args) throws IOException {
Indexer indexer = new Indexer();
InputStream stream = Main.class.getClassLoader().getResourceAsStream("test/Broken.class");
indexer.index(stream);
Index index = indexer.complete();
}
}
and failed on this class:
package test;
import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
public class Broken {
public static <U extends @NonNull List<String>> U listOf() {
return null;
}
}
This was, obviously, from a much more complicated example, but I tried to reduce it down to the simplest case that still generated the exception.
As far as I could tell, the problem lies here:
case TYPE_PARAMETER_BOUND: {
TypeParameterBoundTypeTarget boundTarget = target.asTypeParameterBound();
type = getTypeParameters(enclosingTarget)[boundTarget.position()]
.asTypeVariable().boundArray()[boundTarget.boundPosition()];
break;
}
where the boundTarget.position is 1, and boundPosition is 1, but while the getTypeParameters returns a 2-element array, the second array (for boundPosition) is only a 1-element array, and thus, fails.
Unfortunately, I couldn't follow the code logic enough to track the underlying problem, so just reporting with all the data I did get.
Cheers.
If there is a nested class with simple name Test$
, e.g. org.jboss.jandex.test.IndexerTestCase$Test$
(note that although it's not recommended it's legal to use $
in a class name) then Indexer
decodes the DotName
as:
componentized true
innerClass true
local "" (id=108)
prefix DotName (id=111)
componentized true
innerClass true
local "Test" (id=117)
prefix DotName (id=86)
componentized true
innerClass false
local "test" (id=130)
prefix DotName (id=131)
...
The toString()
of the DotName
is org.jboss.jandex.test.IndexerTestCase$Test$
however the equals()
method does not match the following:
DotName org = DotName.createComponentized(null, "org");
DotName jboss = DotName.createComponentized(org, "jboss");
DotName jandex = DotName.createComponentized(jboss, "jandex");
DotName test = DotName.createComponentized(jandex, "test");
DotName indexerTestCase = DotName.createComponentized(test, "IndexerTestCase");
DotName testName = DotName.createComponentized(indexerTestCase, Test$.class.getSimpleName(), true);
whose toString()
is also org.jboss.jandex.test.IndexerTestCase$Test$
.
Maybe I'm missing something obvious.
Hello,
I try to use jandex to add annotation like:
@XmlAccessorType(XmlAccessType.FIELD)
So I use the following code:
classCreator.addAnnotation(AnnotationInstance.create(
DotName.createSimple(XmlAccessorType.class.getName()), null,
new AnnotationValue[]{
AnnotationValue.createEnumValue("value",
DotName.createSimple(XmlAccessType.class.getName()),
String.valueOf(XmlAccessType.FIELD))}));
but I try a lot of different value for string value: "FIELD", String.valueOf(XmlAccessType.FIELD), XmlAccessType.FIELD.toString(), XmlAccessType.FIELD.toString(), XmlAccessType.FIELD.name() but it never work.
AnnotationValue.createStringValue work but impossible to have a string value for a regular enum.
generated class has the annotation but without value:
@XmlAccessorType
public class Reply {
instead of
@XmlAccessorType(XmlAccessType.FIELD)
public class Reply {
So I think there is a bug with value of type string for enum. Why not used an object type for value instead of String?
Currently, to get the parameter type, we need to do something like:
methodParameterInfo.method().parameters().get(methodParameterInfo.position())
The issue with this is that parameters()
includes the synthetic parameters (confirmed by @FroMage ) whereas they are not counted in the result of position()
, leading to incorrect results if we have synthetic parameters.
@FroMage implemented a nice user friendly API to get the parameter name. We should do the same for the parameter type.
Inspired by the movement on #138...
The plug-in supports scanning .class
files.
Has there ever been thought to including .class
entries in other dependency JARs?
In this Helidon issue helidon-io/helidon#2729 referenced from #138, the use case is this: The application currently being built by maven (and for which the Jandex plug-in is executed) contains endpoints with MicroProfile OpenAPI annotations. Those endpoints happen to refer to POJO business classes defined in other dependencies which might or might not also bear OpenAPI annotations.
It would be great to have a way to create a single Jandex index (from one execution of the plug-in) that included not only information about the current project's classes but also selected classes from other dependencies, JARs which do not have their own Jandex indices.
Of course, one would want a way to identify which dependencies, which classes or packages within them, etc. to include in the scan.
A workaround, also mentioned in the Helidon issue, would be to extract the contents of selected other dependencies into temp directories and point fileset
configuration to the extracted contents. For the developer, though, it'd be simpler to be able to tell just the Jandex plug-in about those other dependencies and the relevant selection of packages/classes, rather than involve other plug-ins to extract their contents to disk first.
org.jboss.jandex.IndexView.getAnnotations(DotName)
currently does not return the instances of repeatable annotations and that's ok (repeatable annotations are compiled into the container annotations anyway). However, we should probably add some convenient method to handle repeatable annotations as well, ie. something similar to java.lang.reflect.AnnotatedElement.getAnnotationsByType(Class<T>)
. Of course, we can't modify the behavior of IndexView.getAnnotations()
due to backwards compatibility.
Example:
@Repeatable(Routes.class)
public @interface Route {
@interface Routes {
Route[] value();
}
}
// the boolean param can be used to specify whether to return repeatable annotations or not
Collection<AnnotationInstance> annotations = index.getAnnotations(DotName.createSimple(Route.class.getName()), true);
package org.jboss.jandex.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigInteger;
public class ClassBounds<V1 extends @ClassBounds.NonNull BigInteger,
V2 extends @ClassBounds.Nullable Runnable> {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface NonNull {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface Nullable {
}
}
Test class:
package org.jboss.jandex.test;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import org.junit.Test;
import java.io.IOException;
public class TestClassBounds {
@Test
public void classBounds() throws IOException {
Indexer indexer = new Indexer();
indexer.index(ClassBounds.class.getResourceAsStream("ClassBounds.class"));
Index index = indexer.complete();
System.out.println("subclasses");
index.printSubclasses();
System.out.println("annotations");
index.printAnnotations();
}
}
Actual error:
java.lang.ArrayIndexOutOfBoundsException: 1
at org.jboss.jandex.Indexer.updateTypeTarget(Indexer.java:847)
at org.jboss.jandex.Indexer.updateTypeTargets(Indexer.java:630)
at org.jboss.jandex.Indexer.index(Indexer.java:1618)
at org.jboss.jandex.test.TestClassBounds.classBounds(TestClassBounds.java:13)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
It looks like jandex misses something like openjdk/jdk@9d7f8bc
The Maven plugin could expose a configuration property for setting which index version to write. It would then call IndexWriter.write(Index, int)
instead of just IndexWriter.write(Index)
. Default would be unconfigured, which would lead to calling IndexWriter.write(Index)
and hence using the latest index version.
The purpose would be to decouple Jandex version bump from Jandex persistent index format version bump. Libraries that produce and distribute a Jandex index could decide to bump Jandex without bumping the persistent index format, so that the index remains readable by older Jandex versions.
On the other hand, there's clear desire to make libraries ship latest indices, so that new Jandex features can be used, so maybe we would only allow configuring a subset of possible index versions? Say, current and current - 1, something like that.
I didn't find an easy way to determine if a field/method is synthetic.
The answer could have been "use Modifier.isSynthetic()
" but unfortunately this method is package private.
I wonder if we should provide something in Jandex similar to what we have on reflection Field
/Method
?
Jandex historically used the JBoss.org JIRA (now Red Hat JIRA), but it has obviously moved to GitHub Issues. The JIRA project needs to be decommissioned to avoid confusion. There's just 6 open issues at the moment, and at least 1 has been fixed in 2.4.0.Final (JANDEX-41, module-info.class support), so it shouldn't be a big deal.
Hey Jason, etal. I started playing around again with using Jandex in Hibernate.
The thing I am still unsure of is how to manually build ClassInfo, MethodInfo, etc objects. For background, this is related to applying orm.xml
mapping overrides. I realize this is a unique use-case, but wondered if you know of anything else using Jandex in a similar fashion or thoughts on how to best accomplish this.
The main thing I run into is that I need to re-create all of the AnnotationTarget references, which seems to get messy
We're going to need to be able to ask Jandex for methods that call specified methods. It would be nice if we could tell Jandex to index all calls to Foo.bar()
(which could be a list of methods) so that later we can query this and do bytecode modification or compile-time validation only for the methods where those call are located.
While indexing, Jandex could look at the bytecode and record methods that call the set of specified methods we're after. Then we can query Jandex for all methods calling those methods.
jandex [-d] <index-file-name>
should output the version of the index.
Two PRs (#64, #75) already attempt to make the annotation access API more regular. I think this should be addressed holistically. In my opinion, all the annotation access methods should be present on AnnotationTarget
, so that all annotation targets expose uniform API. It would look roughly like this:
interface AnnotationTarget {
...
boolean hasAnnotation(DotName name);
AnnotationInstance annotation(DotName name);
Collection<AnnotationInstance> annotationWithRepeatable(DotName name, IndexView index);
Collection<AnnotationInstance> annotations();
Map<DotName, List<AnnotationInstance>> annotationsMap();
boolean hasDirectAnnotation(DotName name);
AnnotationInstance directAnnotation(DotName name);
Collection<AnnotationInstance> directAnnotationWithRepeatable(DotName name, IndexView index);
Collection<AnnotationInstance> directAnnotations();
Map<DotName, List<AnnotationInstance>> directAnnotationsMap();
}
The direct*
methods would return annotations that are present directly on the annotation target, while the generic methods would -- just like now -- return annotations present on the annotation target as well as nested annotation targets.
This would unfortunately be a breaking change, because ClassInfo
currently exposes a very different API than other annotation targets and there's a name collision.
Sample:
public interface EqualityComparer<T> {
boolean equal(T v1, T v2);
int hashCode(T t);
}
private static class ArrayEqualityComparer
implements EqualityComparer<@Nullable Object[]> {
@Override public boolean equal(@Nullable Object[] v1, @Nullable Object[] v2) {
return Arrays.deepEquals(v1, v2);
}
@Override public int hashCode(@Nullable Object[] t) {
return Arrays.deepHashCode(t);
}
}
Error (in jandex 2.0.3.Final and 2.2.2.Final):
Caused by: java.lang.IllegalArgumentException: Not an array type!
at org.jboss.jandex.Type.asArrayType(Type.java:254)
at org.jboss.jandex.Indexer.resolveTypePath(Indexer.java:621)
at org.jboss.jandex.Indexer.resolveTypeAnnotation(Indexer.java:593)
at org.jboss.jandex.Indexer.resolveTypeAnnotations(Indexer.java:478)
at org.jboss.jandex.Indexer.index(Indexer.java:1458)
at com.github.vlsi.jandex.JandexWork.execute(JandexWork.kt:52)
SmallRye's OpenAPI implementation uses Jandex. Not only for annotation processing but also to get general reflective class info. Unfortunately object fields are sorted by Jandex: https://github.com/wildfly/jandex/blob/61d1115e0a0f15dcfb2b0bbd082100f847963529/src/main/java/org/jboss/jandex/ClassInfo.java#L557
This ultimately alters the field order of all objects appearing in the openapi file, though in most cases declaration order is desired.
It would therefore be great to have an Indexer option to configure ClassInfo's field sorting behavior. Maybe for completeness also for methods.
Thanks!
Currently, it is hard to write tests since jandex
repository defaults to source=target=1.6
, and the test code that uses type annotations just does not compile.
It would be nice if there was a way to add test classes right into jandex
repository (e.g. via Maven toolchains or Gradle toolchains)
JUnit 4 is history. JUnit 5 FTW! :-)
Can you propose a way to index "jrt-fs.jar" please?
Or maybe have a JarFsIndexer
?
On my linux box: java -jar jandex.jar -m modules/org/slf4j/main/slf4j-api-1.5.10.jar
if (modify) {
jarFile.delete();
tmpCopy.renameTo(jarFile);
}
can lead to:
rename("/tmp/slf4j-api-1.5.10003664802446273229194jmp", "modules/org/slf4j/main/slf4j-api-1.5.10.jar") = -1 EXDEV (Invalid cross-device link)
a somewhat unusual file system layout makes such simple code break - file lost!!
It seems inconsistent to me.
If a class declares a recursive bound (which should be legal and often used e.g. in builders) such as interface Score<S extends Score<S>> {}
the type hierarchy contains an UnresolvedTypeVariable
. And according to the javadoc "This type will only occur as a result of a bug, or a non-compliant Java class file".
A test can be found here:
mkouba@2141db3
In fact, I'm not quite sure whether it's a bug or how to handle this correctly.
The error message is hard to understand, and the root cause is not clear.
What do you think if the error includes the class name or something else for making the exception actionable?
The current exception:
Caused by: java.lang.IllegalStateException: Required class information is missing
at org.jboss.jandex.Indexer.rebuildNestedType(Indexer.java:926)
at org.jboss.jandex.Indexer.resolveTypePath(Indexer.java:786)
at org.jboss.jandex.Indexer.resolveTypeAnnotation(Indexer.java:705)
at org.jboss.jandex.Indexer.resolveTypeAnnotations(Indexer.java:613)
at org.jboss.jandex.Indexer.index(Indexer.java:1602)
at org.hibernate.boot.archive.scan.spi.ClassFileArchiveEntryHandler.toClassDescriptor(ClassFileArchiveEntryHandler.java:64)
at org.hibernate.boot.archive.scan.spi.ClassFileArchiveEntryHandler.handleEntry(ClassFileArchiveEntryHandler.java:52)
My need is to get all interface wich extends another interface.
The issue is that index is class oriented. So I can get subclass or class which implement an interface. But I found not solution to get interface which inherit from another.
example
interface A{
}
interface B extends A{
}
interface C extends B{
}
goal is to be abble to get C and B.
I try to use getAllKnownImplementors but it skip interface (I have check source)
I try to do my own method with a recurssive method:
void produceRecursiveProxies(IndexView index,
DotName interfaceDN,
BuildProducer<NativeImageProxyDefinitionBuildItem> proxies) {
index.getKnownDirectImplementors(interfaceDN).stream()
.filter(classinfo -> Modifier.isInterface(classinfo.flags()))
.map(ClassInfo::name)
.forEach((className) -> {
proxies.produce(new NativeImageProxyDefinitionBuildItem(className.toString()));
produceRecursiveProxies(index, className, proxies);
});
}
but getKnownDirectImplementors seems to not return the interface but only class... not sure ?
Best is to have a new function in indexer for that.
getKnownSubInterface
java.lang.reflect.Field
has the method isEnumConstant
that uses the package private value Modifier.ENUM
. The value of the constant 0x4000
is defined in the class file format to indicate that a field is "declared as an element of an enum" (https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5-200-A.1).
It would be a nice convenience to implement a similar method on FieldInfo
checking for 0x4000
in the field's flags.
package org.jboss.jandex;
...
public final class FieldInfo implements AnnotationTarget {
...
static final int ENUM = 0x00004000;
....
public boolean isEnumConstant() {
return (flags() & ENUM) != 0;
}
....
}
The Indexer.index()
method returns ClassInfo
representing the just-indexed class. This precludes doing two-phase class processing, which is required to correctly resolve some recursive types. As a first step, Indexer.index()
should be changed to return void
-- indexed classes should only be accessible from a complete index.
I would like to be able to (efficiently) look up all the interfaces that extends a target interface.
Say for example that I have
public interface SomeInterface {
}
and there is an interface that extends is:
public interface SomeExtendingInterface extends SomeInterface {
}
Neither index.getAllKnownSubclasses()
nor index.getAllKnownImplementors()
covers the aforementioned usecase so I am forced do the following (inefficient) lookup:
List<ClassInfo> getAllInterfacesExtending(DotName target, IndexView index) {
List<ClassInfo> result = new ArrayList<>();
Collection<ClassInfo> knownClasses = index.getKnownClasses();
for (ClassInfo clazz : knownClasses) {
if (!Modifier.isInterface(clazz.flags())) {
continue;
}
List<DotName> interfaceNames = clazz.interfaceNames();
boolean found = false;
for (DotName interfaceName : interfaceNames) {
if (interfaceName.equals(target)) {
found = true;
break;
}
}
if (found) {
result.add(clazz);
}
}
return result;
}
AFAIK the current "standard" is to include index as META-INF/index.idx
.
However, that would result in collisions when dependencies are shaded, so it looks like jandex is not ready for adding indices to Java libraries yet :(
I think it would be helpful if there was a documented procedure to merge indices (and remap classnames) for shading dependencies (e.g. Resource Transformers for maven-shade-plugin
, Content Merging in com.github.johnrengelman.shadow
Gradle plugin, and so on).
In the evolution of Jakarta EE 8 to 9 there is a package name change complicates support for the now legacy type, even though there are no semantic differences. In a prototype of arc I was looking at trying to make the minimal changes to have arc support the jakarta.* package while also handling existing code to work. This resulted in duplicate DotName instances and checks like the following:
public static final DotName PRIORITY = create(jakarta.annotation.Priority.class);
public static final DotName PRIORITY_JAVAX = create("javax.annotation.Priority");
...
} else if (DotNames.PRIORITY.equals(annotation.name())
|| DotNames.PRIORITY_JAVAX.equals(annotation.name())) {
alternativePriority = annotation.value().asInt();
} else {
This issue is about whether an alias notion can effectively be added to jandex. It has been pointed out that:
It seems like what is needed is an alias registry in DotName that is populated by a new factory method, and that all instance would check for an alias.
The existing Jandex documentation consists of a README, which is even somewhat outdated, and a Javadoc. This needs to be improved quite a bit. I'd personally downplay the published Javadoc (it's enough you have access to it in an IDE) and instead focus on a user guide kind of thing. We could use the SmallRye documentation site (https://smallrye.io/docs/), as we plan to move to SmallRye anyway (#124).
As discussed here with Stuart and Guillaume:
https://quarkusio.zulipchat.com/#narrow/stream/187038-dev/topic/Convert.20Jandex.20ClassInfo.20to.20Class
quarkus extensions often have to convert a Jandex ClassInfo into a Class, which Jandex doesn't support (it's an API gap).
Now, converting it into a Class, does mean the Class gets instantiated, which means the user must be well aware of the cost of that side effect, but that seems obvious to me - otherwise ClassInfo would not exist.
The code itself:
public <T> Class<T> toClass() {
String className = name().toString();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
return (Class<T>) classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("The class (" + className
+ ") cannot be created.", e);
}
}
jandex is not capable of parsing the current guava release, guava-30.0-jre. It fails when indexing ClosingFuture.class.
Please see the stacktrace:
java -jar ~/.m2/repository/org/jboss/jandex/2.2.1.Final/jandex-2.2.1.Final.jar ~/.m2/repository/com/google/guava/guava/30.0-jre/guava-30.0-jre.jar
ERROR: Could not index com/google/common/util/concurrent/ClosingFuture.class: Class extends type annotation appeared on a non class target
java.lang.IllegalStateException
at org.jboss.jandex.IndexWriterV2$ReferenceTable.getReferenceEntry(IndexWriterV2.java:125)
at org.jboss.jandex.IndexWriterV2$ReferenceTable.positionOf(IndexWriterV2.java:132)
at org.jboss.jandex.IndexWriterV2.positionOf(IndexWriterV2.java:448)
at org.jboss.jandex.IndexWriterV2.writeTypeTargetFields(IndexWriterV2.java:381)
at org.jboss.jandex.IndexWriterV2.writeTypeTarget(IndexWriterV2.java:366)
at org.jboss.jandex.IndexWriterV2.writeAnnotationTarget(IndexWriterV2.java:335)
at org.jboss.jandex.IndexWriterV2.writeAnnotation(IndexWriterV2.java:319)
at org.jboss.jandex.IndexWriterV2.writeReferenceOrFull(IndexWriterV2.java:614)
at org.jboss.jandex.IndexWriterV2.writeClassEntry(IndexWriterV2.java:537)
at org.jboss.jandex.IndexWriterV2.writeClasses(IndexWriterV2.java:472)
at org.jboss.jandex.IndexWriterV2.write(IndexWriterV2.java:207)
at org.jboss.jandex.IndexWriter.write(IndexWriter.java:107)
at org.jboss.jandex.IndexWriter.write(IndexWriter.java:74)
at org.jboss.jandex.JarIndexer.createJarIndex(JarIndexer.java:193)
at org.jboss.jandex.JarIndexer.createJarIndex(JarIndexer.java:86)
at org.jboss.jandex.Main.getIndex(Main.java:87)
at org.jboss.jandex.Main.execute(Main.java:68)
at org.jboss.jandex.Main.main(Main.java:53)
I became aware of this problem because a deployment of mine is using this version of guava, and when I deploy the EAR in to JBoss EAP 7.3.2, a warning with a similar stacktrace is thrown. The version of jandex in the JBoss is jandex-2.1.2.Final-redhat-00001, however this problem still exists with the 2.2.1 release.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.