/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.lang.reflect;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
/*-[
#include "IOSClass.h"
#include "IOSPrimitiveClass.h"
#include "IOSProxyClass.h"
#include "IOSReflection.h"
#include "java/lang/IllegalArgumentException.h"
#include "java/lang/reflect/Method.h"
#include <objc/runtime.h>
]-*/
/**
* {@code Proxy} defines methods for creating dynamic proxy classes and instances.
* A proxy class implements a declared set of interfaces and delegates method
* invocations to an {@code InvocationHandler}.
*
* @see InvocationHandler
* @since 1.3
*/
public class Proxy implements Serializable {
private static final long serialVersionUID = -2222568056686623797L;
// maps class loaders to created classes by interface names
private static final Map<ClassLoader, Map<String, WeakReference<Class<?>>>> loaderCache =
new WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>>();
private static int NextClassNameIndex = 0;
/**
* The invocation handler on which the method calls are dispatched.
*/
protected InvocationHandler handler;
protected Map<String,Method> methodMap = new HashMap<String,Method>();
private Proxy() {
}
/**
* Constructs a new {@code Proxy} instance with the specified invocation
* handler.
*
* @param h
* the invocation handler for the newly created proxy
*/
protected Proxy(InvocationHandler h) {
this.handler = h;
}
/**
* Returns the dynamically built {@code Class} for the specified interfaces.
* Creates a new {@code Class} when necessary. The order of the interfaces
* is relevant. Invocations of this method with the same interfaces but
* different order result in different generated classes. The interfaces
* must be visible from the supplied class loader; no duplicates are
* permitted. All non-public interfaces must be defined in the same package.
*
* @param loader
* the class loader that will define the proxy class
* @param interfaces
* an array of {@code Class} objects, each one identifying an
* interface that will be implemented by the returned proxy
* class
* @return a proxy class that implements all of the interfaces referred to
* in the contents of {@code interfaces}
* @throws IllegalArgumentException
* if any of the interface restrictions are violated
* @throws NullPointerException
* if either {@code interfaces} or any of its elements are
* {@code null}
*/
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces) throws IllegalArgumentException {
// check that interfaces are a valid array of visible interfaces
if (interfaces == null) {
throw new NullPointerException("interfaces == null");
}
String commonPackageName = null;
for (int i = 0, length = interfaces.length; i < length; i++) {
Class<?> next = interfaces[i];
if (next == null) {
throw new NullPointerException("interfaces[" + i + "] == null");
}
String name = next.getName();
if (!next.isInterface()) {
throw new IllegalArgumentException(name + " is not an interface");
}
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
if (loader != next.getClassLoader()) {
try {
if (next != Class.forName(name, false, loader)) {
throw new IllegalArgumentException(name +
" is not visible from class loader");
}
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException(name + " is not visible from class loader");
}
}
for (int j = i + 1; j < length; j++) {
if (next == interfaces[j]) {
throw new IllegalArgumentException(name + " appears more than once");
}
}
if (!Modifier.isPublic(next.getModifiers())) {
int last = name.lastIndexOf('.');
String p = last == -1 ? "" : name.substring(0, last);
if (commonPackageName == null) {
commonPackageName = p;
} else if (!commonPackageName.equals(p)) {
throw new IllegalArgumentException("non-public interfaces must be " +
"in the same package");
}
}
}
// search cache for matching proxy class using the class loader
synchronized (loaderCache) {
Map<String, WeakReference<Class<?>>> interfaceCache = loaderCache.get(loader);
if (interfaceCache == null) {
loaderCache.put(loader,
(interfaceCache = new HashMap<String, WeakReference<Class<?>>>()));
}
String interfaceKey = "";
if (interfaces.length == 1) {
interfaceKey = interfaces[0].getName();
} else {
StringBuilder names = new StringBuilder();
for (int i = 0, length = interfaces.length; i < length; i++) {
names.append(interfaces[i].getName());
names.append(' ');
}
interfaceKey = names.toString();
}
Class<?> newClass;
WeakReference<Class<?>> ref = interfaceCache.get(interfaceKey);
if (ref == null) {
String nextClassName = "$Proxy" + NextClassNameIndex++;
if (commonPackageName != null && commonPackageName.length() > 0) {
nextClassName = commonPackageName + "." + nextClassName;
}
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
newClass = generateProxy(nextClassName.replace('.', '/'), interfaces, loader);
// Need a weak reference to the class so it can
// be unloaded if the class loader is discarded
interfaceCache.put(interfaceKey, new WeakReference<Class<?>>(newClass));
} else {
newClass = ref.get();
assert newClass != null : "\ninterfaceKey=\"" + interfaceKey + "\""
+ "\nloaderCache=\"" + loaderCache + "\""
+ "\nintfCache=\"" + interfaceCache + "\"";
}
return newClass;
}
}
/**
* Returns an instance of the dynamically built class for the specified
* interfaces. Method invocations on the returned instance are forwarded to
* the specified invocation handler. The interfaces must be visible from the
* supplied class loader; no duplicates are permitted. All non-public
* interfaces must be defined in the same package.
*
* @param loader
* the class loader that will define the proxy class
* @param interfaces
* an array of {@code Class} objects, each one identifying an
* interface that will be implemented by the returned proxy
* object
* @param h
* the invocation handler that handles the dispatched method
* invocations
* @return a new proxy object that delegates to the handler {@code h}
* @throws IllegalArgumentException
* if any of the interface restrictions are violated
* @throws NullPointerException
* if the interfaces or any of its elements are null
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException {
if (h == null) {
throw new NullPointerException("h == null");
}
try {
return getProxyClass(loader, interfaces).getConstructor(
new Class<?>[] { InvocationHandler.class }).newInstance(
new Object[] { h });
} catch (NoSuchMethodException ex) {
throw (InternalError) (new InternalError(ex.toString())
.initCause(ex));
} catch (IllegalAccessException ex) {
throw (InternalError) (new InternalError(ex.toString())
.initCause(ex));
} catch (InstantiationException ex) {
throw (InternalError) (new InternalError(ex.toString())
.initCause(ex));
} catch (InvocationTargetException ex) {
Throwable target = ex.getTargetException();
throw (InternalError) (new InternalError(target.toString())
.initCause(target));
}
}
/**
* Indicates whether or not the specified class is a dynamically generated
* proxy class.
*
* @param cl
* the class
* @return {@code true} if the class is a proxy class, {@code false}
* otherwise
* @throws NullPointerException
* if the class is {@code null}
*/
public static native boolean isProxyClass(Class<?> cl) /*-[
return [nil_chk(cl) isKindOfClass:[IOSProxyClass class]];
]-*/;
/**
* Returns the invocation handler of the specified proxy instance.
*
* @param proxy
* the proxy instance
* @return the invocation handler of the specified proxy instance
* @throws IllegalArgumentException
* if the supplied {@code proxy} is not a proxy object
*/
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException {
if (isProxyClass(proxy.getClass())) {
return ((Proxy) proxy).handler;
}
throw new IllegalArgumentException("not a proxy instance");
}
private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
ClassLoader loader) throws IllegalArgumentException /*-[
Class proxyClass = objc_allocateClassPair([JavaLangReflectProxy class], [name UTF8String], 0);
jint interfaceCount = interfaces->size_;
for (jint i = 0; i < interfaceCount; i++) {
IOSClass *intrface = (IOSClass *) [interfaces objectAtIndex:i];
if (![intrface isInterface]) {
@throw AUTORELEASE([[JavaLangIllegalArgumentException alloc]
initWithNSString:[intrface description]]);
}
class_addProtocol(proxyClass, intrface.objcProtocol);
}
objc_registerClassPair(proxyClass);
SEL sel = @selector(initWithJavaLangReflectInvocationHandler:);
Method constructor = class_getInstanceMethod([JavaLangReflectProxy class], sel);
class_addMethod(proxyClass, sel, method_getImplementation(constructor),
method_getTypeEncoding(constructor));
return IOSClass_NewProxyClass(proxyClass);
]-*/;
/*-[
static JavaLangReflectMethod *FindMethod(id self, SEL sel) {
for (IOSClass *cls in [[self java_getClass] getInterfacesInternal]) {
JavaLangReflectMethod *result = JreMethodForSelectorInherited(cls, sel);
if (result) {
return result;
}
}
return nil;
}
]-*/
/*-[
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [FindMethod(self, aSelector) getSignature];
}
]-*/
/*-[
- (BOOL)respondsToSelector:(SEL)aSelector {
return FindMethod(self, aSelector) != nil;
}
]-*/
/*-[
// Forwards a message to the invocation handler for this proxy.
-(void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
JavaLangReflectMethod *method = FindMethod(self, selector);
if (!method) {
[self doesNotRecognizeSelector:_cmd];
}
IOSObjectArray *paramTypes = [method getParameterTypes];
jint numArgs = paramTypes->size_;
IOSObjectArray *args = [IOSObjectArray arrayWithLength:numArgs type:NSObject_class_()];
for (jint i = 0; i < numArgs; i++) {
J2ObjcRawValue arg;
[anInvocation getArgument:&arg atIndex:i + 2];
id javaArg = [paramTypes->buffer_[i] __boxValue:&arg];
[args replaceObjectAtIndex:i withObject:javaArg];
}
id javaResult = [handler_ invokeWithId:self
withJavaLangReflectMethod:method
withNSObjectArray:args];
IOSClass *returnType = [method getReturnType];
if (returnType != [IOSClass voidClass]) {
IOSClass *resultType = [javaResult java_getClass];
if ([returnType isPrimitive]) {
// Return value is currently wrapped, so check wrapper type instead.
returnType = [(IOSPrimitiveClass *) returnType wrapperClass];
}
if (javaResult && ![returnType isAssignableFrom:resultType]) {
@throw AUTORELEASE([[JavaLangIllegalArgumentException alloc] init]);
}
J2ObjcRawValue result;
[[method getReturnType] __unboxValue:javaResult toRawValue:&result];
[anInvocation setReturnValue:&result];
}
}
]-*/
}