More robust type parameter detection
- Also removed unnecessary constructors which were added due to incomplete type parameter detection logic
This commit is contained in:
parent
d0a3c2d95e
commit
53c27ef5ae
@ -53,14 +53,7 @@ public abstract class ByteToMessageCodec<I> extends ChannelDuplexHandler
|
||||
};
|
||||
|
||||
protected ByteToMessageCodec() {
|
||||
this(ByteToMessageCodec.class, 0);
|
||||
}
|
||||
|
||||
protected ByteToMessageCodec(
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends ByteToMessageCodec> parameterizedHandlerType,
|
||||
int messageTypeParamIndex) {
|
||||
outboundMsgMatcher = TypeParameterMatcher.find(this, parameterizedHandlerType, messageTypeParamIndex);
|
||||
outboundMsgMatcher = TypeParameterMatcher.find(this, ByteToMessageCodec.class, "I");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,9 +18,7 @@ package io.netty.handler.codec;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.MessageBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundMessageHandler;
|
||||
import io.netty.channel.ChannelOutboundMessageHandlerAdapter;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
|
||||
|
||||
/**
|
||||
@ -42,21 +40,6 @@ import io.netty.channel.ChannelPipeline;
|
||||
*/
|
||||
public abstract class MessageToByteEncoder<I> extends ChannelOutboundMessageHandlerAdapter<I> {
|
||||
|
||||
/**
|
||||
* The types which will be accepted by the encoder. If a received message is an other type it will be just forwared
|
||||
* to the next {@link ChannelOutboundMessageHandler} in the {@link ChannelPipeline}
|
||||
*/
|
||||
protected MessageToByteEncoder() {
|
||||
this(MessageToByteEncoder.class, 0);
|
||||
}
|
||||
|
||||
protected MessageToByteEncoder(
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends MessageToByteEncoder> parameterizedHandlerType,
|
||||
int messageTypeParamIndex) {
|
||||
super(parameterizedHandlerType, messageTypeParamIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void flush(ChannelHandlerContext ctx, I msg) throws Exception {
|
||||
try {
|
||||
|
@ -19,9 +19,7 @@ import io.netty.buffer.MessageBuf;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundMessageHandler;
|
||||
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
|
||||
import io.netty.channel.ChannelOutboundMessageHandler;
|
||||
import io.netty.channel.ChannelOutboundMessageHandlerAdapter;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.internal.TypeParameterMatcher;
|
||||
|
||||
@ -88,22 +86,8 @@ public abstract class MessageToMessageCodec<INBOUND_IN, OUTBOUND_IN>
|
||||
private final TypeParameterMatcher outboundMsgMatcher;
|
||||
|
||||
protected MessageToMessageCodec() {
|
||||
inboundMsgMatcher = TypeParameterMatcher.find(this, MessageToMessageCodec.class, 0);
|
||||
outboundMsgMatcher = TypeParameterMatcher.find(this, MessageToMessageCodec.class, 1);
|
||||
}
|
||||
|
||||
protected MessageToMessageCodec(
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends ChannelInboundMessageHandlerAdapter> parameterizedInboundHandlerType,
|
||||
int inboundMessageTypeParamIndex,
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends ChannelOutboundMessageHandlerAdapter> parameterizedOutboundHandlerType,
|
||||
int outboundMessageTypeParamIndex) {
|
||||
|
||||
inboundMsgMatcher = TypeParameterMatcher.find(
|
||||
this, parameterizedInboundHandlerType, inboundMessageTypeParamIndex);
|
||||
outboundMsgMatcher = TypeParameterMatcher.find(
|
||||
this, parameterizedOutboundHandlerType, outboundMessageTypeParamIndex);
|
||||
inboundMsgMatcher = TypeParameterMatcher.find(this, MessageToMessageCodec.class, "INBOUND_IN");
|
||||
outboundMsgMatcher = TypeParameterMatcher.find(this, MessageToMessageCodec.class, "OUTBOUND_IN");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -44,17 +44,6 @@ import io.netty.channel.ChannelInboundMessageHandlerAdapter;
|
||||
*/
|
||||
public abstract class MessageToMessageDecoder<I> extends ChannelInboundMessageHandlerAdapter<I> {
|
||||
|
||||
protected MessageToMessageDecoder() {
|
||||
super(MessageToMessageDecoder.class, 0);
|
||||
}
|
||||
|
||||
protected MessageToMessageDecoder(
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends ChannelInboundMessageHandlerAdapter> parameterizedHandlerType,
|
||||
int messageTypeParamIndex) {
|
||||
super(parameterizedHandlerType, messageTypeParamIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void messageReceived(ChannelHandlerContext ctx, I msg) throws Exception {
|
||||
ctx.nextInboundMessageBuffer().unfoldAndAdd(decode(ctx, msg));
|
||||
|
@ -17,9 +17,7 @@ package io.netty.handler.codec;
|
||||
|
||||
import io.netty.buffer.MessageBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundMessageHandler;
|
||||
import io.netty.channel.ChannelOutboundMessageHandlerAdapter;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
|
||||
/**
|
||||
* {@link ChannelOutboundMessageHandlerAdapter} which encodes from one message to an other message
|
||||
@ -44,22 +42,6 @@ import io.netty.channel.ChannelPipeline;
|
||||
*/
|
||||
public abstract class MessageToMessageEncoder<I> extends ChannelOutboundMessageHandlerAdapter<I> {
|
||||
|
||||
/**
|
||||
* The types which will be accepted by the decoder. If a received message is an other type it will be just forwared
|
||||
* to the next {@link ChannelOutboundMessageHandler} in the {@link ChannelPipeline}
|
||||
*/
|
||||
protected MessageToMessageEncoder() {
|
||||
super(MessageToMessageEncoder.class, 0);
|
||||
}
|
||||
|
||||
protected MessageToMessageEncoder(
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends ChannelOutboundMessageHandlerAdapter> parameterizedHandlerType,
|
||||
int messageTypeParamIndex) {
|
||||
|
||||
super(parameterizedHandlerType, messageTypeParamIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void flush(ChannelHandlerContext ctx, I msg) throws Exception {
|
||||
try {
|
||||
|
@ -16,80 +16,113 @@
|
||||
|
||||
package io.netty.util.internal;
|
||||
|
||||
import java.lang.reflect.GenericDeclaration;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class TypeParameterMatcher {
|
||||
|
||||
private static final TypeParameterMatcher NOOP = new NoOpTypeParameterMatcher();
|
||||
|
||||
private static final ThreadLocal<Map<Class<?>, TypeParameterMatcher>> typeMap =
|
||||
new ThreadLocal<Map<Class<?>, TypeParameterMatcher>>() {
|
||||
private static final ThreadLocal<Map<Class<?>, Map<String, TypeParameterMatcher>>> typeMap =
|
||||
new ThreadLocal<Map<Class<?>, Map<String, TypeParameterMatcher>>>() {
|
||||
@Override
|
||||
protected Map<Class<?>, TypeParameterMatcher> initialValue() {
|
||||
return new IdentityHashMap<Class<?>, TypeParameterMatcher>();
|
||||
protected Map<Class<?>, Map<String, TypeParameterMatcher>> initialValue() {
|
||||
return new IdentityHashMap<Class<?>, Map<String, TypeParameterMatcher>>();
|
||||
}
|
||||
};
|
||||
|
||||
public static TypeParameterMatcher find(
|
||||
final Object object, final Class<?> parameterizedSuperClass, final int typeParamIndex) {
|
||||
final Object object, final GenericDeclaration parameterizedSuperclass, final String typeParamName) {
|
||||
|
||||
final Map<Class<?>, TypeParameterMatcher> typeMap = TypeParameterMatcher.typeMap.get();
|
||||
final Map<Class<?>, Map<String, TypeParameterMatcher>> typeMap = TypeParameterMatcher.typeMap.get();
|
||||
final Class<?> thisClass = object.getClass();
|
||||
|
||||
TypeParameterMatcher matcher = typeMap.get(thisClass);
|
||||
Map<String, TypeParameterMatcher> map = typeMap.get(thisClass);
|
||||
if (map == null) {
|
||||
map = new HashMap<String, TypeParameterMatcher>();
|
||||
typeMap.put(thisClass, map);
|
||||
}
|
||||
|
||||
TypeParameterMatcher matcher = map.get(typeParamName);
|
||||
if (matcher == null) {
|
||||
Class<?> currentClass = thisClass;
|
||||
for (;;) {
|
||||
if (currentClass.getSuperclass() == parameterizedSuperClass) {
|
||||
Type[] types = ((ParameterizedType) currentClass.getGenericSuperclass()).getActualTypeArguments();
|
||||
if (types.length - 1 < typeParamIndex) {
|
||||
List<Type> typeList = new ArrayList<Type>(types.length);
|
||||
Collections.addAll(typeList, types);
|
||||
throw new IllegalStateException(
|
||||
"invalid typeParamIndex: " + typeParamIndex + " (typeParams: " + typeList + ')');
|
||||
}
|
||||
|
||||
Type t = types[typeParamIndex];
|
||||
Class<?> messageType;
|
||||
if (t instanceof Class) {
|
||||
messageType = (Class<?>) t;
|
||||
} else if (t instanceof ParameterizedType) {
|
||||
messageType = (Class<?>) ((ParameterizedType) t).getRawType();
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"cannot determine the type of the type parameter of " +
|
||||
thisClass.getSimpleName() + ": " + t);
|
||||
}
|
||||
|
||||
if (messageType == Object.class) {
|
||||
matcher = NOOP;
|
||||
} else if (PlatformDependent.hasJavassist()) {
|
||||
try {
|
||||
matcher = JavassistTypeParameterMatcherGenerator.generate(messageType);
|
||||
} catch (Exception e) {
|
||||
// Will not usually happen, but just in case.
|
||||
matcher = new ReflectiveMatcher(messageType);
|
||||
}
|
||||
} else {
|
||||
matcher = new ReflectiveMatcher(messageType);
|
||||
}
|
||||
break;
|
||||
Class<?> messageType = find0(object, parameterizedSuperclass, typeParamName);
|
||||
if (messageType == Object.class) {
|
||||
matcher = NOOP;
|
||||
} else if (PlatformDependent.hasJavassist()) {
|
||||
try {
|
||||
matcher = JavassistTypeParameterMatcherGenerator.generate(messageType);
|
||||
} catch (Exception e) {
|
||||
// Will not usually happen, but just in case.
|
||||
matcher = new ReflectiveMatcher(messageType);
|
||||
}
|
||||
currentClass = currentClass.getSuperclass();
|
||||
} else {
|
||||
matcher = new ReflectiveMatcher(messageType);
|
||||
}
|
||||
|
||||
typeMap.put(thisClass, matcher);
|
||||
map.put(typeParamName, matcher);
|
||||
}
|
||||
|
||||
return matcher;
|
||||
}
|
||||
|
||||
private static Class<?> find0(
|
||||
final Object object, GenericDeclaration parameterizedSuperclass, String typeParamName) {
|
||||
|
||||
final Class<?> thisClass = object.getClass();
|
||||
Class<?> currentClass = thisClass;
|
||||
for (;;) {
|
||||
if (currentClass.getSuperclass() == parameterizedSuperclass) {
|
||||
int typeParamIndex = -1;
|
||||
TypeVariable<?>[] typeParams = currentClass.getSuperclass().getTypeParameters();
|
||||
for (int i = 0; i < typeParams.length; i ++) {
|
||||
if (typeParamName.equals(typeParams[i].getName())) {
|
||||
typeParamIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeParamIndex < 0) {
|
||||
throw new IllegalStateException(
|
||||
"unknown type parameter '" + typeParamName + "': " + parameterizedSuperclass);
|
||||
}
|
||||
|
||||
Type[] actualTypeParams =
|
||||
((ParameterizedType) currentClass.getGenericSuperclass()).getActualTypeArguments();
|
||||
|
||||
Type actualTypeParam = actualTypeParams[typeParamIndex];
|
||||
if (actualTypeParam instanceof Class) {
|
||||
return (Class<?>) actualTypeParam;
|
||||
}
|
||||
if (actualTypeParam instanceof ParameterizedType) {
|
||||
return (Class<?>) ((ParameterizedType) actualTypeParam).getRawType();
|
||||
}
|
||||
if (actualTypeParam instanceof TypeVariable) {
|
||||
// Resolved type parameter points to another type parameter.
|
||||
TypeVariable<?> v = (TypeVariable<?>) actualTypeParam;
|
||||
currentClass = thisClass;
|
||||
parameterizedSuperclass = v.getGenericDeclaration();
|
||||
typeParamName = v.getName();
|
||||
continue;
|
||||
}
|
||||
|
||||
return fail(thisClass, typeParamName);
|
||||
}
|
||||
currentClass = currentClass.getSuperclass();
|
||||
if (currentClass == null) {
|
||||
return fail(thisClass, typeParamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> fail(Class<?> type, String typeParamName) {
|
||||
throw new IllegalStateException(
|
||||
"cannot determine the type of the type parameter '" + typeParamName + "': " + type);
|
||||
}
|
||||
|
||||
public abstract boolean match(Object msg);
|
||||
|
||||
private static final class ReflectiveMatcher extends TypeParameterMatcher {
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project 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 io.netty.util.internal;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TypeParameterMatcherTest {
|
||||
|
||||
@Test
|
||||
public void testSimple() throws Exception {
|
||||
Object o = new TypeQ();
|
||||
|
||||
TypeParameterMatcher m;
|
||||
|
||||
m = TypeParameterMatcher.find(o, TypeX.class, "A");
|
||||
assertFalse(m.match(new Object()));
|
||||
assertFalse(m.match(new A()));
|
||||
assertFalse(m.match(new AA()));
|
||||
assertTrue(m.match(new AAA()));
|
||||
assertFalse(m.match(new B()));
|
||||
assertFalse(m.match(new BB()));
|
||||
assertFalse(m.match(new BBB()));
|
||||
assertFalse(m.match(new C()));
|
||||
assertFalse(m.match(new CC()));
|
||||
|
||||
try {
|
||||
TypeParameterMatcher.find(o, TypeX.class, "B");
|
||||
} catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
m = TypeParameterMatcher.find(new TypeQ<BBB>() { }, TypeX.class, "B");
|
||||
assertFalse(m.match(new Object()));
|
||||
assertFalse(m.match(new A()));
|
||||
assertFalse(m.match(new AA()));
|
||||
assertFalse(m.match(new AAA()));
|
||||
assertFalse(m.match(new B()));
|
||||
assertFalse(m.match(new BB()));
|
||||
assertTrue(m.match(new BBB()));
|
||||
assertFalse(m.match(new C()));
|
||||
assertFalse(m.match(new CC()));
|
||||
|
||||
m = TypeParameterMatcher.find(o, TypeX.class, "C");
|
||||
assertFalse(m.match(new Object()));
|
||||
assertFalse(m.match(new A()));
|
||||
assertFalse(m.match(new AA()));
|
||||
assertFalse(m.match(new AAA()));
|
||||
assertFalse(m.match(new B()));
|
||||
assertFalse(m.match(new BB()));
|
||||
assertFalse(m.match(new BBB()));
|
||||
assertFalse(m.match(new C()));
|
||||
assertTrue(m.match(new CC()));
|
||||
}
|
||||
|
||||
public static class TypeX<A, B, C> {
|
||||
A a;
|
||||
B b;
|
||||
C c;
|
||||
}
|
||||
|
||||
public static class TypeY<D extends C, E extends A, F extends B> extends TypeX<E, F, D> { }
|
||||
|
||||
public static class TypeZ<G extends AA, H extends BB> extends TypeY<CC, G, H> { }
|
||||
|
||||
public static class TypeQ<I extends BBB> extends TypeZ<AAA, I> { }
|
||||
|
||||
@SuppressWarnings("ClassMayBeInterface")
|
||||
public static class A { }
|
||||
public static class AA extends A { }
|
||||
public static class AAA extends AA { }
|
||||
|
||||
@SuppressWarnings("ClassMayBeInterface")
|
||||
public static class B { }
|
||||
public static class BB extends B { }
|
||||
public static class BBB extends BB { }
|
||||
|
||||
@SuppressWarnings("ClassMayBeInterface")
|
||||
public static class C { }
|
||||
public static class CC extends C { }
|
||||
}
|
@ -54,14 +54,7 @@ public abstract class ChannelInboundMessageHandlerAdapter<I>
|
||||
private final TypeParameterMatcher msgMatcher;
|
||||
|
||||
protected ChannelInboundMessageHandlerAdapter() {
|
||||
this(ChannelInboundMessageHandlerAdapter.class, 0);
|
||||
}
|
||||
|
||||
protected ChannelInboundMessageHandlerAdapter(
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends ChannelInboundMessageHandlerAdapter> parameterizedHandlerType,
|
||||
int messageTypeParamIndex) {
|
||||
msgMatcher = TypeParameterMatcher.find(this, parameterizedHandlerType, messageTypeParamIndex);
|
||||
msgMatcher = TypeParameterMatcher.find(this, ChannelInboundMessageHandlerAdapter.class, "I");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -39,14 +39,7 @@ public abstract class ChannelOutboundMessageHandlerAdapter<I>
|
||||
private boolean closeOnFailedFlush = true;
|
||||
|
||||
protected ChannelOutboundMessageHandlerAdapter() {
|
||||
this(ChannelOutboundMessageHandlerAdapter.class, 0);
|
||||
}
|
||||
|
||||
protected ChannelOutboundMessageHandlerAdapter(
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends ChannelOutboundMessageHandlerAdapter> parameterizedHandlerType,
|
||||
int messageTypeParamIndex) {
|
||||
msgMatcher = TypeParameterMatcher.find(this, parameterizedHandlerType, messageTypeParamIndex);
|
||||
msgMatcher = TypeParameterMatcher.find(this, ChannelOutboundMessageHandlerAdapter.class, "I");
|
||||
}
|
||||
|
||||
protected final boolean isCloseOnFailedFlush() {
|
||||
|
Loading…
Reference in New Issue
Block a user