blob: 14b048f63c37f8e25d8742756d3f8415a5e809b7 [file] [log] [blame]
/*
* Copyright (c) 2011, 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:
// Rick Barkhouse - 2.2 - Initial implementation
package org.eclipse.persistence.jaxb.javamodel.oxm;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import jakarta.xml.bind.JAXBElement;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.jaxb.compiler.XMLProcessor;
import org.eclipse.persistence.jaxb.javamodel.JavaAnnotation;
import org.eclipse.persistence.jaxb.javamodel.JavaClass;
import org.eclipse.persistence.jaxb.javamodel.JavaClassInstanceOf;
import org.eclipse.persistence.jaxb.javamodel.JavaConstructor;
import org.eclipse.persistence.jaxb.javamodel.JavaField;
import org.eclipse.persistence.jaxb.javamodel.JavaMethod;
import org.eclipse.persistence.jaxb.javamodel.JavaModel;
import org.eclipse.persistence.jaxb.javamodel.JavaPackage;
import org.eclipse.persistence.jaxb.xmlmodel.JavaAttribute;
import org.eclipse.persistence.jaxb.xmlmodel.JavaType;
import org.eclipse.persistence.jaxb.xmlmodel.JavaType.JavaAttributes;
import org.eclipse.persistence.jaxb.xmlmodel.XmlAnyAttribute;
import org.eclipse.persistence.jaxb.xmlmodel.XmlAnyElement;
import org.eclipse.persistence.jaxb.xmlmodel.XmlAttribute;
import org.eclipse.persistence.jaxb.xmlmodel.XmlElement;
import org.eclipse.persistence.jaxb.xmlmodel.XmlElementRef;
import org.eclipse.persistence.jaxb.xmlmodel.XmlElements;
import org.eclipse.persistence.jaxb.xmlmodel.XmlInverseReference;
import org.eclipse.persistence.jaxb.xmlmodel.XmlJoinNodes;
import org.eclipse.persistence.jaxb.xmlmodel.XmlValue;
/**
* INTERNAL:
* <p>
* <b>Purpose:</b> <code>JavaClass</code> implementation wrapping MOXy's <code>xmlmodel.JavaType</code>.
* Used when bootstrapping a <code>DynamicJAXBContext</code> from XML Bindings.
* </p>
*
* <p>
* <b>Responsibilities:</b>
* </p>
* <ul>
* <li>Provide Class information from the underlying <code>JavaType</code>.</li>
* </ul>
*
* @since EclipseLink 2.2
*
* @see org.eclipse.persistence.jaxb.javamodel.JavaClass
* @see org.eclipse.persistence.jaxb.xmlmodel.JavaType
*/
public class OXMJavaClassImpl implements JavaClass {
private JavaType javaType;
private String javaName;
private List<String> enumValues;
private JavaModel javaModel;
/**
* Construct a new instance of <code>OXMJavaClassImpl</code>.
*
* @param aJavaType - the XJC <code>JavaType</code> to be wrapped.
*/
public OXMJavaClassImpl(JavaType aJavaType) {
this.javaType = aJavaType;
}
/**
* Construct a new instance of <code>OXMJavaClassImpl</code>.
*
* @param aJavaTypeName - the name of the JavaType to create.
*/
public OXMJavaClassImpl(String aJavaTypeName) {
this.javaName = aJavaTypeName;
}
/**
* Construct a new instance of <code>OXMJavaClassImpl</code>
* representing a Java <code>enum</code>.
*
* @param aJavaTypeName - the name of the JavaType to create.
* @param enumValues - the list of values for this <code>enum</code>.
*/
public OXMJavaClassImpl(String aJavaTypeName, List<String> enumValues) {
this.javaName = aJavaTypeName;
this.enumValues = enumValues;
}
// ========================================================================
/**
* Return the "actual" type from a parameterized type. For example, if this
* <code>JavaClass</code> represents <code>List&lt;Employee</code>, this method will return the
* <code>Employee</code> <code>JavaClass</code>.
*
* @return a <code>Collection</code> containing the actual type's <code>JavaClass</code>.
*/
@Override
public Collection<JavaClass> getActualTypeArguments() {
Object jType = null;
if (this.javaType != null) {
jType = this.javaType;
} else {
try {
Class<?> jTypeClass = PrivilegedAccessHelper.getClassForName(this.javaName);
jType = PrivilegedAccessHelper.newInstanceFromClass(jTypeClass);
} catch (Exception e) {
return new ArrayList<JavaClass>();
}
}
ArrayList<JavaClass> argCollection = new ArrayList<JavaClass>();
if (jType instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) jType;
Type[] params = pType.getActualTypeArguments();
for (Type type : params) {
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
argCollection.add(this.javaModel.getClass(pt.getRawType().getClass()));
} else if (type instanceof WildcardType) {
Type[] upperTypes = ((WildcardType) type).getUpperBounds();
if (upperTypes.length >0) {
Type upperType = upperTypes[0];
if (upperType instanceof Class<?>) {
argCollection.add(this.javaModel.getClass(upperType.getClass()));
}
}
} else if (type instanceof Class<?>) {
argCollection.add(this.javaModel.getClass(type.getClass()));
} else if (type instanceof GenericArrayType) {
Class<?> genericTypeClass = (Class<?>) ((GenericArrayType) type).getGenericComponentType();
genericTypeClass = java.lang.reflect.Array.newInstance(genericTypeClass, 0).getClass();
argCollection.add(this.javaModel.getClass(genericTypeClass.getClass()));
}
}
}
return argCollection;
}
/**
* If this <code>JavaClass</code> is an array type, return the type of the
* array components.
*
* @return always returns <code>null</code>, as <code>JavaTypes</code> do not represent arrays.
*/
@Override
public JavaClass getComponentType() {
return null;
}
/**
* Return the <code>JavaConstructor</code> for this <code>JavaClass</code> that has the
* provided parameter types.
*
* @param parameterTypes the parameter list used to identify the constructor.
*
* @return the <code>JavaConstructor</code> with the signature matching parameterTypes.
*/
@Override
public JavaConstructor getConstructor(JavaClass[] parameterTypes) {
return new OXMJavaConstructorImpl(this);
}
/**
* Return all of the <code>JavaConstructors</code> for this JavaClass.
*
* @return A <code>Collection</code> containing this <code>JavaClass'</code> <code>JavaConstructors</code>.
*/
@Override
public Collection<JavaConstructor> getConstructors() {
ArrayList<JavaConstructor> constructors = new ArrayList<JavaConstructor>(1);
constructors.add(new OXMJavaConstructorImpl(this));
return constructors;
}
/**
* Return this <code>JavaClass'</code> inner classes.
*
* @return always returns an empty <code>ArrayList</code> as <code>JavaTypes</code> do not represent inner classes.
*/
@Override
public Collection<JavaClass> getDeclaredClasses() {
return new ArrayList<JavaClass>();
}
/**
* Return the declared <code>JavaConstructor</code> for this <code>JavaClass</code>.
*
* @return the <code>JavaConstructor</code> for this <code>JavaClass</code>.
*/
@Override
public JavaConstructor getDeclaredConstructor(JavaClass[] parameterTypes) {
return new OXMJavaConstructorImpl(this);
}
/**
* Return all of the declared <code>JavaConstructors</code> for this <code>JavaClass</code>.
*
* @return A <code>Collection</code> containing this <code>JavaClass'</code> <code>JavaConstructors</code>.
*/
@Override
public Collection<JavaConstructor> getDeclaredConstructors() {
ArrayList<JavaConstructor> constructors = new ArrayList<JavaConstructor>(1);
constructors.add(new OXMJavaConstructorImpl(this));
return constructors;
}
/**
* Return the declared <code>JavaField</code> for this <code>JavaClass</code>, identified
* by <code>fieldName</code>.
*
* @param name the name of the <code>JavaField</code> to return.
*
* @return the <code>JavaField</code> named <code>fieldName</code> from this <code>JavaClass</code>.
*/
@Override
public JavaField getDeclaredField(String name) {
Collection<JavaField> allFields = getDeclaredFields();
for (Iterator<JavaField> iterator = allFields.iterator(); iterator.hasNext();) {
JavaField field = iterator.next();
if (field.getName().equals(name)) {
return field;
}
}
return null;
}
/**
* Return all of the declared <code>JavaFields</code> for this <code>JavaClass</code>.
*
* @return A <code>Collection</code> containing this <code>JavaClass'</code> <code>JavaFields</code>.
*/
@Override
public Collection<JavaField> getDeclaredFields() {
List<JavaField> fieldsToReturn = new ArrayList<JavaField>();
if (this.enumValues != null) {
for (Iterator<String> iterator = this.enumValues.iterator(); iterator.hasNext();) {
fieldsToReturn.add(new OXMJavaFieldImpl(iterator.next(), JAVA_LANG_OBJECT, this));
}
} else {
JavaAttributes javaAttributes = this.javaType.getJavaAttributes();
if(null != javaAttributes) {
List<JAXBElement<? extends JavaAttribute>> fields = javaAttributes.getJavaAttribute();
for (Iterator<JAXBElement<? extends JavaAttribute>> iterator = fields.iterator(); iterator.hasNext();) {
JAXBElement<? extends JavaAttribute> jaxbElement = iterator.next();
JavaAttribute att = jaxbElement.getValue();
if (att instanceof XmlElement) {
XmlElement xme = (XmlElement) att;
String fieldName = xme.getJavaAttribute();
String fieldType = xme.getType();
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
} else if (att instanceof XmlElements) {
XmlElements xmes = (XmlElements) att;
String fieldName = xmes.getJavaAttribute();
String fieldType = JAVA_LANG_OBJECT;
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
} else if (att instanceof XmlElementRef) {
XmlElementRef xmer = (XmlElementRef) att;
String fieldName = xmer.getJavaAttribute();
String fieldType = xmer.getType();
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
} else if (att instanceof XmlAttribute) {
XmlAttribute xma = (XmlAttribute) att;
String fieldName = xma.getJavaAttribute();
String fieldType = xma.getType();
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
} else if (att instanceof XmlValue) {
XmlValue xmv = (XmlValue) att;
String fieldName = xmv.getJavaAttribute();
String fieldType = xmv.getType();
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
} else if (att instanceof XmlAnyElement) {
XmlAnyElement xmae = (XmlAnyElement) att;
String fieldName = xmae.getJavaAttribute();
String fieldType = JAVA_LANG_OBJECT;
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
} else if (att instanceof XmlAnyAttribute) {
XmlAnyAttribute xmaa = (XmlAnyAttribute) att;
String fieldName = xmaa.getJavaAttribute();
String fieldType = JAVA_UTIL_MAP;
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
} else if (att instanceof XmlJoinNodes) {
XmlJoinNodes xmjn = (XmlJoinNodes) att;
String fieldName = xmjn.getJavaAttribute();
String fieldType = xmjn.getType();
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
} else if (att instanceof XmlInverseReference) {
XmlInverseReference xmir = (XmlInverseReference) att;
String fieldName = xmir.getJavaAttribute();
String fieldType = xmir.getType();
fieldsToReturn.add(new OXMJavaFieldImpl(fieldName, fieldType, this));
}
}
}
}
return fieldsToReturn;
}
/**
* Return the declared <code>JavaMethod</code> for this <code>JavaClass</code>,
* identified by <code>name</code>, with the signature matching <code>args</code>.
*
* @param name the name of the <code>JavaMethod</code> to return.
* @param args the parameter list used to identify the method.
*
* @return always returns <code>null</code>, as <code>JavaTypes</code> do not have methods.
*/
@Override
public JavaMethod getDeclaredMethod(String name, JavaClass[] args) {
return null;
}
/**
* Return all of the declared <code>JavaMethods</code> for this <code>JavaClass</code>.
*
* @return always returns an empty <code>ArrayList</code>, as <code>JavaTypes</code> do not have methods.
*/
@Override
public Collection<JavaMethod> getDeclaredMethods() {
return new ArrayList<JavaMethod>();
}
/**
* Return the <code>JavaMethod</code> for this <code>JavaClass</code>,
* identified by <code>name</code>, with the signature matching <code>args</code>.
*
* @param name the name of the <code>JavaMethod</code> to return.
* @param args the parameter list used to identify the method.
*
* @return always returns <code>null</code>, as <code>JavaTypes</code> do not have methods.
*/
@Override
public JavaMethod getMethod(String name, JavaClass[] args) {
return null;
}
/**
* Return all of the <code>JavaMethods</code> for this <code>JavaClass</code>.
*
* @return always returns an empty <code>ArrayList</code>, as <code>JavaTypes</code> do not have methods.
*/
@Override
public Collection<JavaMethod> getMethods() {
return new ArrayList<JavaMethod>();
}
/**
* Returns the Java language modifiers for this <code>JavaClass</code>, encoded in an integer.
*
* @return always returns <code>0</code> as <code>JavaTypes</code> do not have modifiers.
*
* @see java.lang.reflect.Modifier
*/
@Override
public int getModifiers() {
return 0;
}
/**
* Returns the name of this <code>JavaClass</code>.
*
* @return the <code>String</code> name of this <code>JavaClass</code>.
*/
@Override
public String getName() {
if (this.javaType != null) {
return this.javaType.getName();
}
return this.javaName;
}
/**
* Returns the <code>JavaPackage</code> that this <code>JavaClass</code> belongs to.
*
* @return the <code>JavaPackage</code> of this <code>JavaClass</code>.
*/
@Override
public JavaPackage getPackage() {
return new OXMJavaPackageImpl(getPackageName());
}
/**
* Returns the package name of this <code>JavaClass</code>.
*
* @return the <code>String</code> name of this <code>JavaClass'</code> <code>JavaPackage</code>.
*/
@Override
public String getPackageName() {
int lastDotIndex = getQualifiedName().lastIndexOf('.');
if (lastDotIndex == -1) {
return EMPTY_STRING;
}
return getQualifiedName().substring(0, lastDotIndex);
}
/**
* Returns the fully-qualified name of this <code>JavaClass</code>.
*
* @return the <code>String</code> name of this <code>JavaClass</code>.
*/
@Override
public String getQualifiedName() {
return getName();
}
/**
* Returns the raw name of this <code>JavaClass</code>. Array types will
* have "[]" appended to the name.
*
* @return the <code>String</code> raw name of this <code>JavaClass</code>.
*/
@Override
public String getRawName() {
return getName();
}
/**
* Returns the super class of this <code>JavaClass</code>.
*
* @return <code>JavaClass</code> representing the super class of this <code>JavaClass</code>.
*/
@Override
public JavaClass getSuperclass() {
if (this.javaModel == null) {
return null;
}
if (this.javaType != null) {
if (!(this.javaType.getSuperType().equals(XMLProcessor.DEFAULT))) {
return this.javaModel.getClass(javaType.getSuperType());
}
}
return this.javaModel.getClass(JAVA_LANG_OBJECT);
}
@Override
public Type[] getGenericInterfaces() {
return new Type[0];
}
@Override
public Type getGenericSuperclass() {
return null;
}
/**
* Indicates if this <code>JavaClass</code> has actual type arguments, i.e. is a
* parameterized type (for example, <code>List&lt;Employee</code>).
*
* @return always returns <code>false</code> as <code>JavaTypes</code> are not parameterized.
*/
@Override
public boolean hasActualTypeArguments() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is <code>abstract</code>.
*
* @return always returns <code>false</code> as <code>JavaTypes</code> are never <code>abstract</code>.
*/
@Override
public boolean isAbstract() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is an <code>Annotation</code>.
*
* @return always returns <code>false</code> as <code>JavaTypes</code> are never <code>Annotations</code>.
*/
@Override
public boolean isAnnotation() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is an Array type.
*
* @return always returns <code>false</code>, as <code>JavaTypes</code> do not represent arrays.
*/
@Override
public boolean isArray() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is either the same as, or is a superclass of,
* the <code>javaClass</code> argument.
*
* @param arg0 the <code>Class</code> to test.
*
* @return <code>true</code> if this <code>JavaClass</code> is assignable from
* <code>javaClass</code>, otherwise <code>false</code>.
*
* @see java.lang.Class#isAssignableFrom(Class)
*/
@Override
@SuppressWarnings("unchecked")
public boolean isAssignableFrom(JavaClass arg0) {
String thisJavaName = EMPTY_STRING;
String argJavaName = arg0.getName();
if (this.javaName != null) {
thisJavaName = this.javaName;
} else {
thisJavaName = this.javaType.getName();
}
if (thisJavaName.startsWith(JAVA) && argJavaName.startsWith(JAVA)) {
// Only try class lookup if this is a JDK class, because
// we won't ever find classes for dynamically generated types.
try {
Class<Object> thisClass = PrivilegedAccessHelper.getClassForName(thisJavaName);
Class<Object> argClass = PrivilegedAccessHelper.getClassForName(argJavaName);
return thisClass.isAssignableFrom(argClass);
} catch (Exception e) {
return false;
}
} else {
return thisJavaName.equals(argJavaName);
}
}
/**
* Indicates if this <code>JavaClass</code> is an <code>enum</code>.
*
* @return <code>true</code> if this <code>JavaClass</code> is an <code>enum</code>, otherwise <code>false</code>.
*/
@Override
public boolean isEnum() {
return this.enumValues != null;
}
/**
* Indicates if this <code>JavaClass</code> is <code>final</code>.
*
* @return <code>true</code> if this <code>JavaClass</code> is <code>final</code>, otherwise <code>false</code>.
*/
@Override
public boolean isFinal() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is an <code>interface</code>.
*
* @return <code>true</code> if this <code>JavaClass</code> is an <code>interface</code>, otherwise <code>false</code>.
*/
@Override
public boolean isInterface() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is an inner <code>Class</code>.
*
* @return <code>true</code> if this <code>JavaClass</code> is an inner <code>Class</code>, otherwise <code>false</code>.
*/
@Override
public boolean isMemberClass() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> represents a primitive type.
*
* @return <code>true</code> if this <code>JavaClass</code> represents a primitive type, otherwise <code>false</code>.
*/
@Override
public boolean isPrimitive() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is <code>private</code>.
*
* @return <code>true</code> if this <code>JavaClass</code> is <code>private</code>, otherwise <code>false</code>.
*/
@Override
public boolean isPrivate() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is <code>protected</code>.
*
* @return <code>true</code> if this <code>JavaClass</code> is <code>protected</code>, otherwise <code>false</code>.
*/
@Override
public boolean isProtected() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is <code>public</code>.
*
* @return <code>true</code> if this <code>JavaClass</code> is <code>public</code>, otherwise <code>false</code>.
*/
@Override
public boolean isPublic() {
return false;
}
/**
* Indicates if this <code>JavaClass</code> is <code>static</code>.
*
* @return <code>true</code> if this <code>JavaClass</code> is <code>static</code>, otherwise <code>false</code>.
*/
@Override
public boolean isStatic() {
return false;
}
/**
* Not supported.
*/
@Override
public boolean isSynthetic() {
throw new UnsupportedOperationException("isSynthetic");
}
@Override
public JavaClassInstanceOf instanceOf() {
return JavaClassInstanceOf.OXM_JAVA_CLASS_IMPL;
}
/**
* If this <code>JavaClass</code> is annotated with an <code>Annotation</code> matching <code>aClass</code>,
* return its <code>JavaAnnotation</code> representation.
*
* @param aClass a <code>JavaClass</code> representing the <code>Annotation</code> to look for.
*
* @return always returns <code>null</code>, as <code>JavaTypes</code> do not have <code>Annotations</code>.
*/
@Override
public JavaAnnotation getAnnotation(JavaClass aClass) {
return null;
}
/**
* Return all of the <code>Annotations</code> for this <code>JavaClass</code>.
*
* @return always returns an empty <code>ArrayList</code>, as <code>JavaTypes</code> do not have <code>Annotations</code>.
*/
@Override
public Collection<JavaAnnotation> getAnnotations() {
return new ArrayList<JavaAnnotation>();
}
/**
* If this <code>JavaClass</code> declares an <code>Annotation</code> matching <code>aClass</code>,
* return its <code>JavaAnnotation</code> representation.
*
* @param arg0 a <code>JavaClass</code> representing the <code>Annotation</code> to look for.
*
* @return always returns <code>null</code>, as <code>JavaTypes</code> do not have <code>Annotations</code>.
*/
@Override
public JavaAnnotation getDeclaredAnnotation(JavaClass arg0) {
return null;
}
/**
* Return all of the declared <code>Annotations</code> for this <code>JavaClass</code>.
*
* @return always returns an empty <code>ArrayList</code>, as <code>JavaTypes</code> do not have <code>Annotations</code>.
*/
@Override
public Collection<JavaAnnotation> getDeclaredAnnotations() {
return new ArrayList<JavaAnnotation>();
}
/**
* Set this <code>JavaClass'</code> <code>JavaModel</code>.
*
* @param model The <code>JavaModel</code> to set.
*/
public void setJavaModel(JavaModel model) {
this.javaModel = model;
}
/**
* Get this <code>JavaClass'</code> <code>JavaModel</code>.
*
* @return The <code>JavaModel</code> associated with this <code>JavaClass</code>.
*/
public JavaModel getJavaModel() {
return this.javaModel;
}
// ========================================================================
private static String EMPTY_STRING = "";
private static String JAVA = "java";
private static String DOT = ".";
private static String JAVA_LANG_OBJECT = "java.lang.Object";
private static String JAVA_UTIL_MAP = "java.util.Map";
}