// Licensed 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 org.mockito.internal.creation.ios;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.invocation.MockHandler;
import org.mockito.mock.MockCreationSettings;
import org.mockito.plugins.MockMaker;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/*-[
#include "java/lang/IllegalArgumentException.h"
#include "java/lang/reflect/Method.h"
]-*/
/**
* MockMaker implementation for iOS. Unlike the JRE and Android versions,
* this class creates mocks for classes using the Objective-C runtime.
*
* @author Tom Ball
*/
public final class IosMockMaker implements MockMaker {
// to find previously created types
private static Map<String, WeakReference<Class<?>>> classCache =
new HashMap<String, WeakReference<Class<?>>>();
private static final Map<Class<?>, Class<?>> proxyCache = new WeakHashMap<Class<?>, Class<?>>();
private static int nextClassNameIndex = 0;
@Override
@SuppressWarnings("unchecked")
public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
Class<T> typeToMock = settings.getTypeToMock();
@SuppressWarnings("rawtypes")
Set<Class> interfacesSet = settings.getExtraInterfaces();
Class<?>[] extraInterfaces = interfacesSet.toArray(new Class[interfacesSet.size()]);
InvocationHandler invocationHandler = new InvocationHandlerAdapter(handler);
if (typeToMock.isInterface()) {
// support interfaces via java.lang.reflect.Proxy
@SuppressWarnings("rawtypes")
Class[] classesToMock = new Class[extraInterfaces.length + 1];
classesToMock[0] = typeToMock;
System.arraycopy(extraInterfaces, 0, classesToMock, 1, extraInterfaces.length);
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
// newProxyInstance returns the type of typeToMock
T mock = (T) Proxy.newProxyInstance(contextClassLoader, classesToMock, invocationHandler);
return mock;
} else {
try {
Class<? extends T> proxyClass = getProxyClass(typeToMock, extraInterfaces);
T mock = proxyClass.newInstance();
((ClassProxy) mock).setHandler(invocationHandler);
return mock;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new MockitoException("Failed to mock " + typeToMock, e);
}
}
}
@Override
public MockHandler getHandler(Object mock) {
InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
return adapter != null ? adapter.getHandler() : null;
}
@Override
@SuppressWarnings("rawtypes")
public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
InvocationHandlerAdapter adapter = getInvocationHandlerAdapter(mock);
adapter.setHandler(newHandler);
}
private InvocationHandlerAdapter getInvocationHandlerAdapter(Object mock) {
if (Proxy.isProxyClass(mock.getClass())) {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(mock);
return invocationHandler instanceof InvocationHandlerAdapter
? (InvocationHandlerAdapter) invocationHandler
: null;
}
if (mock instanceof ClassProxy) {
InvocationHandler invocationHandler = ((ClassProxy) mock).getHandler();
return invocationHandler instanceof InvocationHandlerAdapter
? (InvocationHandlerAdapter) invocationHandler
: null;
}
return null;
}
@SuppressWarnings("unchecked")
<T> Class<T> getProxyClass(Class<T> typeToMock, Class<?>[] interfaces) {
synchronized (classCache) {
String className = typeToMock.getName();
Class<?> newClass;
WeakReference<Class<?>> ref = classCache.get(className);
if (ref == null) {
String nextClassName = className + "$Proxy" + nextClassNameIndex++;
newClass = generateClassProxy(nextClassName.replace('.', '/'), typeToMock, interfaces);
classCache.put(className, new WeakReference<Class<?>>(newClass));
synchronized (proxyCache) {
proxyCache.put(newClass, typeToMock);
}
} else {
newClass = ref.get();
assert newClass != null : "\nclassName=\"" + className + "\""
+ "\nclassCache=\"" + classCache + "\""
+ "\nproxyCache=\"" + proxyCache + "\"";
}
return (Class<T>) newClass;
}
}
private static native <T> Class<T> generateClassProxy(String name, Class<T> classToMock,
Class<?>[] interfaces) throws IllegalArgumentException /*-[
Class proxyClass =
objc_allocateClassPair([OrgMockitoInternalCreationIosIosMockMaker_ClassProxy 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 create_JavaLangIllegalArgumentException_initWithNSString_([intrface description]);
}
class_addProtocol(proxyClass, intrface.objcProtocol);
}
objc_registerClassPair(proxyClass);
return IOSClass_fromClass(proxyClass);
]-*/;
static class ClassProxy {
InvocationHandler $__handler;
InvocationHandler getHandler() {
return $__handler;
}
void setHandler(InvocationHandler handler) {
$__handler = handler;
}
/*-[
static JavaLangReflectMethod *FindMethod(id self, SEL selector) {
IOSClass *mockedClass =
[OrgMockitoInternalCreationIosIosMockMaker_proxyCache getWithId:[self java_getClass]];
return [mockedClass getMethodWithSelector:sel_getName(selector)];
}
]-*/
/*-[
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [FindMethod(self, aSelector) getSignature];
}
]-*/
/*-[
// 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];
NSUInteger numArgs = paramTypes->size_;
IOSObjectArray *args = [IOSObjectArray arrayWithLength:numArgs type:NSObject_class_()];
for (unsigned 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<JavaLangReflectInvocationHandler> handler = [self getHandler];
id javaResult = [handler invokeWithId:self
withJavaLangReflectMethod:method
withNSObjectArray:args];
IOSClass *returnType = [method getReturnType];
if (returnType != [IOSClass voidClass]) {
J2ObjcRawValue result;
[[method getReturnType] __unboxValue:javaResult toRawValue:&result];
[anInvocation setReturnValue:&result];
}
}
]-*/
}
}