dorkbox.network.rmi.CachedMethod Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Network Show documentation
Show all versions of Network Show documentation
Encrypted, high-performance, and event-driven/reactive network stack for Java 11+
/*
* Copyright 2010 dorkbox, llc
*
* 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.
*
* Copyright (c) 2008, Nathan Sweet
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
* - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package dorkbox.network.rmi;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.util.IdentityMap;
import com.esotericsoftware.kryo.util.Util;
import com.esotericsoftware.reflectasm.MethodAccess;
import dorkbox.network.connection.Connection;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.KryoExtra;
import dorkbox.network.util.RMISerializationManager;
import dorkbox.util.ClassHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public
class CachedMethod {
private static final Logger logger = LoggerFactory.getLogger(CachedMethod.class);
private static final Comparator METHOD_COMPARATOR = new Comparator() {
@Override
public
int compare(Method o1, Method o2) {
// Methods are sorted so they can be represented as an index.
int diff = o1.getName()
.compareTo(o2.getName());
if (diff != 0) {
return diff;
}
Class>[] argTypes1 = o1.getParameterTypes();
Class>[] argTypes2 = o2.getParameterTypes();
if (argTypes1.length > argTypes2.length) {
return 1;
}
if (argTypes1.length < argTypes2.length) {
return -1;
}
for (int i = 0; i < argTypes1.length; i++) {
diff = argTypes1[i].getName()
.compareTo(argTypes2[i].getName());
if (diff != 0) {
return diff;
}
}
throw new RuntimeException("Two methods with same signature!"); // Impossible, should never happen
}
};
// the purpose of the method cache, is to accelerate looking up methods for specific class
private static final Map, CachedMethod[]> methodCache = new ConcurrentHashMap, CachedMethod[]>(EndPoint.DEFAULT_THREAD_POOL_SIZE);
private static final OverriddenMethods overriddenMethods = OverriddenMethods.INSTANCE();
// type will be likely be the interface
public static
CachedMethod[] getMethods(final Kryo kryo, final Class> type) {
CachedMethod[] cachedMethods = methodCache.get(type);
if (cachedMethods != null) {
return cachedMethods;
}
cachedMethods = getCachedMethods(kryo, type);
methodCache.put(type, cachedMethods);
return cachedMethods;
}
// type will be likely be the interface
public static
CachedMethod[] getMethods(final RMISerializationManager serializationManager, final Class> type) {
CachedMethod[] cachedMethods = methodCache.get(type);
if (cachedMethods != null) {
return cachedMethods;
}
final KryoExtra kryo = serializationManager.takeKryo();
try {
cachedMethods = getCachedMethods(kryo, type);
methodCache.put(type, cachedMethods);
} finally {
serializationManager.returnKryo(kryo);
}
return cachedMethods;
}
private static
CachedMethod[] getCachedMethods(final Kryo kryo, final Class> type) {
// race-conditions are OK, because we just recreate the same thing.
final ArrayList methods = getMethods(type);
final int size = methods.size();
final CachedMethod[] cachedMethods = new CachedMethod[size];
// In situations where we want to pass in the Connection (to an RMI method), we have to be able to override method A, with method B.
// This is to support calling RMI methods from an interface (that does pass the connection reference) to
// an implType, that DOES pass the connection reference. The remote side (that initiates the RMI calls), MUST use
// the interface, and the implType may override the method, so that we add the connection as the first in
// the list of parameters.
//
// for example:
// Interface: foo(String x)
// Impl: foo(Connection c, String x)
//
// The implType (if it exists, with the same name, and with the same signature+connection) will be called from the interface.
// This MUST hold valid for both remote and local connection types.
// To facilitate this functionality, for methods with the same name, the "overriding" method is the one that inherits the Connection
// interface as the first parameter, and .registerRemote(ifaceClass, implClass) must be called.
final IdentityMap overriddenMethods = getOverriddenMethods(type, methods);
final boolean asmEnabled = kryo.getAsmEnabled();
MethodAccess methodAccess = null;
// reflectASM can't get any method from the 'Object' object, and it MUST be public
if (asmEnabled && type != Object.class && !Util.isAndroid && Modifier.isPublic(type.getModifiers())) {
methodAccess = MethodAccess.get(type);
if (methodAccess.getMethodNames().length == 0 && methodAccess.getParameterTypes().length == 0 &&
methodAccess.getReturnTypes().length == 0) {
// there was NOTHING reflectASM found, so trying to use it doesn't do us any good
methodAccess = null;
}
}
for (int i = 0; i < size; i++) {
final Method origMethod = methods.get(i);
Method method = origMethod; // copy because one or more can be overridden
MethodAccess localMethodAccess = methodAccess; // copy because one or more can be overridden
Class>[] parameterTypes = method.getParameterTypes();
Class>[] asmParameterTypes = parameterTypes;
if (overriddenMethods != null) {
Method overriddenMethod = overriddenMethods.remove(method);
if (overriddenMethod != null) {
// we can override the details of this method BECAUSE (and only because) our kryo registration override will return
// the correct object for this overridden method to be called on.
method = overriddenMethod;
Class> overrideType = method.getDeclaringClass();
if (asmEnabled && !Util.isAndroid && Modifier.isPublic(overrideType.getModifiers())) {
localMethodAccess = MethodAccess.get(overrideType);
asmParameterTypes = method.getParameterTypes();
}
}
}
CachedMethod cachedMethod = null;
if (localMethodAccess != null) {
try {
final int index = localMethodAccess.getIndex(method.getName(), asmParameterTypes);
AsmCachedMethod asmCachedMethod = new AsmCachedMethod();
asmCachedMethod.methodAccessIndex = index;
asmCachedMethod.methodAccess = localMethodAccess;
cachedMethod = asmCachedMethod;
} catch (Exception e) {
if (logger.isTraceEnabled()) {
logger.trace("Unable to use ReflectAsm for {}.{}", method.getDeclaringClass(), method.getName(), e);
}
}
}
if (cachedMethod == null) {
cachedMethod = new CachedMethod();
}
cachedMethod.method = method;
cachedMethod.origMethod = origMethod;
cachedMethod.methodClassID = kryo.getRegistration(method.getDeclaringClass()).getId();
cachedMethod.methodIndex = i;
// Store the serializer for each final parameter.
// ONLY for the ORIGINAL method, not he overridden one.
cachedMethod.serializers = new Serializer>[parameterTypes.length];
for (int ii = 0, nn = parameterTypes.length; ii < nn; ii++) {
if (kryo.isFinal(parameterTypes[ii])) {
cachedMethod.serializers[ii] = kryo.getSerializer(parameterTypes[ii]);
}
}
cachedMethods[i] = cachedMethod;
}
return cachedMethods;
}
private static
IdentityMap getOverriddenMethods(final Class> type, final ArrayList origMethods) {
final Class> implType = overriddenMethods.get(type);
if (implType != null) {
final ArrayList implMethods = getMethods(implType);
final IdentityMap overrideMap = new IdentityMap(implMethods.size());
for (Method origMethod : origMethods) {
String name = origMethod.getName();
Class>[] origTypes = origMethod.getParameterTypes();
int origLength = origTypes.length + 1;
METHOD_CHECK:
for (Method implMethod : implMethods) {
String checkName = implMethod.getName();
Class>[] checkTypes = implMethod.getParameterTypes();
int checkLength = checkTypes.length;
if (origLength != checkLength || !(name.equals(checkName))) {
continue;
}
// checkLength > 0
Class> shouldBeConnectionType = checkTypes[0];
if (ClassHelper.hasInterface(dorkbox.network.connection.Connection.class, shouldBeConnectionType)) {
// now we check to see if our "check" method is equal to our "cached" method + Connection
if (checkLength == 1) {
overrideMap.put(origMethod, implMethod);
break;
}
else {
for (int k = 1; k < checkLength; k++) {
if (origTypes[k - 1] == checkTypes[k]) {
overrideMap.put(origMethod, implMethod);
break METHOD_CHECK;
}
}
}
}
}
}
return overrideMap;
}
else {
return null;
}
}
private static
ArrayList getMethods(final Class> type) {
final ArrayList allMethods = new ArrayList();
Class> nextClass = type;
while (nextClass != null) {
Collections.addAll(allMethods, nextClass.getDeclaredMethods());
nextClass = nextClass.getSuperclass();
if (nextClass == Object.class) {
break;
}
}
final ArrayList methods = new ArrayList(Math.max(1, allMethods.size()));
for (int i = 0, n = allMethods.size(); i < n; i++) {
Method method = allMethods.get(i);
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers)) {
continue;
}
if (Modifier.isPrivate(modifiers)) {
continue;
}
if (method.isSynthetic()) {
continue;
}
methods.add(method);
}
Collections.sort(methods, METHOD_COMPARATOR);
return methods;
}
/**
* Called by the SerializationManager, so that RMI classes that are overridden for serialization purposes, can check to see if certain
* methods need to be overridden.
*/
public static
void registerOverridden(final Class> ifaceClass, final Class> implClass) {
overriddenMethods.set(ifaceClass, implClass);
}
public Method method;
public int methodClassID;
public int methodIndex;
/**
* in some cases, we want to override the cached method, with one that supports passing 'Connection' as the first argument. This is
* completely OPTIONAL, however - greatly adds functionality to RMI methods.
*/
public transient Method origMethod;
@SuppressWarnings("rawtypes")
public Serializer[] serializers;
public
Object invoke(final Connection connection, Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
// did we override our cached method?
if (method == origMethod) {
return this.method.invoke(target, args);
}
else {
int length = args.length;
Object[] newArgs = new Object[length + 1];
newArgs[0] = connection;
System.arraycopy(args, 0, newArgs, 1, length);
return this.method.invoke(target, newArgs);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy