You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2026/10/13 13:09:12 UTC

(cxf-xjc-utils) 01/01: Use public codemodel API in Bug986Plugin instead of reflection

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/bug986
in repository https://gitbox.apache.org/repos/asf/cxf-xjc-utils.git

commit 68f29240090cb1f829901c133aa08298dd5ed969
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Tue Oct 13 14:08:36 2026 +0100

    Use public codemodel API in Bug986Plugin instead of reflection
---
 bug986/pom.xml                                     |   5 +
 .../org/apache/cxf/xjc/bug986/Bug986Plugin.java    |  65 ++---------
 .../apache/cxf/xjc/bug986/Bug986PluginTest.java    | 127 +++++++++++++++++++++
 bug986/src/test/resources/bug986.xsd               |  54 +++++++++
 4 files changed, 194 insertions(+), 57 deletions(-)

diff --git a/bug986/pom.xml b/bug986/pom.xml
index d0b9446..3091ca9 100644
--- a/bug986/pom.xml
+++ b/bug986/pom.xml
@@ -41,6 +41,11 @@
             <artifactId>jaxb-runtime</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/bug986/src/main/java/org/apache/cxf/xjc/bug986/Bug986Plugin.java b/bug986/src/main/java/org/apache/cxf/xjc/bug986/Bug986Plugin.java
index 4040707..a30cc43 100644
--- a/bug986/src/main/java/org/apache/cxf/xjc/bug986/Bug986Plugin.java
+++ b/bug986/src/main/java/org/apache/cxf/xjc/bug986/Bug986Plugin.java
@@ -21,10 +21,7 @@ package org.apache.cxf.xjc.bug986;
 
 
 import java.io.StringWriter;
-import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Logger;
@@ -34,10 +31,8 @@ import org.xml.sax.ErrorHandler;
 
 import com.sun.codemodel.JAnnotationUse;
 import com.sun.codemodel.JAnnotationValue;
-import com.sun.codemodel.JClass;
 import com.sun.codemodel.JFieldVar;
 import com.sun.codemodel.JFormatter;
-import com.sun.codemodel.JVar;
 import com.sun.tools.xjc.Options;
 import com.sun.tools.xjc.Plugin;
 import com.sun.tools.xjc.outline.ClassOutline;
