More robust type parameter detection

- Also removed unnecessary constructors which were added due to incomplete type parameter detection logic
This commit is contained in:
Trustin Lee 2013-02-13 19:02:55 -08:00
parent d0a3c2d95e
commit 53c27ef5ae
9 changed files with 183 additions and 137 deletions

View File

@ -53,14 +53,7 @@ public abstract class ByteToMessageCodec<I> extends ChannelDuplexHandler
}; };
protected ByteToMessageCodec() { protected ByteToMessageCodec() {
this(ByteToMessageCodec.class, 0); outboundMsgMatcher = TypeParameterMatcher.find(this, ByteToMessageCodec.class, "I");
}
protected ByteToMessageCodec(
@SuppressWarnings("rawtypes")
Class<? extends ByteToMessageCodec> parameterizedHandlerType,
int messageTypeParamIndex) {
outboundMsgMatcher = TypeParameterMatcher.find(this, parameterizedHandlerType, messageTypeParamIndex);
} }
@Override @Override

View File

@ -18,9 +18,7 @@ package io.netty.handler.codec;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.MessageBuf; import io.netty.buffer.MessageBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundMessageHandler;
import io.netty.channel.ChannelOutboundMessageHandlerAdapter; 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> { 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 @Override
protected void flush(ChannelHandlerContext ctx, I msg) throws Exception { protected void flush(ChannelHandlerContext ctx, I msg) throws Exception {
try { try {

View File

@ -19,9 +19,7 @@ import io.netty.buffer.MessageBuf;
import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundMessageHandler; import io.netty.channel.ChannelInboundMessageHandler;
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import io.netty.channel.ChannelOutboundMessageHandler; import io.netty.channel.ChannelOutboundMessageHandler;
import io.netty.channel.ChannelOutboundMessageHandlerAdapter;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.util.internal.TypeParameterMatcher; import io.netty.util.internal.TypeParameterMatcher;
@ -88,22 +86,8 @@ public abstract class MessageToMessageCodec<INBOUND_IN, OUTBOUND_IN>
private final TypeParameterMatcher outboundMsgMatcher; private final TypeParameterMatcher outboundMsgMatcher;
protected MessageToMessageCodec() { protected MessageToMessageCodec() {
inboundMsgMatcher = TypeParameterMatcher.find(this, MessageToMessageCodec.class, 0); inboundMsgMatcher = TypeParameterMatcher.find(this, MessageToMessageCodec.class, "INBOUND_IN");
outboundMsgMatcher = TypeParameterMatcher.find(this, MessageToMessageCodec.class, 1); outboundMsgMatcher = TypeParameterMatcher.find(this, MessageToMessageCodec.class, "OUTBOUND_IN");
}
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);
} }
@Override @Override

View File

@ -44,17 +44,6 @@ import io.netty.channel.ChannelInboundMessageHandlerAdapter;
*/ */
public abstract class MessageToMessageDecoder<I> extends ChannelInboundMessageHandlerAdapter<I> { 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 @Override
protected final void messageReceived(ChannelHandlerContext ctx, I msg) throws Exception { protected final void messageReceived(ChannelHandlerContext ctx, I msg) throws Exception {
ctx.nextInboundMessageBuffer().unfoldAndAdd(decode(ctx, msg)); ctx.nextInboundMessageBuffer().unfoldAndAdd(decode(ctx, msg));

View File

@ -17,9 +17,7 @@ package io.netty.handler.codec;
import io.netty.buffer.MessageBuf; import io.netty.buffer.MessageBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundMessageHandler;
import io.netty.channel.ChannelOutboundMessageHandlerAdapter; import io.netty.channel.ChannelOutboundMessageHandlerAdapter;
import io.netty.channel.ChannelPipeline;
/** /**
* {@link ChannelOutboundMessageHandlerAdapter} which encodes from one message to an other message * {@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> { 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 @Override
protected final void flush(ChannelHandlerContext ctx, I msg) throws Exception { protected final void flush(ChannelHandlerContext ctx, I msg) throws Exception {
try { try {

View File

@ -16,80 +16,113 @@
package io.netty.util.internal; package io.netty.util.internal;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.lang.reflect.TypeVariable;
import java.util.Collections; import java.util.HashMap;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public abstract class TypeParameterMatcher { public abstract class TypeParameterMatcher {
private static final TypeParameterMatcher NOOP = new NoOpTypeParameterMatcher(); private static final TypeParameterMatcher NOOP = new NoOpTypeParameterMatcher();
private static final ThreadLocal<Map<Class<?>, TypeParameterMatcher>> typeMap = private static final ThreadLocal<Map<Class<?>, Map<String, TypeParameterMatcher>>> typeMap =
new ThreadLocal<Map<Class<?>, TypeParameterMatcher>>() { new ThreadLocal<Map<Class<?>, Map<String, TypeParameterMatcher>>>() {
@Override @Override
protected Map<Class<?>, TypeParameterMatcher> initialValue() { protected Map<Class<?>, Map<String, TypeParameterMatcher>> initialValue() {
return new IdentityHashMap<Class<?>, TypeParameterMatcher>(); return new IdentityHashMap<Class<?>, Map<String, TypeParameterMatcher>>();
} }
}; };
public static TypeParameterMatcher find( 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(); 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) { if (matcher == null) {
Class<?> currentClass = thisClass; Class<?> messageType = find0(object, parameterizedSuperclass, typeParamName);
for (;;) { if (messageType == Object.class) {
if (currentClass.getSuperclass() == parameterizedSuperClass) { matcher = NOOP;
Type[] types = ((ParameterizedType) currentClass.getGenericSuperclass()).getActualTypeArguments(); } else if (PlatformDependent.hasJavassist()) {
if (types.length - 1 < typeParamIndex) { try {
List<Type> typeList = new ArrayList<Type>(types.length); matcher = JavassistTypeParameterMatcherGenerator.generate(messageType);
Collections.addAll(typeList, types); } catch (Exception e) {
throw new IllegalStateException( // Will not usually happen, but just in case.
"invalid typeParamIndex: " + typeParamIndex + " (typeParams: " + typeList + ')'); matcher = new ReflectiveMatcher(messageType);
}
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;
} }
currentClass = currentClass.getSuperclass(); } else {
matcher = new ReflectiveMatcher(messageType);
} }
map.put(typeParamName, matcher);
typeMap.put(thisClass, matcher);
} }
return 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); public abstract boolean match(Object msg);
private static final class ReflectiveMatcher extends TypeParameterMatcher { private static final class ReflectiveMatcher extends TypeParameterMatcher {

View File

@ -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 { }
}

View File

@ -54,14 +54,7 @@ public abstract class ChannelInboundMessageHandlerAdapter<I>
private final TypeParameterMatcher msgMatcher; private final TypeParameterMatcher msgMatcher;
protected ChannelInboundMessageHandlerAdapter() { protected ChannelInboundMessageHandlerAdapter() {
this(ChannelInboundMessageHandlerAdapter.class, 0); msgMatcher = TypeParameterMatcher.find(this, ChannelInboundMessageHandlerAdapter.class, "I");
}
protected ChannelInboundMessageHandlerAdapter(
@SuppressWarnings("rawtypes")
Class<? extends ChannelInboundMessageHandlerAdapter> parameterizedHandlerType,
int messageTypeParamIndex) {
msgMatcher = TypeParameterMatcher.find(this, parameterizedHandlerType, messageTypeParamIndex);
} }
@Override @Override

View File

@ -39,14 +39,7 @@ public abstract class ChannelOutboundMessageHandlerAdapter<I>
private boolean closeOnFailedFlush = true; private boolean closeOnFailedFlush = true;
protected ChannelOutboundMessageHandlerAdapter() { protected ChannelOutboundMessageHandlerAdapter() {
this(ChannelOutboundMessageHandlerAdapter.class, 0); msgMatcher = TypeParameterMatcher.find(this, ChannelOutboundMessageHandlerAdapter.class, "I");
}
protected ChannelOutboundMessageHandlerAdapter(
@SuppressWarnings("rawtypes")
Class<? extends ChannelOutboundMessageHandlerAdapter> parameterizedHandlerType,
int messageTypeParamIndex) {
msgMatcher = TypeParameterMatcher.find(this, parameterizedHandlerType, messageTypeParamIndex);
} }
protected final boolean isCloseOnFailedFlush() { protected final boolean isCloseOnFailedFlush() {