blob: 350f270f856d392b6ef05532483e9732ff959208 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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
*/
// Contributors:
// Denise Smith - January, 2010 - 2.0.1
package org.eclipse.persistence.jaxb.compiler;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlList;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.internal.jaxb.AccessorFactoryWrapper;
import org.eclipse.persistence.internal.jaxb.JaxbClassLoader;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.TypeMappingInfo;
import org.eclipse.persistence.jaxb.javamodel.Helper;
import org.eclipse.persistence.jaxb.javamodel.JavaClass;
import org.eclipse.persistence.jaxb.javamodel.JavaField;
import org.eclipse.persistence.jaxb.javamodel.JavaMethod;
import org.eclipse.persistence.jaxb.javamodel.reflection.JavaClassImpl;
import org.eclipse.persistence.jaxb.xmlmodel.XmlJoinNodes;
/**
* Helper class for code that needs to be shared between AnnotationsProcessor,
* MappingsGenerator, SchemaGenerator
*/
public class CompilerHelper {
public static final String XML_LOCATION_ANNOTATION_NAME = "org.glassfish.jaxb.core.annotation.XmlLocation";
public static final String OLD_XML_LOCATION_ANNOTATION_NAME = "com.sun.xml.bind.annotation.XmlLocation";
public static final String INTERNAL_XML_LOCATION_ANNOTATION_NAME = "com.sun.xml.internal.bind.annotation.XmlLocation";
private static final String XML_ACCESSOR_FACTORY_ANNOTATION_NAME = "org.glassfish.jaxb.runtime.XmlAccessorFactory";
private static final String OLD_ACCESSOR_FACTORY_ANNOTATION_NAME = "com.sun.xml.bind.XmlAccessorFactory";
private static final String INTERNAL_ACCESSOR_FACTORY_ANNOTATION_NAME = "com.sun.xml.internal.bind.XmlAccessorFactory";
private static final String METADATA_MODEL_PACKAGE = "org.eclipse.persistence.jaxb.xmlmodel";
public static Class ACCESSOR_FACTORY_ANNOTATION_CLASS = null;
public static Method ACCESSOR_FACTORY_VALUE_METHOD = null;
public static Class OLD_ACCESSOR_FACTORY_ANNOTATION_CLASS = null;
public static Method OLD_ACCESSOR_FACTORY_VALUE_METHOD = null;
public static Class INTERNAL_ACCESSOR_FACTORY_ANNOTATION_CLASS = null;
public static Method INTERNAL_ACCESSOR_FACTORY_VALUE_METHOD = null;
public static Class XML_LOCATION_ANNOTATION_CLASS = null;
public static Class OLD_XML_LOCATION_ANNOTATION_CLASS = null;
public static Class INTERNAL_XML_LOCATION_ANNOTATION_CLASS = null;
private static JAXBContext xmlBindingsModelContext;
static {
try {
ACCESSOR_FACTORY_ANNOTATION_CLASS = PrivilegedAccessHelper.getClassForName(XML_ACCESSOR_FACTORY_ANNOTATION_NAME, true, CompilerHelper.class.getClassLoader());
ACCESSOR_FACTORY_VALUE_METHOD = PrivilegedAccessHelper.getDeclaredMethod(ACCESSOR_FACTORY_ANNOTATION_CLASS, "value", new Class[]{});
} catch (Exception ex) {
}
try {
XML_LOCATION_ANNOTATION_CLASS = PrivilegedAccessHelper.getClassForName(XML_LOCATION_ANNOTATION_NAME, true, CompilerHelper.class.getClassLoader());
} catch (Exception ex) {
}
try {
OLD_XML_LOCATION_ANNOTATION_CLASS = PrivilegedAccessHelper.getClassForName(OLD_XML_LOCATION_ANNOTATION_NAME, true, CompilerHelper.class.getClassLoader());
} catch (Exception ex) {
}
try{
OLD_ACCESSOR_FACTORY_ANNOTATION_CLASS = PrivilegedAccessHelper.getClassForName(OLD_ACCESSOR_FACTORY_ANNOTATION_NAME);
OLD_ACCESSOR_FACTORY_VALUE_METHOD = PrivilegedAccessHelper.getDeclaredMethod(OLD_ACCESSOR_FACTORY_ANNOTATION_CLASS, "value", new Class[]{});
} catch (Exception ex) {
}
try{
INTERNAL_ACCESSOR_FACTORY_ANNOTATION_CLASS = PrivilegedAccessHelper.getClassForName(INTERNAL_ACCESSOR_FACTORY_ANNOTATION_NAME);
INTERNAL_ACCESSOR_FACTORY_VALUE_METHOD = PrivilegedAccessHelper.getDeclaredMethod(INTERNAL_ACCESSOR_FACTORY_ANNOTATION_CLASS, "value", new Class[]{});
} catch (Exception ex) {
}
try{
INTERNAL_XML_LOCATION_ANNOTATION_CLASS = PrivilegedAccessHelper.getClassForName(INTERNAL_XML_LOCATION_ANNOTATION_NAME);
}catch (Exception ex) {
}
}
/**
* If 2 TypeMappingInfo objects would generate the same generated class (and
* therefore complex type) then return the existing class otherwise return
* null.
*/
static Class getExisitingGeneratedClass(TypeMappingInfo tmi, Map<TypeMappingInfo, Class> typeMappingInfoToGeneratedClasses, Map<TypeMappingInfo, Class> typeMappingInfoToAdapterClasses, ClassLoader loader) {
Iterator<Map.Entry<TypeMappingInfo, Class>> iter = typeMappingInfoToGeneratedClasses.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<TypeMappingInfo, Class> next = iter.next();
TypeMappingInfo nextTMI = next.getKey();
if (CompilerHelper.generatesSameComplexType(tmi, nextTMI, loader)) {
return next.getValue();
}
}
return null;
}
/**
* Return true if the two TypeMappingInfoObjects should generate the same
* complex type in the XSD
*/
private static boolean generatesSameComplexType(TypeMappingInfo tmi1, TypeMappingInfo tmi2, ClassLoader loader) {
org.eclipse.persistence.jaxb.xmlmodel.XmlElement element1 = null;
org.eclipse.persistence.jaxb.xmlmodel.XmlElement element2 = null;
if (tmi1.getXmlElement() != null) {
element1 = getXmlElement(tmi1.getXmlElement(), loader);
}
if (tmi2.getXmlElement() != null) {
element2 = getXmlElement(tmi2.getXmlElement(), loader);
}
Type actualType1 = getActualType(tmi1, element1);
Type actualType2 = getActualType(tmi2, element2);
if (!areTypesEqual(actualType1, actualType2)) {
return false;
}
if (!hasSameClassName(tmi1,tmi2)) {
return false;
}
boolean isXmlList1 = isXmlList(tmi1, element1);
boolean isXmlList2 = isXmlList(tmi2, element2);
if (isXmlList1) {
if (!isXmlList2) {
return false;
}
} else if (isXmlList2) {
return false;
}
return true;
}
/**
* Return true if tmi1 and tmi2 are instances of Class and have same class name.
*
* @param tmi1 instance of TypeMappingInfo
* @param tmi2 instance of typeMappingInfo
* @return true if TypeMappingInfos are instances of Class and have same class name
*/
private static boolean hasSameClassName(TypeMappingInfo tmi1, TypeMappingInfo tmi2) {
Type type1 = tmi1.getType();
Type type2 = tmi2.getType();
if (type1 != null && type2 == null) {
return false;
} else if (type1 == null && type2 != null) {
return false;
} else if (type1 instanceof Class && type2 instanceof Class){
String typeName1 = ((Class)type1).getName();
String typeName2 = ((Class)type2).getName();
if (!typeName1.equals(typeName2)) {
return false;
}
}
return true;
}
/**
* Return if this TypeMappingInfo has an XmlList annotation or is specified
* to be an xmlList in an XMLElement override
*/
private static boolean isXmlList(TypeMappingInfo tmi, org.eclipse.persistence.jaxb.xmlmodel.XmlElement element) {
if (element != null && element.isXmlList()) {
return true;
}
if (tmi.getAnnotations() != null) {
for (int i = 0; i < tmi.getAnnotations().length; i++) {
java.lang.annotation.Annotation nextAnnotation = tmi.getAnnotations()[i];
if (nextAnnotation != null && nextAnnotation instanceof XmlList) {
return true;
}
}
}
return false;
}
/**
* Return true if the Types are equal. Accounts for Classes and
* Parameterized types or any combintation of the two.
*/
private static boolean areTypesEqual(java.lang.reflect.Type type, java.lang.reflect.Type type2) {
// handle null
if (type == null) {
return type2 == null;
} else if (type instanceof Class) {
if (type2 instanceof ParameterizedType) {
java.lang.reflect.Type rawType = ((ParameterizedType) type2).getRawType();
if (!areTypesEqual(type, rawType)) {
return false;
}
java.lang.reflect.Type[] args = ((ParameterizedType) type2).getActualTypeArguments();
for (int i = 0; i < args.length; i++) {
Type argType = getActualArgumentType(args[i]);
if (!areTypesEqual(Object.class, argType)) {
return false;
}
}
return true;
} else if (type2 instanceof Class) {
return type.equals(type2);
} else {
return false;
}
} else if (type instanceof ParameterizedType) {
if (type2 instanceof Class) {
java.lang.reflect.Type rawType = ((ParameterizedType) type).getRawType();
if (!areTypesEqual(type2, rawType)) {
return false;
}
java.lang.reflect.Type[] args = ((ParameterizedType) type).getActualTypeArguments();
for (int i = 0; i < args.length; i++) {
Type argType = getActualArgumentType(args[i]);
if (!areTypesEqual(Object.class, argType)) {
return false;
}
}
return true;
} else if (type2 instanceof ParameterizedType) {
// compare raw type
if (!areTypesEqual(((ParameterizedType) type).getRawType(),
((ParameterizedType) type2).getRawType())) {
return false;
}
java.lang.reflect.Type[] ta1 = ((ParameterizedType) type).getActualTypeArguments();
java.lang.reflect.Type[] ta2 = ((ParameterizedType) type2).getActualTypeArguments();
// check array length
if (ta1.length != ta2.length) {
return false;
}
for (int i = 0; i < ta1.length; i++) {
Type componentType1 = getActualArgumentType(ta1[i]);
Type componentType2 = getActualArgumentType(ta2[i]);
if (!areTypesEqual(componentType1, componentType2)) {
return false;
}
}
return true;
} else {
return false;
}
}
return false;
}
private static Type getActualArgumentType(Type argument){
if(argument instanceof WildcardType){
Type[] upperBounds = ((WildcardType)argument).getUpperBounds();
if(upperBounds != null && upperBounds.length >0){
return upperBounds[0];
}else{
return Object.class;
}
}else if (argument instanceof GenericArrayType){
return ((GenericArrayType)argument).getGenericComponentType();
}
return argument;
}
/**
* Convenience method for creating an XmlElement object based on a given
* Element. The method will load the eclipselink metadata model and
* unmarshal the Element. This assumes that the Element represents an
* xml-element to be unmarshalled.
*
*/
static org.eclipse.persistence.jaxb.xmlmodel.XmlElement getXmlElement(org.w3c.dom.Element xmlElementNode, ClassLoader classLoader) {
try {
Unmarshaller unmarshaller = CompilerHelper.getXmlBindingsModelContext().createUnmarshaller();
JAXBElement<org.eclipse.persistence.jaxb.xmlmodel.XmlElement> jelt = unmarshaller.unmarshal(xmlElementNode, org.eclipse.persistence.jaxb.xmlmodel.XmlElement.class);
return jelt.getValue();
} catch (jakarta.xml.bind.JAXBException jaxbEx) {
throw org.eclipse.persistence.exceptions.JAXBException.couldNotUnmarshalMetadata(jaxbEx);
}
}
/**
* If adapter class is null return null If there is a marshal method that
* returns something other than Object on the adapter class return the
* return type of that method Otherwise return Object.class
*/
static Class getTypeFromAdapterClass(Class adapterClass) {
if (adapterClass != null) {
Class<Object> declJavaType = Object.class;
// look for marshal method
Method[] tacMethods = PrivilegedAccessHelper.getMethods(adapterClass);
for (int i = 0; i < tacMethods.length; i++) {
Method method = tacMethods[i];
if (method.getName().equals("marshal")) {
Class<Object> returnType = PrivilegedAccessHelper.getMethodReturnType(method);
if (!(returnType == declJavaType)) {
declJavaType = returnType;
return declJavaType;
}
}
}
return declJavaType;
}
return null;
}
/**
* If adapter class is null return null If there is a marshal method that
* returns something other than Object on the adapter class return the
* return type of that method Otherwise return Object.class
*/
public static JavaClass getTypeFromAdapterClass(JavaClass adapterClass, Helper helper) {
if (adapterClass != null) {
//JavaClass declJavaType = Object.class;
JavaClass declJavaType = helper.getJavaClass(Object.class);
// look for marshal method
Object[] tacMethods = adapterClass.getMethods().toArray();
for (int i = 0; i < tacMethods.length; i++) {
JavaMethod method = (JavaMethod)tacMethods[i];
if (method.getName().equals("marshal")) {
JavaClass returnType = method.getReturnType();
if (!(returnType.getQualifiedName().equals(declJavaType.getQualifiedName()))) {
declJavaType = returnType;
return declJavaType;
}
}
}
return declJavaType;
}
return null;
}
/**
* Return true if the type is a Collection, List or Set
*/
private static boolean isCollectionType(Type theType) {
if (theType instanceof Class) {
if (Collection.class.isAssignableFrom((Class) theType)
|| List.class.isAssignableFrom((Class) theType)
|| Set.class.isAssignableFrom((Class) theType)) {
return true;
}
return false;
} else if (theType instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) theType).getRawType();
return isCollectionType(rawType);
}
return false;
}
/**
* The actual type accounts for adapter classes or xmlelemnt types specified
* in either an annotation or an XML override
*
*/
static Type getActualType(TypeMappingInfo tmi, org.eclipse.persistence.jaxb.xmlmodel.XmlElement element) {
try {
if (element == null) {
if (tmi.getAnnotations() != null) {
for (int i = 0; i < tmi.getAnnotations().length; i++) {
java.lang.annotation.Annotation nextAnnotation = tmi.getAnnotations()[i];
if (nextAnnotation != null) {
if (nextAnnotation instanceof XmlJavaTypeAdapter) {
Class typeClass = ((XmlJavaTypeAdapter) nextAnnotation).type();
if (typeClass.getName().equals("jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter$DEFAULT")) {
Class<? extends XmlAdapter> adapterClass = ((XmlJavaTypeAdapter) nextAnnotation).value();
return getTypeFromAdapterClass(adapterClass);
}
return typeClass;
} else if (nextAnnotation instanceof XmlElement) {
Class typeClass = ((XmlElement) nextAnnotation).type();
if (!typeClass.getName().equals("jakarta.xml.bind.annotation.XmlElement.DEFAULT")) {
final Type tmiType = tmi.getType();
if (isCollectionType(tmiType)) {
final Class itemType = typeClass;
Type parameterizedType = new ParameterizedType() {
Type[] typeArgs = { itemType };
@Override
public Type[] getActualTypeArguments() {
return typeArgs;
}
@Override
public Type getOwnerType() {
return null;
}
@Override
public Type getRawType() {
return tmiType;
}
};
return parameterizedType;
} else {
return typeClass;
}
}
}
}
}
}
return tmi.getType();
} else {
// if it has an XMLElement specified
// Check for an adapater, then check for XMLElement.type
if (element.getXmlJavaTypeAdapter() != null) {
String actualType = element.getXmlJavaTypeAdapter().getType();
if (actualType != null && !actualType.equals("jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter.DEFAULT")) {
return PrivilegedAccessHelper.getClassForName(actualType);
} else {
String adapterClassName = element.getXmlJavaTypeAdapter().getValue();
Class<Object> adapterClass = PrivilegedAccessHelper.getClassForName(adapterClassName);
return getTypeFromAdapterClass(adapterClass);
}
}
if (!(element.getType().equals("jakarta.xml.bind.annotation.XmlElement.DEFAULT"))) {
String actualType = element.getType();
final Type tmiType = tmi.getType();
if (isCollectionType(tmiType)) {
final Class<Object> itemType = PrivilegedAccessHelper.getClassForName(actualType);
Type parameterizedType = new ParameterizedType() {
Type[] typeArgs = { itemType };
@Override
public Type[] getActualTypeArguments() {
return typeArgs;
}
@Override
public Type getOwnerType() {
return null;
}
@Override
public Type getRawType() {
return tmiType;
}
};
return parameterizedType;
} else {
return PrivilegedAccessHelper.getClassForName(actualType);
}
}
return tmi.getType();
}
} catch (Exception e) {
return tmi.getType();
}
}
/**
* The method will load the eclipselink metadata model and return the
* corresponding JAXBContext
*/
public static JAXBContext getXmlBindingsModelContext() {
if (xmlBindingsModelContext == null) {
try {
xmlBindingsModelContext = (JAXBContext) JAXBContextFactory.createContext(METADATA_MODEL_PACKAGE,CompilerHelper.class.getClassLoader());
} catch (JAXBException e) {
throw org.eclipse.persistence.exceptions.JAXBException.couldNotCreateContextForXmlModel(e);
}
if (xmlBindingsModelContext == null) {
throw org.eclipse.persistence.exceptions.JAXBException.couldNotCreateContextForXmlModel();
}
}
return xmlBindingsModelContext;
}
public static JavaClass getNextMappedSuperClass(JavaClass cls, Map<String, TypeInfo> typeInfo, Helper helper) {
JavaClass superClass = cls.getSuperclass();
if(superClass == null || helper.isBuiltInJavaType(cls) || superClass.getRawName().equals("java.lang.Object")){
return null;
}
TypeInfo parentTypeInfo = typeInfo.get(superClass.getQualifiedName());
if(parentTypeInfo == null || parentTypeInfo.isTransient()) {
return getNextMappedSuperClass(superClass, typeInfo, helper);
}
return superClass;
}
public static void addClassToClassLoader(JavaClass cls, ClassLoader loader) {
if(loader.getClass() == JaxbClassLoader.class && cls.getClass() == JavaClassImpl.class) {
Class wrappedClass = ((JavaClassImpl)cls).getJavaClass();
((JaxbClassLoader)loader).putClass(wrappedClass.getName(), wrappedClass);
}
}
static boolean hasNonAttributeJoinNodes(Property property) {
if(property.isSetXmlJoinNodes()) {
for(XmlJoinNodes.XmlJoinNode next: property.getXmlJoinNodes().getXmlJoinNode()) {
if(!(next.getXmlPath().startsWith("@"))) {
return true;
}
}
} else if(property.isSetXmlJoinNodesList()) {
for(XmlJoinNodes nextNodes:property.getXmlJoinNodesList()) {
for(XmlJoinNodes.XmlJoinNode next: nextNodes.getXmlJoinNode()) {
if(!(next.getXmlPath().startsWith("@"))) {
return true;
}
}
}
}
return false;
}
public static Object createAccessorFor(JavaClass jClass, Property property, Helper helper, AccessorFactoryWrapper accessorFactory) {
if(!(jClass.getClass() == JavaClassImpl.class)) {
return null;
}
Class beanClass = ((JavaClassImpl)jClass).getJavaClass();
if(property.isMethodProperty()) {
try {
Method getMethod = null;
Method setMethod = null;
if(property.getGetMethodName() != null) {
getMethod = PrivilegedAccessHelper.getMethod(beanClass, property.getGetMethodName(), new Class[]{}, true);
}
if(property.getSetMethodName() != null) {
String setMethodParamTypeName = property.getType().getName();
JavaClassImpl paramType = (JavaClassImpl)helper.getJavaClass(setMethodParamTypeName);
Class[] setMethodParams = new Class[]{paramType.getJavaClass()};
setMethod = PrivilegedAccessHelper.getMethod(beanClass, property.getSetMethodName(), setMethodParams, true);
}
return accessorFactory.createPropertyAccessor(beanClass, getMethod, setMethod);
} catch(Exception ex) {}
} else {
try {
Field field = PrivilegedAccessHelper.getField(beanClass, ((JavaField)property.getElement()).getName(), true);
return accessorFactory.createFieldAccessor(beanClass, field, property.isReadOnly());
} catch(Exception ex) {
ex.printStackTrace();
}
}
return null;
}
public static boolean isSimpleType(TypeInfo info) {
if (info.isEnumerationType()) {
return true;
}
Property xmlValueProperty = info.getXmlValueProperty();
boolean hasMappedAttributes = false;
for (Property nextProp : info.getPropertyList()) {
if (nextProp.isAttribute() && !nextProp.isTransient()) {
hasMappedAttributes = true;
}
}
hasMappedAttributes = hasMappedAttributes || info.hasPredicateProperties();
return (xmlValueProperty != null && !hasMappedAttributes);
}
}