1 /*
2  * Copyright 2013 The Netty Project
3  *
4  * The Netty Project licenses this file to you under the Apache License,
5  * version 2.0 (the "License"); you may not use this file except in compliance
6  * with the License. You may obtain a copy of the License at:
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */

16
17 package io.netty.util.internal;
18
19 import java.lang.reflect.Array;
20 import java.lang.reflect.GenericArrayType;
21 import java.lang.reflect.ParameterizedType;
22 import java.lang.reflect.Type;
23 import java.lang.reflect.TypeVariable;
24 import java.util.HashMap;
25 import java.util.Map;
26
27 public abstract class TypeParameterMatcher {
28
29     private static final TypeParameterMatcher NOOP = new TypeParameterMatcher() {
30         @Override
31         public boolean match(Object msg) {
32             return true;
33         }
34     };
35
36     public static TypeParameterMatcher get(final Class<?> parameterType) {
37         final Map<Class<?>, TypeParameterMatcher> getCache =
38                 InternalThreadLocalMap.get().typeParameterMatcherGetCache();
39
40         TypeParameterMatcher matcher = getCache.get(parameterType);
41         if (matcher == null) {
42             if (parameterType == Object.class) {
43                 matcher = NOOP;
44             } else {
45                 matcher = new ReflectiveMatcher(parameterType);
46             }
47             getCache.put(parameterType, matcher);
48         }
49
50         return matcher;
51     }
52
53     public static TypeParameterMatcher find(
54             final Object object, final Class<?> parametrizedSuperclass, final String typeParamName) {
55
56         final Map<Class<?>, Map<String, TypeParameterMatcher>> findCache =
57                 InternalThreadLocalMap.get().typeParameterMatcherFindCache();
58         final Class<?> thisClass = object.getClass();
59
60         Map<String, TypeParameterMatcher> map = findCache.get(thisClass);
61         if (map == null) {
62             map = new HashMap<String, TypeParameterMatcher>();
63             findCache.put(thisClass, map);
64         }
65
66         TypeParameterMatcher matcher = map.get(typeParamName);
67         if (matcher == null) {
68             matcher = get(find0(object, parametrizedSuperclass, typeParamName));
69             map.put(typeParamName, matcher);
70         }
71
72         return matcher;
73     }
74
75     private static Class<?> find0(
76             final Object object, Class<?> parametrizedSuperclass, String typeParamName) {
77
78         final Class<?> thisClass = object.getClass();
79         Class<?> currentClass = thisClass;
80         for (;;) {
81             if (currentClass.getSuperclass() == parametrizedSuperclass) {
82                 int typeParamIndex = -1;
83                 TypeVariable<?>[] typeParams = currentClass.getSuperclass().getTypeParameters();
84                 for (int i = 0; i < typeParams.length; i ++) {
85                     if (typeParamName.equals(typeParams[i].getName())) {
86                         typeParamIndex = i;
87                         break;
88                     }
89                 }
90
91                 if (typeParamIndex < 0) {
92                     throw new IllegalStateException(
93                             "unknown type parameter '" + typeParamName + "': " + parametrizedSuperclass);
94                 }
95
96                 Type genericSuperType = currentClass.getGenericSuperclass();
97                 if (!(genericSuperType instanceof ParameterizedType)) {
98                     return Object.class;
99                 }
100
101                 Type[] actualTypeParams = ((ParameterizedType) genericSuperType).getActualTypeArguments();
102
103                 Type actualTypeParam = actualTypeParams[typeParamIndex];
104                 if (actualTypeParam instanceof ParameterizedType) {
105                     actualTypeParam = ((ParameterizedType) actualTypeParam).getRawType();
106                 }
107                 if (actualTypeParam instanceof Class) {
108                     return (Class<?>) actualTypeParam;
109                 }
110                 if (actualTypeParam instanceof GenericArrayType) {
111                     Type componentType = ((GenericArrayType) actualTypeParam).getGenericComponentType();
112                     if (componentType instanceof ParameterizedType) {
113                         componentType = ((ParameterizedType) componentType).getRawType();
114                     }
115                     if (componentType instanceof Class) {
116                         return Array.newInstance((Class<?>) componentType, 0).getClass();
117                     }
118                 }
119                 if (actualTypeParam instanceof TypeVariable) {
120                     // Resolved type parameter points to another type parameter.
121                     TypeVariable<?> v = (TypeVariable<?>) actualTypeParam;
122                     currentClass = thisClass;
123                     if (!(v.getGenericDeclaration() instanceof Class)) {
124                         return Object.class;
125                     }
126
127                     parametrizedSuperclass = (Class<?>) v.getGenericDeclaration();
128                     typeParamName = v.getName();
129                     if (parametrizedSuperclass.isAssignableFrom(thisClass)) {
130                         continue;
131                     } else {
132                         return Object.class;
133                     }
134                 }
135
136                 return fail(thisClass, typeParamName);
137             }
138             currentClass = currentClass.getSuperclass();
139             if (currentClass == null) {
140                 return fail(thisClass, typeParamName);
141             }
142         }
143     }
144
145     private static Class<?> fail(Class<?> type, String typeParamName) {
146         throw new IllegalStateException(
147                 "cannot determine the type of the type parameter '" + typeParamName + "': " + type);
148     }
149
150     public abstract boolean match(Object msg);
151
152     private static final class ReflectiveMatcher extends TypeParameterMatcher {
153         private final Class<?> type;
154
155         ReflectiveMatcher(Class<?> type) {
156             this.type = type;
157         }
158
159         @Override
160         public boolean match(Object msg) {
161             return type.isInstance(msg);
162         }
163     }
164
165     TypeParameterMatcher() { }
166 }
167