@@ -75,11 +70,13 @@ public class Bug986Plugin {
         for (ClassOutline classOutline : outline.getClasses()) {
             Map<String, JFieldVar> fields = classOutline.implClass.fields();
             for (JFieldVar field : fields.values()) {
-                Collection<JAnnotationUse> annotations = getAnnotations(field);
-                List<JAnnotationUse> toRemove = new ArrayList<JAnnotationUse>();
-                for (JAnnotationUse j : annotations) {
-                    if (XmlSchemaType.class.getName().equals(getAnnotationClass(j).fullName())) {
-                        JAnnotationValue st = getAnnotationMember(j, "name");
+                List<JAnnotationUse> toRemove = new ArrayList<>();
+                for (JAnnotationUse j : field.annotations()) {
+                    if (XmlSchemaType.class.getName().equals(j.getAnnotationClass().fullName())) {
+                        JAnnotationValue st = j.getAnnotationMembers().get("name");
+                        if (st == null) {
+                            continue;
+                        }
                         StringWriter sw = new StringWriter();
                         st.generate(new JFormatter(sw));
                         if ("\"anySimpleType\"".equals(sw.toString())) {
@@ -96,56 +93,10 @@ public class Bug986Plugin {
                     }
                 }
                 for (JAnnotationUse j : toRemove) {
-                    annotations.remove(j);
+                    field.removeAnnotation(j);
                 }
             }
         }
         return true;
     }
-    
-    private JAnnotationValue getAnnotationMember(JAnnotationUse ju, String name) {
-        try {
-            Field f = JAnnotationUse.class.getDeclaredField("memberValues");
-            f.setAccessible(true);
-            @SuppressWarnings("unchecked")
-            Map<String, JAnnotationValue> memberValues = (Map<String, JAnnotationValue>)f.get(ju);
-            if (memberValues == null) {
-                return null;
-            }
-            return memberValues.get(name);
-        } catch (Throwable t) {
-            //ignore for now
-            t.printStackTrace();
-        }
-        return null;
-    }
-    
-    private JClass getAnnotationClass(JAnnotationUse ju) {
-        try {
-            Field f = JAnnotationUse.class.getDeclaredField("clazz");
-            f.setAccessible(true);
-            return (JClass)f.get(ju);
-        } catch (Throwable t) {
-            //ignore for now
-            t.printStackTrace();
-        }
-        return null;
-    }
-    
-    @SuppressWarnings("unchecked")
-    private List<JAnnotationUse> getAnnotations(JFieldVar field) {
-        try {
-            Field f = JVar.class.getDeclaredField("annotations");
-            f.setAccessible(true);
-            List<?> anns = (List<?>)f.get(field);
-            if (anns == null) {
-                anns = Collections.emptyList();
-            }
-            return (List<JAnnotationUse>)anns;
-        } catch (Throwable t) {
-            //ignore for now
-            t.printStackTrace();
-        }
-        return Collections.emptyList();
-    }
 }
diff --git a/bug986/src/test/java/org/apache/cxf/xjc/bug986/Bug986PluginTest.java b/bug986/src/test/java/org/apache/cxf/xjc/bug986/Bug986PluginTest.java
new file mode 100644
index 0000000..8ebaea2
--- /dev/null
+++ b/bug986/src/test/java/org/apache/cxf/xjc/bug986/Bug986PluginTest.java
@@ -0,0 +1,127 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.xjc.bug986;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.stream.Stream;
+
+import com.sun.tools.xjc.Driver;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class Bug986PluginTest {
+
+    private static final String ANY_SIMPLE_TYPE = "@XmlSchemaType(name = \"anySimpleType\")";
+
+    @Rule
+    public TemporaryFolder tmp = new TemporaryFolder();
+
+    @Test
+    public void testWithoutPlugin() throws Exception {
+        String src = generate(false);
+        Assert.assertTrue(annotationsOf(src, "protected Integer intAttr;").contains(ANY_SIMPLE_TYPE));
+        Assert.assertTrue(annotationsOf(src, "protected String stringAttr;").contains(ANY_SIMPLE_TYPE));
+    }
+
+    @Test
+    public void testRemovesAnySimpleTypeFromNonStringFields() throws Exception {
+        String src = generate(true);
+        Assert.assertFalse(annotationsOf(src, "protected Integer intAttr;").contains(ANY_SIMPLE_TYPE));
+        Assert.assertTrue(annotationsOf(src, "protected String stringAttr;").contains(ANY_SIMPLE_TYPE));
+    }
+
+    /*
+     * Run XJC on the module path, where the codemodel module does not open its
+     * packages to the plugin, so reflection on codemodel internals fails.
+     */
+    @Test
+    public void testOnModulePath() throws Exception {
+        List<String> modulePath = new ArrayList<>();
+        for (String e : System.getProperty("java.class.path").split(File.pathSeparator)) {
+            if (e.endsWith(".jar")) {
+                modulePath.add(e);
+            }
+        }
+        // the plugin's classes as an automatic module
+        modulePath.add(jar(Paths.get(Bug986Plugin.class.getProtectionDomain().getCodeSource().getLocation().toURI()),
+                           tmp.newFile("cxf-xjc-bug986.jar").toPath()));
+        File out = tmp.newFolder();
+        String schema = new File(getClass().getResource("/bug986.xsd").toURI()).getAbsolutePath();
+        Process p = new ProcessBuilder(
+            Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
+            "--module-path", String.join(File.pathSeparator, modulePath),
+            "--add-modules", "ALL-MODULE-PATH",
+            "com.sun.tools.xjc.XJCFacade",
+            "-extension", "-Xbug986", "-d", out.getAbsolutePath(), schema)
+            .inheritIO().start();
+        Assert.assertTrue("XJC did not finish", p.waitFor(2, TimeUnit.MINUTES));
+        Assert.assertEquals(0, p.exitValue());
+
+        File holder = new File(out, "org/apache/cxf/bug986/Holder.java");
+        String src = new String(Files.readAllBytes(holder.toPath()), StandardCharsets.UTF_8);
+        Assert.assertFalse(annotationsOf(src, "protected Integer intAttr;").contains(ANY_SIMPLE_TYPE));
+        Assert.assertTrue(annotationsOf(src, "protected String stringAttr;").contains(ANY_SIMPLE_TYPE));
+    }
+
+    private static String jar(Path classes, Path jar) throws Exception {
+        try (OutputStream os = Files.newOutputStream(jar);
+            JarOutputStream jos = new JarOutputStream(os);
+            Stream<Path> files = Files.walk(classes)) {
+            for (Path f : (Iterable<Path>) files.filter(Files::isRegularFile)::iterator) {
+                jos.putNextEntry(new JarEntry(classes.relativize(f).toString().replace(File.separatorChar, '/')));
+                Files.copy(f, jos);
+                jos.closeEntry();
+            }
+        }
+        return jar.toString();
+    }
+
+    private String generate(boolean withPlugin) throws Exception {
+        File out = tmp.newFolder();
+        String schema = new File(getClass().getResource("/bug986.xsd").toURI()).getAbsolutePath();
+        String[] args = withPlugin
+            ? new String[] {"-extension", "-Xbug986", "-d", out.getAbsolutePath(), schema}
+            : new String[] {"-extension", "-d", out.getAbsolutePath(), schema};
+        Assert.assertEquals(0, Driver.run(args, System.out, System.out));
+        File holder = new File(out, "org/apache/cxf/bug986/Holder.java");
+        return new String(Files.readAllBytes(holder.toPath()), StandardCharsets.UTF_8);
+    }
+
+    // the annotations written between the previous declaration and the given field
+    private static String annotationsOf(String src, String field) {
+        int idx = src.indexOf(field);
+        Assert.assertTrue(field + " not generated", idx != -1);
+        int start = Math.max(src.lastIndexOf(';', idx), src.lastIndexOf('{', idx));
+        return src.substring(start + 1, idx);
+    }
+}
diff --git a/bug986/src/test/resources/bug986.xsd b/bug986/src/test/resources/bug986.xsd
new file mode 100644
index 0000000..9621569
--- /dev/null
+++ b/bug986/src/test/resources/bug986.xsd
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<!--
+  Mapping xs:anySimpleType to a non-String type leaves
+  @XmlSchemaType(name = "anySimpleType") on the generated attribute field.
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
+    xmlns:tns="http://cxf.apache.org/bug986" targetNamespace="http://cxf.apache.org/bug986"
+    elementFormDefault="qualified" jaxb:version="3.0">
+
+    <xs:annotation>
+        <xs:appinfo>
+            <jaxb:globalBindings>
+                <jaxb:javaType name="java.lang.Integer" xmlType="xs:anySimpleType"
+                    parseMethod="java.lang.Integer.valueOf" printMethod="java.lang.String.valueOf"/>
+            </jaxb:globalBindings>
+        </xs:appinfo>
+    </xs:annotation>
+
+    <xs:complexType name="holder">
+        <xs:attribute name="intAttr" type="xs:anySimpleType"/>
+        <xs:attribute name="stringAttr" type="xs:anySimpleType">
+            <xs:annotation>
+                <xs:appinfo>
+                    <jaxb:property>
+                        <jaxb:baseType>
+                            <jaxb:javaType name="java.lang.String"
+                                parseMethod="jakarta.xml.bind.DatatypeConverter.parseString"
+                                printMethod="jakarta.xml.bind.DatatypeConverter.printString"/>
+                        </jaxb:baseType>
+                    </jaxb:property>
+                </xs:appinfo>
+            </xs:annotation>
+        </xs:attribute>
+    </xs:complexType>
+
+</xs:schema>