From 2899fd7aca70b10638806643c4d0a8c8429340f6 Mon Sep 17 00:00:00 2001 From: Joe Littlejohn Date: Wed, 4 Feb 2026 23:18:32 +0000 Subject: [PATCH] Add support for annotations with parameters and fix 'since' tags - Add annotated(JAnnotationUse) overload to allow configuring annotation parameters (e.g., @Size(min=1, max=10)) - Update all since' tags to 4.2.0 for consistency - Add tests for parameterized annotations --- .../com/helger/jcodemodel/AbstractJClass.java | 29 +++++++++- .../helger/jcodemodel/JAnnotatedClass.java | 23 +++++++- .../jcodemodel/JAnnotatedClassTest.java | 53 +++++++++++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/AbstractJClass.java b/jcodemodel/src/main/java/com/helger/jcodemodel/AbstractJClass.java index b5bae1e5..639045c9 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/AbstractJClass.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/AbstractJClass.java @@ -2,7 +2,7 @@ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. - * Portions Copyright 2013-2026 Philip Helger + contributors + * Portions Copyright 2013-2025 Philip Helger + contributors * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -353,6 +353,7 @@ public AbstractJClass narrowAny () @NonNull public JAnnotatedClass annotated (@NonNull final Class aClazz) { + ValueEnforcer.notNull (aClazz, "Clazz"); return annotated (owner ().ref (aClazz)); } @@ -392,9 +393,35 @@ public JAnnotatedClass annotated (@NonNull final Class aC @NonNull public JAnnotatedClass annotated (@NonNull final AbstractJClass aClazz) { + ValueEnforcer.notNull (aClazz, "Clazz"); return new JAnnotatedClass (this, new JAnnotationUse (aClazz)); } + /** + * Creates a new type with a type-use annotation (JSR 308, Java 8+). + *

+ * This overload accepts a pre-configured {@link JAnnotationUse}, allowing annotations + * with parameters: + *

+   * JAnnotationUse sizeAnnotation = new JAnnotationUse(codeModel.ref(Size.class));
+   * sizeAnnotation.param("min", 1).param("max", 10);
+   * AbstractJClass annotatedString = stringClass.annotated(sizeAnnotation);
+   * // Generates: @Size(min = 1, max = 10) String
+   * 
+ * + * @param aAnnotation + * The pre-configured annotation to apply as a type-use annotation. + * @return A new {@link JAnnotatedClass} wrapping this class with the annotation. + * @since 4.2.0 + * @see #annotated(Class) for simple annotations without parameters + */ + @NonNull + public JAnnotatedClass annotated (@NonNull final JAnnotationUse aAnnotation) + { + ValueEnforcer.notNull (aAnnotation, "Annotation"); + return new JAnnotatedClass (this, aAnnotation); + } + /** * @return If this class is parameterized, the type parameters of the given index. */ diff --git a/jcodemodel/src/main/java/com/helger/jcodemodel/JAnnotatedClass.java b/jcodemodel/src/main/java/com/helger/jcodemodel/JAnnotatedClass.java index 374b8d6c..ef1cc749 100644 --- a/jcodemodel/src/main/java/com/helger/jcodemodel/JAnnotatedClass.java +++ b/jcodemodel/src/main/java/com/helger/jcodemodel/JAnnotatedClass.java @@ -69,8 +69,18 @@ * Example: {@code private List<@NotNull String> items;} * *

