diff --git a/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JCommentPart.java b/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JCommentPart.java index f983fefaf..a1785f2b6 100644 --- a/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JCommentPart.java +++ b/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JCommentPart.java @@ -11,11 +11,11 @@ package com.sun.codemodel; -import java.util.AbstractMap; +import com.sun.codemodel.util.Util; + import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.List; /** * A part is a part of a javadoc comment, and it is a list of values. @@ -34,13 +34,6 @@ public class JCommentPart extends ArrayList { private static final long serialVersionUID = 1L; - private static final List> ESCAPED_XML_JAVADOC = new ArrayList<>(); - static { - ESCAPED_XML_JAVADOC.add(new AbstractMap.SimpleImmutableEntry<>("&", "&")); - ESCAPED_XML_JAVADOC.add(new AbstractMap.SimpleImmutableEntry<>("<", "<")); - ESCAPED_XML_JAVADOC.add(new AbstractMap.SimpleImmutableEntry<>(">", ">")); - ESCAPED_XML_JAVADOC.add(new AbstractMap.SimpleImmutableEntry<>("@", "@")); - } protected JCommentPart() { } @@ -63,7 +56,7 @@ public JCommentPart append(Object o) { * Otherwise it will be converted to String via {@link Object#toString()}. */ public JCommentPart appendXML(String s) { - add(escapeXML(s)); + add(Util.escapeXML(s)); return this; } @@ -133,29 +126,6 @@ protected void format( JFormatter f, String indent ) { f.nl(); } - /** - * Escapes the XML tags for Javadoc compatibility - */ - private String escapeXML(String s) { - if (s == null) { - return s; - } - for (AbstractMap.SimpleImmutableEntry entry : ESCAPED_XML_JAVADOC) { - int entryKeyLength = entry.getKey().length(); - int entryValueLength = entry.getValue().length(); - int idx = -1; - while (true) { - idx = s.indexOf(entry.getKey(), idx); - if (idx < 0) { - break; - } - s = s.substring(0, idx) + entry.getValue() + s.substring(idx + entryKeyLength); - idx += entryValueLength; - } - } - return s; - } - /** * Escapes the appearance of the comment terminator. */ diff --git a/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/util/Util.java b/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/util/Util.java new file mode 100644 index 000000000..9f298192b --- /dev/null +++ b/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/util/Util.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package com.sun.codemodel.util; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; + +/** + * This class aims to provide some util methods used in codemodel and dependencies project. + *

+ * For now, it allows user to escape some dangerous characters for javadoc generation processing + */ +public class Util { + + private static final List> ESCAPED_XML_JAVADOC = new ArrayList<>(); + static { + ESCAPED_XML_JAVADOC.add(new AbstractMap.SimpleImmutableEntry<>("&", "&")); + ESCAPED_XML_JAVADOC.add(new AbstractMap.SimpleImmutableEntry<>("<", "<")); + ESCAPED_XML_JAVADOC.add(new AbstractMap.SimpleImmutableEntry<>(">", ">")); + ESCAPED_XML_JAVADOC.add(new AbstractMap.SimpleImmutableEntry<>("@", "@")); + } + + private Util() {} + + /** + * Escapes the XML tags for Javadoc compatibility + */ + public static String escapeXML(String s) { + if (s == null) { + return s; + } + for (AbstractMap.SimpleImmutableEntry entry : ESCAPED_XML_JAVADOC) { + int entryKeyLength = entry.getKey().length(); + int entryValueLength = entry.getValue().length(); + int idx = -1; + while (true) { + idx = s.indexOf(entry.getKey(), idx); + if (idx < 0) { + break; + } + s = s.substring(0, idx) + entry.getValue() + s.substring(idx + entryKeyLength); + idx += entryValueLength; + } + } + return s; + } +} diff --git a/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/generator/bean/BeanGenerator.java b/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/generator/bean/BeanGenerator.java index c7ec5e07c..7f5027f09 100644 --- a/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/generator/bean/BeanGenerator.java +++ b/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/generator/bean/BeanGenerator.java @@ -557,7 +557,7 @@ private void generateClassBody(ClassOutlineImpl cc) { } // generate some class level javadoc - cc.ref.javadoc().appendXML(target.javadoc); + cc.ref.javadoc().append(target.javadoc); cc._package().objectFactoryGenerator().populate(cc); } @@ -616,7 +616,7 @@ private EnumOutline generateEnumDef(CEnumLeafInfo e) { type = getClassFactory().createClass( getContainer(e.parent, EXPOSED), e.shortName, e.getLocator(), ClassType.ENUM); - type.javadoc().appendXML(e.javadoc); + type.javadoc().append(e.javadoc); return new EnumOutline(e, type) { @@ -683,7 +683,7 @@ private void generateEnumBody(EnumOutline eo) { // set javadoc if (mem.javadoc != null) { - constRef.javadoc().appendXML(mem.javadoc); + constRef.javadoc().append(mem.javadoc); } eo.constants.add(new EnumConstantOutline(mem, constRef) { diff --git a/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/reader/xmlschema/ClassSelector.java b/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/reader/xmlschema/ClassSelector.java index a9dc37fad..46e0a8022 100644 --- a/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/reader/xmlschema/ClassSelector.java +++ b/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/reader/xmlschema/ClassSelector.java @@ -1,5 +1,6 @@ /* * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025 Contributors to the Eclipse Foundation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -20,6 +21,7 @@ import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JJavaName; import com.sun.codemodel.JPackage; +import com.sun.codemodel.util.Util; import com.sun.istack.NotNull; import com.sun.tools.xjc.model.CBuiltinLeafInfo; import com.sun.tools.xjc.model.CClassInfo; @@ -368,7 +370,7 @@ private void addSchemaFragmentJavadoc( CClassInfo bean, XSComponent sc ) { // first, pick it up from if any. String doc = builder.getDocumentation(sc); if (doc != null) { - append(bean, doc); + append(bean, Util.escapeXML(doc)); } // then the description of where this component came from diff --git a/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/reader/xmlschema/SimpleTypeBuilder.java b/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/reader/xmlschema/SimpleTypeBuilder.java index 54a159c1e..3bffc6d6c 100644 --- a/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/reader/xmlschema/SimpleTypeBuilder.java +++ b/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/reader/xmlschema/SimpleTypeBuilder.java @@ -1,5 +1,6 @@ /* * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025 Contributors to the Eclipse Foundation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0, which is available at @@ -23,6 +24,7 @@ import java.util.Set; import java.util.Stack; +import com.sun.codemodel.util.Util; import jakarta.activation.MimeTypeParseException; import jakarta.xml.bind.DatatypeConverter; @@ -367,7 +369,7 @@ private TypeUse find( XSSimpleType type ) { // list and union cannot be mapped to a type-safe enum, // so in this stage we can safely cast it to XSRestrictionSimpleType return bindToTypeSafeEnum( (XSRestrictionSimpleType)type, - en.className, en.javadoc, en.members, + en.className, Util.escapeXML(en.javadoc), en.members, getEnumMemberMode().getModeWithEnum(), en.getLocation() ); } @@ -394,7 +396,7 @@ private TypeUse find( XSSimpleType type ) { String documentation = ""; String extDocumentation = builder.getDocumentation(type); if (extDocumentation != null) { - documentation = extDocumentation; + documentation = Util.escapeXML(extDocumentation); } // see if this type should be mapped to a type-safe enumeration by default. @@ -649,7 +651,7 @@ private List buildCEnumConstants(XSRestrictionSimpleType type, bo for( XSFacet facet : type.getDeclaredFacets(XSFacet.FACET_ENUMERATION)) { String name=null; - String mdoc= builder.getDocumentation(facet); + String mdoc= Util.escapeXML(builder.getDocumentation(facet)); if(!enums.add(facet.getValue().value)) continue; // ignore the 2nd occasion diff --git a/jaxb-ri/xjc/src/test/java/com/sun/tools/xjc/CodeGenTest.java b/jaxb-ri/xjc/src/test/java/com/sun/tools/xjc/CodeGenTest.java index 23742aeee..d3db7126c 100644 --- a/jaxb-ri/xjc/src/test/java/com/sun/tools/xjc/CodeGenTest.java +++ b/jaxb-ri/xjc/src/test/java/com/sun/tools/xjc/CodeGenTest.java @@ -223,6 +223,73 @@ public void testIssue1788() throws FileNotFoundException, URISyntaxException { } } + /** + * Test issues #1865 for good javadoc generation + * + * @throws FileNotFoundException When the test schema or binding file cannot be read. + * @throws URISyntaxException When the test {@link InputSource} cannot be parsed. + * + * @see Issue #1788 + */ + public void testIssue1865() throws FileNotFoundException, URISyntaxException { + String schemaFileName = "/schemas/issue1865/simple.xsd"; + String packageName = "org.example.issue1865"; + String someClassName = packageName + ".TestEscapeJavadoc"; + String someEnumName = packageName + ".FooEnum"; + + ErrorListener errorListener = new ConsoleErrorReporter(); + + // Parse the XML schema. + SchemaCompiler sc = XJC.createSchemaCompiler(); + sc.setErrorListener(errorListener); + sc.forcePackageName(packageName); + sc.parseSchema(getInputSource(schemaFileName)); + + // Generate the defined class. + S2JJAXBModel model = sc.bind(); + Plugin[] extensions = null; + JCodeModel cm = model.generateCode(extensions, errorListener); + JDefinedClass dc = cm._getClass(someClassName); + assertNotNull(someClassName, dc); + StringWriter swClass = new StringWriter(); + dc.javadoc().generate(new JFormatter(swClass)); + assertNotNull(swClass.toString()); + assertTrue(swClass.toString().contains("Test escape Javadoc on Class < > & ' \" */ Hello World")); + assertTrue(swClass.toString().contains(" *
{@code" + System.lineSeparator() +
+                " * " + System.lineSeparator() +
+                " *   " + System.lineSeparator() +
+                " *     " + System.lineSeparator() +
+                " *       " + System.lineSeparator() +
+                " *         " + System.lineSeparator() +
+                " *         " + System.lineSeparator() +
+                " *         " + System.lineSeparator() +
+                " *         " + System.lineSeparator() +
+                " *         " + System.lineSeparator() +
+                " *         " + System.lineSeparator() +
+                " *         " + System.lineSeparator() +
+                " *         " + System.lineSeparator() +
+                " *       " + System.lineSeparator() +
+                " *     " + System.lineSeparator() +
+                " *   " + System.lineSeparator() +
+                " * " + System.lineSeparator() +
+                " * }
")); + + JDefinedClass ec = cm._getClass(someEnumName); + assertNotNull(someEnumName, ec); + StringWriter swEnum = new StringWriter(); + ec.javadoc().generate(new JFormatter(swEnum)); + assertNotNull(swEnum.toString()); + assertTrue(swEnum.toString().contains("Test escape Javadoc on Enum < > & ' \" */ Hello World")); + assertTrue(swEnum.toString().contains(" *
{@code" + System.lineSeparator() +
+                " * " + System.lineSeparator() +
+                " *   " + System.lineSeparator() +
+                " *     " + System.lineSeparator() +
+                " *     " + System.lineSeparator() +
+                " *   " + System.lineSeparator() +
+                " * " + System.lineSeparator() +
+                " * }
")); + } + private void assertNonEmptyJavadocBlocks(String cmString) throws IOException { int lineNo = 0; try ( LineNumberReader lnr = new LineNumberReader(new StringReader(cmString)) ) { diff --git a/jaxb-ri/xjc/src/test/resources/com/sun/tools/xjc/resources/simple.xsd b/jaxb-ri/xjc/src/test/resources/com/sun/tools/xjc/resources/simple.xsd index 85496fcb4..0de32b6e1 100644 --- a/jaxb-ri/xjc/src/test/resources/com/sun/tools/xjc/resources/simple.xsd +++ b/jaxb-ri/xjc/src/test/resources/com/sun/tools/xjc/resources/simple.xsd @@ -16,7 +16,7 @@ - Test escape Javadoc < > & ' " */ Hello World + Test escape Javadoc on Class < > & ' " */ Hello World @@ -54,7 +54,33 @@ Test escape */ + + + Test enum + + + + + + + Test escape Javadoc on Enum < > & ' " */ Hello World + + + + + + + BAR < enum value + + + + + BAZ > enum value + + + + diff --git a/jaxb-ri/xjc/src/test/resources/schemas/issue1865/simple.xsd b/jaxb-ri/xjc/src/test/resources/schemas/issue1865/simple.xsd new file mode 100644 index 000000000..650cad100 --- /dev/null +++ b/jaxb-ri/xjc/src/test/resources/schemas/issue1865/simple.xsd @@ -0,0 +1,84 @@ + + + + + + + + Test escape Javadoc on Class < > & ' " */ Hello World + + + + + Test escape < + + + + + Test escape > + + + + + Test escape & + + + + + Test escape list < & > + + + + + Test not escape ' + + + + + Test not escape " + + + + + Test escape */ + + + + + Test enum + + + + + + + + Test escape Javadoc on Enum < > & ' " */ Hello World + + + + + + BAR < enum value + + + + + BAZ > enum value + + + + + +