Skip to content

Commit

Permalink
handle xjc XmlEnumValue in DynamicJAXB enum
Browse files Browse the repository at this point in the history
  • Loading branch information
timtatt committed Sep 26, 2024
1 parent ec0c9cd commit a0cf81c
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String, String> 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);
}
Expand Down Expand Up @@ -292,7 +293,7 @@ public static DynamicClassLoader lookup(Session session) {

public static class EnumInfo {
String className;
List<String> literalLabels = new ArrayList<>();
Map<String, String> literalLabels = new HashMap<>();

public EnumInfo(String className) {
this.className = className;
Expand All @@ -302,13 +303,17 @@ public String getClassName() {
return className;
}

public Map<String, String> 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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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('.', '/');
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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]);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -216,9 +212,7 @@ private JavaClass[] createClassModelFromXJC(ArrayList<JDefinedClass> 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<String, JEnumConstant> enumConstants = definedClass.enumConstants();
Object[] enumValues = enumConstants.keySet().toArray();
dynamicClassLoader.addEnum(definedClass.fullName(), enumValues);
dynamicClassLoader.addEnum(definedClass.fullName(), getEnumValues(definedClass));
}
}

Expand All @@ -228,6 +222,23 @@ private JavaClass[] createClassModelFromXJC(ArrayList<JDefinedClass> xjcClasses,
}
}

private Map<String, String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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";
Expand All @@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2018, 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
http://www.eclipse.org/legal/epl-2.0,
or the Eclipse Distribution License v. 1.0 which is available at
http://www.eclipse.org/org/documents/edl-v10.php.
SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
-->

<xs:schema targetNamespace="myNamespace" xmlns:myns="myNamespace" xmlns:xs="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="qualified" elementFormDefault="qualified">

<xs:element name="tax-record">
<xs:complexType>
<xs:sequence>
<xs:element name="period" type="myns:period"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<!-- This test contains exactly 6 enum values to test an ASM boundary case (ICONST) -->
<xs:simpleType name="period">
<xs:restriction base="xs:string">
<xs:enumeration value="QTR1"/>
<xs:enumeration value="QTR2"/>
<xs:enumeration value="QTR3"/>
<xs:enumeration value="QTR4"/>
</xs:restriction>
</xs:simpleType>

</xs:schema>

0 comments on commit a0cf81c

Please sign in to comment.