blob: 738e5f81bc15af8134271a8a5f2fdc57b73d22d2 [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:
// dmccann - December 10/2009 - 2.0.1 - Initial implementation
package org.eclipse.persistence.jaxb.javamodel;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.persistence.internal.helper.ConversionManager;
/**
* <p>
* <b>Purpose:</b>The purpose of this class is to act as a dynamic proxy that
* allows JDK Annotation method calls to be made on a non Annotation object.
*
* <p>
* <b>Responsibilities:</b>
* <ul>
* <li>Create and return a dynamic proxy instance based on an Annotation class
* and a <code>Map</code> of components (method name to value pairs)</li>
* <li>Allow JDK Annotation method calls to be invoked on the proxy object</li>
* </ul>
* <p>
* This class provides a means to invoke JDK Annotation method calls on a non
* Annotation instance.
*
* @see java.lang.reflect.Proxy
*/
public class AnnotationProxy implements InvocationHandler {
private Map<String, Object> components;
private ConversionManager conversionMgr;
private static final String ANNOTATION_TYPE_METHOD_NAME = "annotationType";
/**
* This constructor sets the <code>Map</code> of components (method name to
* value pairs)and the ConversionManager to be used when converting values
* in the <code>Map</code> based on the return type of the associated
* <code>Method</code> Note that the preferred method of obtaining an
* instance of this proxy class is via
* {@code getProxy(Map, Class<A>, ClassLoader, ConversionManager)}
*
* @param components
* <code>Map</code> of method name to value pairs
* @param conversionMgr
* <code>ConversionManager</code> instance for converting to the
* correct return type in the <code>invoke</code> method
*/
private AnnotationProxy(Map<String, Object> components, ConversionManager conversionMgr) {
this.components = components;
this.conversionMgr = conversionMgr;
}
/**
* This is the preferred way to obtain an instance of a dynamic proxy.
*
* The method takes a <code>ClassLoader</code> (which is used to load the
* target <code>Annotation</code>), a <code>Class</code> (which indicates
* the target <code>Annotation</code>, i.e.
* <code>jakarta.xml.bind.annotation.XmlElement.class)</code>, and a
* <code>Map</code> of method name to value pairs, which represent the
* method names on the <code>Annotation</code> and the values that are to be
* returned from each method call. For example, if this proxy is to be used
* for an <code>@XmlElement</code>, the <code>Map</code> should contain the
* following keys:
*
* <ul>
* <li>defaultValue</li> <li>name</li> <li>namespace</li> <li>nillable</li>
* <li>required</li> <li> type</li>
* </ul>
*
* Following are example key/value pairs :
*
* <ul>
* <li>"defaultValue", "##default"</li> <li>"name", "employee"</li> <li>
* "namespace", "www.example.org"</li> <li>"nillable", false</li> <li>
* "required", false</li> <li>"type",
* jakarta.xml.bind.annotation.XmlElement.DEFAULT.class</li>
* </ul>
*
* @param components
* <code>Map</code> of method name/value pairs for this proxy
* instance
* @param annoClass
* The interface for the proxy class to implement
* @param cl
* The <code>ClassLoader</code> to define the proxy class
* @param conversionMgr
* <code>ConversionManager</code> instance for converting to the
* correct return type in the <code>invoke</code> method
* @return A dynamic proxy instance based on a Java model JavaAnnotation
*/
public static <A extends Annotation> A getProxy(Map<String, Object> components, Class<A> annoClass, ClassLoader cl, ConversionManager conversionMgr) {
// add the 'annotationType' method name/value pair to the components map
if (components == null) {
components = new HashMap<String, Object>();
}
components.put(ANNOTATION_TYPE_METHOD_NAME, annoClass.getName());
// Pass the classloader to the ConversionManager as well
// conversionMgr.setLoader(cl);
return (A) Proxy.newProxyInstance(cl, new Class[] { annoClass }, new AnnotationProxy(components, conversionMgr));
}
/**
* Return the <code>Map</code> of method name/value pairs for this proxy
* instance.
*
* @return <code>Map</code> of method name/value pairs for this proxy
* instance
*/
public Map<String, Object> getComponents() {
return this.components;
}
/**
* Invoke a given <code>Method</code> on this proxy. The component
* <code>Map</code> will be accessed using the given <code>Method</code>'s
* name, and if an entry exists, the associated value is returned.
*
* @param proxy
* Satisfy the <code>InvocationHandler</code> interface - not
* used
* @param method
* The <code>Method</code> instance corresponding to the
* interface method invoked on the proxy instance
* @param args
* Satisfy the <code>InvocationHandler</code> interface - not
* used
* @return The value from the method invocation on the proxy instance
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (components == null) {
return null;
}
Class<?> returnType = method.getReturnType();
Object value = getComponents().get(method.getName());
if (value == null && returnType == boolean.class) {
return false;
}
if (value == null && returnType == Boolean.class) {
return Boolean.FALSE;
}
if (returnType.isArray()) {
return handleArrayData(returnType, value);
}
// use the ConversionManager to ensure that the correct type is returned
return conversionMgr.convertObject(value, returnType);
}
private Object handleArrayData(Class returnType, Object value) {
if (value == null) {
return null;
}
Object[] data = (Object[]) value;
Class componentType = returnType.getComponentType();
Object[] convertedArray = (Object[]) Array.newInstance(componentType, data.length);
for (int i = 0; i < data.length; i++) {
convertedArray[i] = conversionMgr.convertObject(data[i], componentType);
}
return convertedArray;
}
}