diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassLoader.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassLoader.java index 12cfa8f3b57..a05171d0585 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassLoader.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassLoader.java @@ -29,10 +29,8 @@ import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.sessions.Session; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** * This custom ClassLoader provides support for dynamically generating classes @@ -96,17 +94,20 @@ public EclipseLinkClassWriter getClassWriter(String className) { } public void addEnum(String className, Object... literalLabels) { + var literalsMap = Arrays.stream(literalLabels) + .collect(Collectors.toMap(Object::toString, Object::toString)); + + addEnum(className, literalsMap); + } + + public void addEnum(String className, Map literalLabels) { EnumInfo enumInfo = enumInfoRegistry.get(className); if (enumInfo == null) { enumInfo = new EnumInfo(className); enumInfoRegistry.put(className, enumInfo); } if (literalLabels != null) { - for (Object literalLabel : literalLabels) { - if (literalLabel != null) { - enumInfo.addLiteralLabel(literalLabel.toString()); - } - } + literalLabels.forEach(enumInfo::addLiteralLabel); } addClass(className); } @@ -292,7 +293,7 @@ public static DynamicClassLoader lookup(Session session) { public static class EnumInfo { String className; - List literalLabels = new ArrayList<>(); + Map literalLabels = new HashMap<>(); public EnumInfo(String className) { this.className = className; @@ -302,13 +303,17 @@ public String getClassName() { return className; } + public Map getEnumValues() { + return literalLabels; + } + public String[] getLiteralLabels() { - return literalLabels.toArray(new String[0]); + return literalLabels.keySet().toArray(new String[0]); } - public void addLiteralLabel(String literalLabel) { - if (!literalLabels.contains(literalLabel) && literalLabel != null) { - literalLabels.add(literalLabel); + public void addLiteralLabel(String literalLabel, String value) { + if (!literalLabels.containsKey(literalLabel) && literalLabel != null) { + literalLabels.put(literalLabel, value); } } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassWriter.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassWriter.java index 4d569450f0b..0d8ba51c11a 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassWriter.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicClassWriter.java @@ -223,7 +223,7 @@ protected void addMethods(ClassWriter cw, String parentClassType) { protected byte[] createEnum(EnumInfo enumInfo) { - String[] enumValues = enumInfo.getLiteralLabels(); + var enumValues = enumInfo.getEnumValues(); String className = enumInfo.getClassName(); String internalClassName = className.replace('.', '/'); @@ -232,8 +232,10 @@ protected byte[] createEnum(EnumInfo enumInfo) { cw.visit(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER + Opcodes.ACC_ENUM, internalClassName, null, "java/lang/Enum", null); // Add the individual enum values - for (String enumValue : enumValues) { - cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC + Opcodes.ACC_ENUM, enumValue, "L" + internalClassName + ";", null, null); + for (var enumValue : enumValues.entrySet()) { + var fv = cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC + Opcodes.ACC_ENUM, enumValue.getKey(), "L" + internalClassName + ";", null, null); + var av = fv.visitAnnotation("Ljakarta/xml/bind/annotation/XmlEnumValue;", true); + av.visit("value", enumValue.getValue()); } // add the synthetic "$VALUES" field @@ -270,8 +272,9 @@ protected byte[] createEnum(EnumInfo enumInfo) { mv = cw.visitMethod(Opcodes.ACC_STATIC, CLINIT, "()V", null, null); int lastCount = 0; - for (int i = 0; i < enumValues.length; i++) { - String enumValue = enumValues[i]; + var enumLiterals = enumInfo.getLiteralLabels(); + for (int i = 0; i < enumLiterals.length; i++) { + String enumValue = enumLiterals[i]; mv.visitTypeInsn(Opcodes.NEW, internalClassName); mv.visitInsn(Opcodes.DUP); mv.visitLdcInsn(enumValue); @@ -296,8 +299,8 @@ protected byte[] createEnum(EnumInfo enumInfo) { } mv.visitTypeInsn(Opcodes.ANEWARRAY, internalClassName); - for (int i = 0; i < enumValues.length; i++) { - String enumValue = enumValues[i]; + for (int i = 0; i < enumLiterals.length; i++) { + String enumValue = enumLiterals[i]; mv.visitInsn(Opcodes.DUP); if (i <= 5) { mv.visitInsn(ICONST[i]); diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicEnumBuilder.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicEnumBuilder.java index 3877d956398..09381eaf309 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicEnumBuilder.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicEnumBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,6 +20,8 @@ import org.eclipse.persistence.mappings.converters.EnumTypeConverter; import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; +import java.util.Map; + public class DynamicEnumBuilder { protected String className; @@ -35,7 +37,7 @@ public DynamicEnumBuilder(String className, AbstractDirectMapping adm, } public void addEnumLiteral(String literalLabel) { - dcl.addEnum(className, literalLabel); + dcl.addEnum(className, Map.of(literalLabel, literalLabel)); EnumTypeConverter converter = (EnumTypeConverter)adm.getConverter(); converter.addConversionValue(literalLabel, literalLabel); } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicTypeBuilder.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicTypeBuilder.java index cf6f7a9e9ce..c3f268d729c 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicTypeBuilder.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/dynamic/DynamicTypeBuilder.java @@ -515,7 +515,7 @@ public void configureSequencing(Sequence sequence, String numberName, String num public DynamicEnumBuilder addEnum(String fieldName, String className, String columnName, DynamicClassLoader dcl) { - dcl.addEnum(className, (Object)null); + dcl.addEnum(className, (Object) null); AbstractDirectMapping adm = addDirectMappingForEnum(fieldName, className, columnName); return new DynamicEnumBuilder(className, adm, dcl); } diff --git a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java index e5a457c0a53..9340306300e 100644 --- a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java +++ b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/JAXBEnumTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -17,6 +17,7 @@ import java.util.EnumSet; import java.util.Iterator; +import jakarta.xml.bind.annotation.XmlEnumValue; import org.eclipse.persistence.exceptions.DescriptorException; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.internal.oxm.mappings.Mapping; @@ -83,7 +84,7 @@ public void initialize(DatabaseMapping mapping, Session session) { if (m_usesOrdinalValues) { addConversionValue(theEnum.ordinal(), theEnum); } else { - addConversionValue(theEnum.name(), theEnum); + addConversionValue(getEnumValue(theEnum), theEnum); } } } @@ -92,6 +93,16 @@ public void initialize(DatabaseMapping mapping, Session session) { super.initialize(mapping, session); } + private String getEnumValue(Enum theEnum) { + try { + var field = theEnum.getClass().getField(theEnum.name()); + var annotation = field.getAnnotation(XmlEnumValue.class); + return annotation != null ? annotation.value() : theEnum.name(); + } catch (NoSuchFieldException exc) { + return theEnum.name(); + } + } + /** * PUBLIC: * Returns true if this converter uses ordinal values for the enum diff --git a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/dynamic/metadata/SchemaMetadata.java b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/dynamic/metadata/SchemaMetadata.java index 7c7182cbc46..17747e05b60 100644 --- a/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/dynamic/metadata/SchemaMetadata.java +++ b/moxy/org.eclipse.persistence.moxy/src/main/java/org/eclipse/persistence/jaxb/dynamic/metadata/SchemaMetadata.java @@ -17,17 +17,17 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; +import com.sun.codemodel.*; import jakarta.xml.bind.JAXBException; + import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.stream.StreamResult; +import jakarta.xml.bind.annotation.XmlEnumValue; import org.eclipse.persistence.dynamic.DynamicClassLoader; import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory; import org.eclipse.persistence.jaxb.javamodel.JavaClass; @@ -45,11 +45,6 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; -import com.sun.codemodel.ClassType; -import com.sun.codemodel.JCodeModel; -import com.sun.codemodel.JDefinedClass; -import com.sun.codemodel.JEnumConstant; -import com.sun.codemodel.JPackage; import com.sun.tools.xjc.Plugin; import com.sun.tools.xjc.api.ErrorListener; import com.sun.tools.xjc.api.S2JJAXBModel; @@ -59,6 +54,7 @@ public class SchemaMetadata extends Metadata { private static final String DEFAULT_SYSTEM_ID = "sysid"; + private static final String XML_ENUM_VALUE_VALUE = "value"; private SchemaCompiler schemaCompiler; @@ -216,9 +212,7 @@ private JavaClass[] createClassModelFromXJC(ArrayList xjcClasses, // If this is an enum, trigger a dynamic class generation, because we won't // be creating a descriptor for it if (definedClass.getClassType().equals(ClassType.ENUM)) { - Map enumConstants = definedClass.enumConstants(); - Object[] enumValues = enumConstants.keySet().toArray(); - dynamicClassLoader.addEnum(definedClass.fullName(), enumValues); + dynamicClassLoader.addEnum(definedClass.fullName(), getEnumValues(definedClass)); } } @@ -228,6 +222,23 @@ private JavaClass[] createClassModelFromXJC(ArrayList xjcClasses, } } + private Map getEnumValues(JDefinedClass definedClass) { + return definedClass.enumConstants() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> + entry.getValue() + .annotations() + .stream() + .filter(annotation -> XmlEnumValue.class.getName().equals(annotation.getAnnotationClass().binaryName())) + .map(annotation -> annotation.getAnnotationMembers().get(XML_ENUM_VALUE_VALUE)) + .filter(value -> value instanceof JAnnotationStringValue) + .map(Object::toString) + .findFirst() + .orElse(entry.getKey())) + ); + } + private static InputSource createInputSourceFromSource(Source aSource) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); StreamResult result = new StreamResult(baos); diff --git a/moxy/org.eclipse.persistence.moxy/src/test/java/org/eclipse/persistence/testing/jaxb/dynamic/DynamicJAXBFromXSDTestCases.java b/moxy/org.eclipse.persistence.moxy/src/test/java/org/eclipse/persistence/testing/jaxb/dynamic/DynamicJAXBFromXSDTestCases.java index f77897a5cfb..756e4838484 100644 --- a/moxy/org.eclipse.persistence.moxy/src/test/java/org/eclipse/persistence/testing/jaxb/dynamic/DynamicJAXBFromXSDTestCases.java +++ b/moxy/org.eclipse.persistence.moxy/src/test/java/org/eclipse/persistence/testing/jaxb/dynamic/DynamicJAXBFromXSDTestCases.java @@ -633,6 +633,26 @@ public void testXmlSchemaType() throws Exception { assertEquals("Unexpected date value.", "1976-02-17", node.getTextContent()); } + public void testXmlEnumWithNumbers() throws Exception { + // Tests XmlEnum and XmlEnumValue + + InputStream inputStream = ClassLoader.getSystemResourceAsStream(RESOURCE_DIR + XMLENUM_NUMBERS); + jaxbContext = DynamicJAXBContextFactory.createContextFromXSD(inputStream, null, null, null); + + DynamicEntity taxRecord = jaxbContext.newDynamicEntity(PACKAGE + "." + TAX_RECORD); + assertNotNull("Could not create Dynamic Entity.", taxRecord); + + Object QTR_1 = jaxbContext.getEnumConstant(PACKAGE + "." + PERIOD, "QTR_1"); + assertNotNull("Could not find enum constant.", QTR_1); + + taxRecord.set("period", QTR_1); + + Document marshalDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + jaxbContext.createMarshaller().marshal(taxRecord, marshalDoc); + + assertEquals("QTR1", marshalDoc.getDocumentElement().getTextContent()); + } + public void testXmlEnum() throws Exception { // Tests XmlEnum and XmlEnumValue @@ -1068,6 +1088,7 @@ private void print(Object o) throws Exception { private static final String XMLELEMENTREF = RESOURCE_DIR + "xmlelementref.xsd"; private static final String XMLSCHEMATYPE = RESOURCE_DIR + "xmlschematype.xsd"; private static final String XMLENUM = RESOURCE_DIR + "xmlenum.xsd"; + private static final String XMLENUM_NUMBERS = RESOURCE_DIR + "xmlenum-numbers.xsd"; private static final String XMLENUM_BIG = RESOURCE_DIR + "xmlenum-big.xsd"; private static final String XMLELEMENTDECL = RESOURCE_DIR + "xmlelementdecl.xsd"; private static final String XMLELEMENTCOLLECTION = RESOURCE_DIR + "xmlelement-collection.xsd"; @@ -1093,6 +1114,8 @@ private void print(Object o) throws Exception { private static final String DEF_PACKAGE = "generated"; private static final String BANK_PACKAGE = "banknamespace"; private static final String PERSON = "Person"; + private static final String TAX_RECORD = "TaxRecord"; + private static final String PERIOD = "Period"; private static final String EMPLOYEE = "Employee"; private static final String INDIVIDUO = "Individuo"; private static final String CDN_CURRENCY = "CdnCurrency"; diff --git a/moxy/org.eclipse.persistence.moxy/src/test/resources/org/eclipse/persistence/testing/jaxb/dynamic/xmlenum-numbers.xsd b/moxy/org.eclipse.persistence.moxy/src/test/resources/org/eclipse/persistence/testing/jaxb/dynamic/xmlenum-numbers.xsd new file mode 100644 index 00000000000..61ebef77d26 --- /dev/null +++ b/moxy/org.eclipse.persistence.moxy/src/test/resources/org/eclipse/persistence/testing/jaxb/dynamic/xmlenum-numbers.xsd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + +