From c0f35f6ce45093c56985223d61b5230b109aed99 Mon Sep 17 00:00:00 2001 From: Ashish Thakur Date: Mon, 9 Feb 2026 13:50:29 +0530 Subject: [PATCH] feat: create metadata generator plugin --- CHANGELOG.md | 1 + graalvm-metadata-generator-plugin/pom.xml | 125 +++++++++ .../src/it/settings.xml | 55 ++++ .../src/it/simple-jackson-project/pom.xml | 70 +++++ .../src/main/java/io/fabric8/test/Person.java | 43 +++ .../it/simple-jackson-project/verify.groovy | 34 +++ .../plugin/GraalVmMetadataGeneratorMojo.java | 151 +++++++++++ .../graalvm/plugin/InclusionStrategy.java | 56 ++++ .../plugin/JandexReflectionScanner.java | 254 ++++++++++++++++++ .../plugin/ReflectionConfigGenerator.java | 102 +++++++ .../graalvm/plugin/ReflectionEntry.java | 139 ++++++++++ pom.xml | 27 ++ 12 files changed, 1057 insertions(+) create mode 100644 graalvm-metadata-generator-plugin/pom.xml create mode 100644 graalvm-metadata-generator-plugin/src/it/settings.xml create mode 100644 graalvm-metadata-generator-plugin/src/it/simple-jackson-project/pom.xml create mode 100644 graalvm-metadata-generator-plugin/src/it/simple-jackson-project/src/main/java/io/fabric8/test/Person.java create mode 100644 graalvm-metadata-generator-plugin/src/it/simple-jackson-project/verify.groovy create mode 100644 graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/GraalVmMetadataGeneratorMojo.java create mode 100644 graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/InclusionStrategy.java create mode 100644 graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/JandexReflectionScanner.java create mode 100644 graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionConfigGenerator.java create mode 100644 graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionEntry.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c0bf154958..e4cf20c13d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ #### New Features * Fix #7385: Support for Kubernetes v1.35 (Timbernetes) +* New graalvm-metadata-generator-plugin Maven plugin to automatically generate GraalVM native-image reflection metadata from Jandex indexes #### _**Note**_: Breaking changes diff --git a/graalvm-metadata-generator-plugin/pom.xml b/graalvm-metadata-generator-plugin/pom.xml new file mode 100644 index 0000000000..a99353e85f --- /dev/null +++ b/graalvm-metadata-generator-plugin/pom.xml @@ -0,0 +1,125 @@ + + + + 4.0.0 + + io.fabric8 + kubernetes-client-project + 7.6-SNAPSHOT + + + graalvm-metadata-generator-plugin + maven-plugin + Fabric8 :: GraalVM Metadata Generator Maven Plugin + Maven plugin to generate GraalVM native-image metadata from Jandex indexes + + + 11 + 11 + + + + + + org.apache.maven + maven-core + + + org.apache.maven.plugin-tools + maven-plugin-annotations + + + + + io.smallrye + jandex + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.assertj + assertj-core + test + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + + graalvm-metadata + + + + default-descriptor + process-classes + + + help-goal + + helpmojo + + + + + + org.apache.maven.plugins + maven-invoker-plugin + + ${project.build.directory}/it + true + src/it/settings.xml + ${project.build.directory}/local-repo + verify + true + ${skipTests} + true + + ${jandex.version} + ${jandex.version} + ${jackson.version} + + + + + integration-test + + install + run + + + + + + + diff --git a/graalvm-metadata-generator-plugin/src/it/settings.xml b/graalvm-metadata-generator-plugin/src/it/settings.xml new file mode 100644 index 0000000000..fc2dff7a0e --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/it/settings.xml @@ -0,0 +1,55 @@ + + + + + + it-repo + + true + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + diff --git a/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/pom.xml b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/pom.xml new file mode 100644 index 0000000000..47d72bb886 --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + io.fabric8.it + simple-jackson-project + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + + com.fasterxml.jackson.core + jackson-databind + @jackson.version@ + + + + + + + io.smallrye + jandex-maven-plugin + @jandex-maven-plugin.version@ + + + + jandex + + + + + + io.fabric8 + graalvm-metadata-generator-plugin + @project.version@ + + + + generate + + + + + + + diff --git a/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/src/main/java/io/fabric8/test/Person.java b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/src/main/java/io/fabric8/test/Person.java new file mode 100644 index 0000000000..6d3f6c6b2e --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/src/main/java/io/fabric8/test/Person.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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. + */ +package io.fabric8.test; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Person { + + @JsonProperty("name") + private String name; + + @JsonProperty("age") + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } +} diff --git a/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/verify.groovy b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/verify.groovy new file mode 100644 index 0000000000..74858ccd33 --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/it/simple-jackson-project/verify.groovy @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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 groovy.json.JsonSlurper + +def configFile = new File(basedir, 'target/classes/META-INF/native-image/io.fabric8/simple-jackson-project/reflect-config.json') + +assert configFile.exists() : "reflect-config.json should exist" + +def json = new JsonSlurper().parse(configFile) + +assert json.size() == 1 : "Should have 1 reflection entry" + +def personEntry = json.find { it.name == 'io.fabric8.test.Person' } +assert personEntry != null : "Should have entry for Person class" +assert personEntry.condition != null : "Should have condition" +assert personEntry.condition.typeReachable == 'io.fabric8.test.Person' : "Should have typeReachable condition" +assert personEntry.allDeclaredConstructors == true : "Should enable all declared constructors" +assert personEntry.allDeclaredMethods == true : "Should enable all declared methods" +assert personEntry.allDeclaredFields == true : "Should enable all declared fields" + +println "Integration test passed: Jackson annotations detected and configuration generated" diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/GraalVmMetadataGeneratorMojo.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/GraalVmMetadataGeneratorMojo.java new file mode 100644 index 0000000000..b3d0015f0c --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/GraalVmMetadataGeneratorMojo.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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. + */ +package io.fabric8.graalvm.plugin; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** + * Maven Mojo for generating GraalVM native-image metadata from Jandex indexes. + */ +@Mojo(name = "generate", defaultPhase = LifecyclePhase.PROCESS_CLASSES, threadSafe = true) +public class GraalVmMetadataGeneratorMojo extends AbstractMojo { + + @Parameter(defaultValue = "${project}", readonly = true, required = true) + private MavenProject project; + + /** + * Output directory for generated reflect-config.json. + * Default: ${project.build.outputDirectory}/META-INF/native-image/io.fabric8/${project.artifactId} + */ + @Parameter(property = "graalvm.metadata.outputDirectory") + private File outputDirectory; + + /** + * Strategy for determining which classes to include in reflection configuration. + */ + @Parameter(property = "graalvm.metadata.inclusionStrategy", defaultValue = "JACKSON_ANNOTATIONS") + private InclusionStrategy inclusionStrategy; + + /** + * Package patterns to include (supports wildcards, e.g., "io.fabric8.*"). + * These are additive to the inclusion strategy. + */ + @Parameter(property = "graalvm.metadata.includePatterns") + private List includePatterns; + + /** + * Package patterns to exclude (supports wildcards, e.g., "*.internal.*"). + */ + @Parameter(property = "graalvm.metadata.excludePatterns") + private List excludePatterns; + + /** + * Merge with existing reflect-config.json if it exists. + */ + @Parameter(property = "graalvm.metadata.mergeWithExisting", defaultValue = "true") + private boolean mergeWithExisting; + + /** + * Skip execution of this plugin. + */ + @Parameter(property = "graalvm.metadata.skip", defaultValue = "false") + private boolean skip; + + /** + * Enable reflection for all declared constructors. + */ + @Parameter(property = "graalvm.metadata.allDeclaredConstructors", defaultValue = "true") + private boolean allDeclaredConstructors; + + /** + * Enable reflection for all declared methods. + */ + @Parameter(property = "graalvm.metadata.allDeclaredMethods", defaultValue = "true") + private boolean allDeclaredMethods; + + /** + * Enable reflection for all declared fields. + */ + @Parameter(property = "graalvm.metadata.allDeclaredFields", defaultValue = "true") + private boolean allDeclaredFields; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if (skip) { + getLog().info("Skipping GraalVM metadata generation (skip=true)"); + return; + } + + try { + // Determine output directory + if (outputDirectory == null) { + String artifactId = project.getArtifactId(); + outputDirectory = new File(project.getBuild().getOutputDirectory(), + "META-INF/native-image/io.fabric8/" + artifactId); + } + + File outputFile = new File(outputDirectory, "reflect-config.json"); + getLog().info("Generating GraalVM reflection configuration: " + outputFile); + getLog().info("Inclusion strategy: " + inclusionStrategy); + + // Locate Jandex index + File classesDirectory = new File(project.getBuild().getOutputDirectory()); + File indexFile = new File(classesDirectory, "META-INF/jandex.idx"); + + if (!indexFile.exists()) { + getLog().warn("Jandex index not found at " + indexFile); + getLog().warn("Consider adding jandex-maven-plugin to your build"); + getLog().warn("Skipping metadata generation"); + return; + } + + // Scan classes + JandexReflectionScanner scanner = new JandexReflectionScanner(indexFile); + Set classes = scanner.findClasses(inclusionStrategy, includePatterns, excludePatterns); + + if (classes.isEmpty()) { + getLog().info("No classes found matching inclusion criteria"); + return; + } + + getLog().info("Found " + classes.size() + " classes for reflection configuration"); + + // Generate configuration + ReflectionConfigGenerator generator = new ReflectionConfigGenerator( + allDeclaredConstructors, + allDeclaredMethods, + allDeclaredFields); + + generator.generate(classes, outputFile, mergeWithExisting); + + getLog().info("Successfully generated reflection configuration at: " + outputFile); + + } catch (IOException e) { + throw new MojoExecutionException("Failed to generate GraalVM metadata", e); + } + } +} diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/InclusionStrategy.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/InclusionStrategy.java new file mode 100644 index 0000000000..8ca6268fa3 --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/InclusionStrategy.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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. + */ +package io.fabric8.graalvm.plugin; + +/** + * Strategy for determining which classes should be included in GraalVM reflection configuration. + */ +public enum InclusionStrategy { + /** + * Include classes with Jackson annotations (@JsonDeserialize, @JsonProperty, @JsonSerialize, etc.) + */ + JACKSON_ANNOTATIONS, + + /** + * Include classes with Sundrio @Buildable annotation and generated builder classes + */ + SUNDRIO_BUILDERS, + + /** + * Include Kubernetes resource classes (HasMetadata implementations) + */ + KUBERNETES_RESOURCES, + + /** + * Combination of JACKSON_ANNOTATIONS, SUNDRIO_BUILDERS, and KUBERNETES_RESOURCES + */ + COMPREHENSIVE, + + /** + * Only use include/exclude patterns (no annotation-based detection) + */ + PATTERN_BASED, + + /** + * Include all public classes (use with caution - generates large config files) + */ + ALL_PUBLIC_CLASSES, + + /** + * Include only classes that directly extend java.lang.Object + */ + DIRECT_OBJECT_SUBCLASSES +} diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/JandexReflectionScanner.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/JandexReflectionScanner.java new file mode 100644 index 0000000000..38f23bf7e6 --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/JandexReflectionScanner.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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. + */ +package io.fabric8.graalvm.plugin; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexReader; +import org.jboss.jandex.IndexView; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Scans classes using Jandex index to find classes that require reflection in GraalVM native-image. + */ +public class JandexReflectionScanner { + + private static final DotName JSON_DESERIALIZE = DotName + .createSimple("com.fasterxml.jackson.databind.annotation.JsonDeserialize"); + private static final DotName JSON_PROPERTY = DotName.createSimple("com.fasterxml.jackson.annotation.JsonProperty"); + private static final DotName JSON_SERIALIZE = DotName.createSimple("com.fasterxml.jackson.databind.annotation.JsonSerialize"); + private static final DotName JSON_TYPE_INFO = DotName.createSimple("com.fasterxml.jackson.annotation.JsonTypeInfo"); + private static final DotName JSON_SUB_TYPES = DotName.createSimple("com.fasterxml.jackson.annotation.JsonSubTypes"); + private static final DotName BUILDABLE = DotName.createSimple("io.sundr.builder.annotations.Buildable"); + private static final DotName HAS_METADATA = DotName.createSimple("io.fabric8.kubernetes.api.model.HasMetadata"); + + private final IndexView index; + + public JandexReflectionScanner(File indexFile) throws IOException { + try (InputStream in = new FileInputStream(indexFile)) { + IndexReader reader = new IndexReader(in); + this.index = reader.read(); + } + } + + /** + * Find classes that match the given inclusion strategy. + */ + public Set findClasses(InclusionStrategy strategy, List includePatterns, List excludePatterns) { + Set classes = new HashSet<>(); + + switch (strategy) { + case JACKSON_ANNOTATIONS: + classes.addAll(findJacksonAnnotatedClasses()); + break; + case SUNDRIO_BUILDERS: + classes.addAll(findSundrioBuilders()); + break; + case KUBERNETES_RESOURCES: + classes.addAll(findKubernetesResources()); + break; + case COMPREHENSIVE: + classes.addAll(findJacksonAnnotatedClasses()); + classes.addAll(findSundrioBuilders()); + classes.addAll(findKubernetesResources()); + break; + case PATTERN_BASED: + // Only use patterns, don't scan for annotations + break; + case ALL_PUBLIC_CLASSES: + classes.addAll(findAllPublicClasses()); + break; + case DIRECT_OBJECT_SUBCLASSES: + classes.addAll(findDirectObjectSubclasses()); + break; + } + + // Apply include patterns + if (includePatterns != null && !includePatterns.isEmpty()) { + Set matchedByPatterns = findByPatterns(includePatterns); + if (strategy == InclusionStrategy.PATTERN_BASED) { + classes.addAll(matchedByPatterns); + } else { + // For other strategies, patterns are additive + classes.addAll(matchedByPatterns); + } + } + + // Apply exclude patterns + if (excludePatterns != null && !excludePatterns.isEmpty()) { + Set excluded = findByPatterns(excludePatterns); + classes.removeAll(excluded); + } + + return classes; + } + + /** + * Find classes with Jackson annotations. + */ + private Set findJacksonAnnotatedClasses() { + Set classes = new HashSet<>(); + + // Find classes with class-level annotations + addClassesWithClassAnnotation(classes, JSON_DESERIALIZE); + addClassesWithClassAnnotation(classes, JSON_SERIALIZE); + addClassesWithClassAnnotation(classes, JSON_TYPE_INFO); + addClassesWithClassAnnotation(classes, JSON_SUB_TYPES); + + // Find classes with member-level annotations + addClassesWithMemberAnnotation(classes, JSON_PROPERTY); + + return classes; + } + + /** + * Add classes that have the specified annotation at the class level. + */ + private void addClassesWithClassAnnotation(Set classes, DotName annotationName) { + for (AnnotationInstance annotation : index.getAnnotations(annotationName)) { + if (annotation.target().kind() == org.jboss.jandex.AnnotationTarget.Kind.CLASS) { + classes.add(annotation.target().asClass().name().toString()); + } + } + } + + /** + * Add classes that have the specified annotation on fields or methods. + */ + private void addClassesWithMemberAnnotation(Set classes, DotName annotationName) { + for (AnnotationInstance annotation : index.getAnnotations(annotationName)) { + org.jboss.jandex.AnnotationTarget.Kind kind = annotation.target().kind(); + if (kind == org.jboss.jandex.AnnotationTarget.Kind.FIELD) { + classes.add(annotation.target().asField().declaringClass().name().toString()); + } else if (kind == org.jboss.jandex.AnnotationTarget.Kind.METHOD) { + classes.add(annotation.target().asMethod().declaringClass().name().toString()); + } + } + } + + /** + * Find classes with Sundrio @Buildable annotation and generated builder classes. + */ + private Set findSundrioBuilders() { + Set classes = new HashSet<>(); + + for (AnnotationInstance annotation : index.getAnnotations(BUILDABLE)) { + if (annotation.target().kind() == org.jboss.jandex.AnnotationTarget.Kind.CLASS) { + String className = annotation.target().asClass().name().toString(); + classes.add(className); + + // Add generated builder class + String builderClassName = className + "Builder"; + if (index.getClassByName(DotName.createSimple(builderClassName)) != null) { + classes.add(builderClassName); + } + } + } + + return classes; + } + + /** + * Find Kubernetes resource classes (HasMetadata implementations). + */ + @SuppressWarnings("deprecation") + private Set findKubernetesResources() { + Set classes = new HashSet<>(); + + // Using getAllKnownImplementors which is deprecated in Jandex 3.x but still functional + for (ClassInfo classInfo : index.getAllKnownImplementors(HAS_METADATA)) { + classes.add(classInfo.name().toString()); + } + + return classes; + } + + /** + * Find all public classes. + */ + private Set findAllPublicClasses() { + Set classes = new HashSet<>(); + + for (ClassInfo classInfo : index.getKnownClasses()) { + if (java.lang.reflect.Modifier.isPublic(classInfo.flags())) { + classes.add(classInfo.name().toString()); + } + } + + return classes; + } + + /** + * Find classes that directly extend java.lang.Object (no other superclass). + */ + private Set findDirectObjectSubclasses() { + Set classes = new HashSet<>(); + DotName objectName = DotName.createSimple("java.lang.Object"); + + for (ClassInfo classInfo : index.getKnownClasses()) { + // Skip interfaces and annotations + if (classInfo.isInterface() || classInfo.isAnnotation()) { + continue; + } + + // Get the superclass + DotName superClass = classInfo.superName(); + + // Include only if superclass is java.lang.Object + if (superClass != null && superClass.equals(objectName)) { + classes.add(classInfo.name().toString()); + } + } + + return classes; + } + + /** + * Find classes matching the given patterns. + */ + private Set findByPatterns(List patterns) { + Set classes = new HashSet<>(); + + if (patterns == null || patterns.isEmpty()) { + return classes; + } + + List compiledPatterns = patterns.stream() + .map(p -> Pattern.compile(p.replace(".", "\\.").replace("*", ".*"))) + .collect(java.util.stream.Collectors.toList()); + + for (ClassInfo classInfo : index.getKnownClasses()) { + String className = classInfo.name().toString(); + for (Pattern pattern : compiledPatterns) { + if (pattern.matcher(className).matches()) { + classes.add(className); + break; + } + } + } + + return classes; + } +} diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionConfigGenerator.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionConfigGenerator.java new file mode 100644 index 0000000000..981f700059 --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionConfigGenerator.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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. + */ +package io.fabric8.graalvm.plugin; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Generates GraalVM reflect-config.json files. + */ +public class ReflectionConfigGenerator { + + private final ObjectMapper objectMapper; + private final boolean allDeclaredConstructors; + private final boolean allDeclaredMethods; + private final boolean allDeclaredFields; + + public ReflectionConfigGenerator(boolean allDeclaredConstructors, boolean allDeclaredMethods, + boolean allDeclaredFields) { + this.allDeclaredConstructors = allDeclaredConstructors; + this.allDeclaredMethods = allDeclaredMethods; + this.allDeclaredFields = allDeclaredFields; + + this.objectMapper = new ObjectMapper(); + this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + } + + /** + * Generate reflection configuration and write to file. + * + * @param classes Classes to include in reflection configuration + * @param outputFile Output file path + * @param mergeWithExisting Whether to merge with existing configuration + * @throws IOException if file operations fail + */ + public void generate(Set classes, File outputFile, boolean mergeWithExisting) throws IOException { + Map entries = new LinkedHashMap<>(); + + // Load existing configuration if merging + if (mergeWithExisting && outputFile.exists()) { + List existing = loadExisting(outputFile); + for (ReflectionEntry entry : existing) { + entries.put(entry.getName(), entry); + } + } + + // Add new classes + for (String className : classes) { + entries.computeIfAbsent(className, key -> { + ReflectionEntry entry = new ReflectionEntry(key); + entry.setCondition(new ReflectionEntry.Condition(key)); + entry.setAllDeclaredConstructors(allDeclaredConstructors); + entry.setAllDeclaredMethods(allDeclaredMethods); + entry.setAllDeclaredFields(allDeclaredFields); + return entry; + }); + } + + // Sort by class name for stable output + List sortedEntries = new ArrayList<>(entries.values()); + sortedEntries.sort(Comparator.comparing(ReflectionEntry::getName)); + + // Ensure output directory exists + File outputDir = outputFile.getParentFile(); + if (outputDir != null && !outputDir.exists()) { + outputDir.mkdirs(); + } + + // Write to file + objectMapper.writeValue(outputFile, sortedEntries); + } + + /** + * Load existing reflection configuration from file. + */ + private List loadExisting(File file) throws IOException { + ReflectionEntry[] entries = objectMapper.readValue(file, ReflectionEntry[].class); + return java.util.Arrays.asList(entries); + } +} diff --git a/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionEntry.java b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionEntry.java new file mode 100644 index 0000000000..46429c53aa --- /dev/null +++ b/graalvm-metadata-generator-plugin/src/main/java/io/fabric8/graalvm/plugin/ReflectionEntry.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * 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. + */ +package io.fabric8.graalvm.plugin; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +/** + * Represents a single entry in GraalVM's reflect-config.json file. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ReflectionEntry { + + @JsonProperty("condition") + private Condition condition; + + @JsonProperty("name") + private String name; + + @JsonProperty("allDeclaredConstructors") + private Boolean allDeclaredConstructors; + + @JsonProperty("allDeclaredMethods") + private Boolean allDeclaredMethods; + + @JsonProperty("allDeclaredFields") + private Boolean allDeclaredFields; + + /** + * Nested class representing the condition for reflection metadata. + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Condition { + @JsonProperty("typeReachable") + private String typeReachable; + + public Condition() { + } + + public Condition(String typeReachable) { + this.typeReachable = typeReachable; + } + + public String getTypeReachable() { + return typeReachable; + } + + public void setTypeReachable(String typeReachable) { + this.typeReachable = typeReachable; + } + } + + public ReflectionEntry() { + } + + public ReflectionEntry(String name) { + this.name = name; + } + + public Condition getCondition() { + return condition; + } + + public void setCondition(Condition condition) { + this.condition = condition; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getAllDeclaredConstructors() { + return allDeclaredConstructors; + } + + public void setAllDeclaredConstructors(Boolean allDeclaredConstructors) { + this.allDeclaredConstructors = allDeclaredConstructors; + } + + public Boolean getAllDeclaredMethods() { + return allDeclaredMethods; + } + + public void setAllDeclaredMethods(Boolean allDeclaredMethods) { + this.allDeclaredMethods = allDeclaredMethods; + } + + public Boolean getAllDeclaredFields() { + return allDeclaredFields; + } + + public void setAllDeclaredFields(Boolean allDeclaredFields) { + this.allDeclaredFields = allDeclaredFields; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ReflectionEntry that = (ReflectionEntry) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return "ReflectionEntry{" + + "name='" + name + '\'' + + ", allDeclaredConstructors=" + allDeclaredConstructors + + ", allDeclaredMethods=" + allDeclaredMethods + + ", allDeclaredFields=" + allDeclaredFields + + '}'; + } +} diff --git a/pom.xml b/pom.xml index 54c7ad62a5..82a52673cc 100644 --- a/pom.xml +++ b/pom.xml @@ -232,6 +232,7 @@ log4j kubernetes-examples bom-generator-plugin + graalvm-metadata-generator-plugin @@ -1504,5 +1505,31 @@ + + generate-graal-metadata + + + + io.smallrye + jandex-maven-plugin + + + io.fabric8 + graalvm-metadata-generator-plugin + ${project.version} + + + + generate + + + JACKSON_ANNOTATIONS + + + + + + +