- * Use {@link AbstractJClass#annotated(Class)} or {@link AbstractJClass#annotated(AbstractJClass)} - * to create instances of this class. + * Annotations with parameters: + *

+ * For annotations that require parameters, use {@link AbstractJClass#annotated(JAnnotationUse)}: + *

+ * JAnnotationUse sizeAnnotation = new JAnnotationUse(codeModel.ref(Size.class));
+ * sizeAnnotation.param("min", 1).param("max", 10);
+ * AbstractJClass annotatedString = stringClass.annotated(sizeAnnotation);
+ * // Generates: @Size(min = 1, max = 10) String
+ * 
+ *

+ * Use {@link AbstractJClass#annotated(Class)}, {@link AbstractJClass#annotated(AbstractJClass)}, + * or {@link AbstractJClass#annotated(JAnnotationUse)} to create instances of this class. * * @since 4.2.0 */ @@ -150,6 +160,15 @@ public JAnnotatedClass annotated (@NonNull final AbstractJClass aClazz) return new JAnnotatedClass (m_aBasis, newAnnotations); } + @Override + @NonNull + public JAnnotatedClass annotated (@NonNull final JAnnotationUse aAnnotation) + { + final List newAnnotations = new ArrayList <> (m_aAnnotations); + newAnnotations.add (aAnnotation); + return new JAnnotatedClass (m_aBasis, newAnnotations); + } + @Override public String name () { diff --git a/jcodemodel/src/test/java/com/helger/jcodemodel/JAnnotatedClassTest.java b/jcodemodel/src/test/java/com/helger/jcodemodel/JAnnotatedClassTest.java index b91c2b84..4e05f161 100644 --- a/jcodemodel/src/test/java/com/helger/jcodemodel/JAnnotatedClassTest.java +++ b/jcodemodel/src/test/java/com/helger/jcodemodel/JAnnotatedClassTest.java @@ -387,4 +387,57 @@ public void testAnnotatedPrimitiveArrayType () throws JCodeModelException testClass.field (JMod.PRIVATE, annotatedIntArray, "values"); CodeModelTestsHelper.parseCodeModel (cm); } + + /** + * Test annotation with parameters: {@code @SuppressWarnings("unchecked") String} + */ + @Test + public void testAnnotationWithParameters () throws JCodeModelException + { + final JCodeModel cm = JCodeModel.createUnified (); + + // Create @SuppressWarnings("unchecked") String + final JAnnotationUse annotation = new JAnnotationUse (cm.ref (SuppressWarnings.class)); + annotation.param ("value", "unchecked"); + final AbstractJClass stringClass = cm.ref (String.class); + final JAnnotatedClass annotatedString = stringClass.annotated (annotation); + + // Check generated output + final String generated = CodeModelTestsHelper.generate (annotatedString); + assertEquals ("@java.lang.SuppressWarnings(\"unchecked\") java.lang.String", generated); + + // Verify it parses when used in a class + final JDefinedClass testClass = cm._class ("com.example.Test"); + testClass.field (JMod.PRIVATE, annotatedString, "value"); + CodeModelTestsHelper.parseCodeModel (cm); + } + + /** + * Test annotation with multiple parameters in a generic type. + */ + @Test + public void testAnnotationWithMultipleParameters () throws JCodeModelException + { + final JCodeModel cm = JCodeModel.createUnified (); + final JDefinedClass testClass = cm._class ("com.example.Test"); + + // Create a custom annotation class for testing (simulating @Size(min=1, max=10)) + // We'll use @SuppressWarnings with array param as a proxy since it's available + final JAnnotationUse annotation = new JAnnotationUse (cm.ref (SuppressWarnings.class)); + annotation.paramArray ("value", "unchecked", "rawtypes"); + + final AbstractJClass stringClass = cm.ref (String.class); + final JAnnotatedClass annotatedString = stringClass.annotated (annotation); + final AbstractJClass listType = cm.ref (List.class).narrow (annotatedString); + + testClass.field (JMod.PRIVATE, listType, "items"); + + // Check the generated output contains the annotation with parameters + final String classOutput = CodeModelTestsHelper.declare (testClass); + assertTrue ("Expected annotation with array parameters in output", + classOutput.contains ("@java.lang.SuppressWarnings({")); + + // Verify it parses + CodeModelTestsHelper.parseCodeModel (cm); + } }