View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.util;
18  
19  import java.lang.reflect.GenericArrayType;
20  import java.lang.reflect.ParameterizedType;
21  import java.lang.reflect.Type;
22  import java.lang.reflect.WildcardType;
23  
24  /**
25   * Utility class for working with Java {@link Type}s and derivatives. This class is adapted heavily from the
26   * <a href="http://projects.spring.io/spring-framework/">Spring Framework</a>, specifically the
27   * <a href="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/TypeUtils.html">TypeUtils</a>
28   * class.
29   *
30   * @see java.lang.reflect.Type
31   * @see java.lang.reflect.GenericArrayType
32   * @see java.lang.reflect.ParameterizedType
33   * @see java.lang.reflect.WildcardType
34   * @see java.lang.Class
35   * @since 2.1
36   */
37  public final class TypeUtil {
38      private TypeUtil() {
39      }
40  
41      /**
42       * Indicates if two {@link Type}s are assignment compatible.
43       *
44       * @param lhs the left hand side to check assignability to
45       * @param rhs the right hand side to check assignability from
46       * @return {@code true} if it is legal to assign a variable of type {@code rhs} to a variable of type {@code lhs}
47       * @see Class#isAssignableFrom(Class)
48       */
49      public static boolean isAssignable(final Type lhs, final Type rhs) {
50          Assert.requireNonNull(lhs, "No left hand side type provided");
51          Assert.requireNonNull(rhs, "No right hand side type provided");
52          if (lhs.equals(rhs)) {
53              return true;
54          }
55          if (Object.class.equals(lhs)) {
56              // everything is assignable to Object
57              return true;
58          }
59          // raw type on left
60          if (lhs instanceof Class<?>) {
61              final Class<?> lhsClass = (Class<?>) lhs;
62              if (rhs instanceof Class<?>) {
63                  // no generics involved
64                  final Class<?> rhsClass = (Class<?>) rhs;
65                  return lhsClass.isAssignableFrom(rhsClass);
66              }
67              if (rhs instanceof ParameterizedType) {
68                  // check to see if the parameterized type has the same raw type as the lhs; this is legal
69                  final Type rhsRawType = ((ParameterizedType) rhs).getRawType();
70                  if (rhsRawType instanceof Class<?>) {
71                      return lhsClass.isAssignableFrom((Class<?>) rhsRawType);
72                  }
73              }
74              if (lhsClass.isArray() && rhs instanceof GenericArrayType) {
75                  // check for compatible array component types
76                  return isAssignable(lhsClass.getComponentType(), ((GenericArrayType) rhs).getGenericComponentType());
77              }
78          }
79          // parameterized type on left
80          if (lhs instanceof ParameterizedType) {
81              final ParameterizedType lhsType = (ParameterizedType) lhs;
82              if (rhs instanceof Class<?>) {
83                  final Type lhsRawType = lhsType.getRawType();
84                  if (lhsRawType instanceof Class<?>) {
85                      return ((Class<?>) lhsRawType).isAssignableFrom((Class<?>) rhs);
86                  }
87              } else if (rhs instanceof ParameterizedType) {
88                  final ParameterizedType rhsType = (ParameterizedType) rhs;
89                  return isParameterizedAssignable(lhsType, rhsType);
90              }
91          }
92          // generic array type on left
93          if (lhs instanceof GenericArrayType) {
94              final Type lhsComponentType = ((GenericArrayType) lhs).getGenericComponentType();
95              if (rhs instanceof Class<?>) {
96                  // raw type on right
97                  final Class<?> rhsClass = (Class<?>) rhs;
98                  if (rhsClass.isArray()) {
99                      return isAssignable(lhsComponentType, rhsClass.getComponentType());
100                 }
101             } else if (rhs instanceof GenericArrayType) {
102                 return isAssignable(lhsComponentType, ((GenericArrayType) rhs).getGenericComponentType());
103             }
104         }
105         // wildcard type on left
106         if (lhs instanceof WildcardType) {
107             return isWildcardAssignable((WildcardType) lhs, rhs);
108         }
109         // strange...
110         return false;
111     }
112 
113     private static boolean isParameterizedAssignable(final ParameterizedType lhs, final ParameterizedType rhs) {
114         if (lhs.equals(rhs)) {
115             // that was easy
116             return true;
117         }
118         final Type[] lhsTypeArguments = lhs.getActualTypeArguments();
119         final Type[] rhsTypeArguments = rhs.getActualTypeArguments();
120         final int size = lhsTypeArguments.length;
121         if (rhsTypeArguments.length != size) {
122             // clearly incompatible types
123             return false;
124         }
125         for (int i = 0; i < size; i++) {
126             // verify all type arguments are assignable
127             final Type lhsArgument = lhsTypeArguments[i];
128             final Type rhsArgument = rhsTypeArguments[i];
129             if (!lhsArgument.equals(rhsArgument) &&
130                 !(lhsArgument instanceof WildcardType &&
131                     isWildcardAssignable((WildcardType) lhsArgument, rhsArgument))) {
132                 return false;
133             }
134         }
135         return true;
136     }
137 
138     private static boolean isWildcardAssignable(final WildcardType lhs, final Type rhs) {
139         final Type[] lhsUpperBounds = getEffectiveUpperBounds(lhs);
140         final Type[] lhsLowerBounds = getEffectiveLowerBounds(lhs);
141         if (rhs instanceof WildcardType) {
142             // oh boy, this scenario requires checking a lot of assignability!
143             final WildcardType rhsType = (WildcardType) rhs;
144             final Type[] rhsUpperBounds = getEffectiveUpperBounds(rhsType);
145             final Type[] rhsLowerBounds = getEffectiveLowerBounds(rhsType);
146             for (final Type lhsUpperBound : lhsUpperBounds) {
147                 for (final Type rhsUpperBound : rhsUpperBounds) {
148                     if (!isBoundAssignable(lhsUpperBound, rhsUpperBound)) {
149                         return false;
150                     }
151                 }
152                 for (final Type rhsLowerBound : rhsLowerBounds) {
153                     if (!isBoundAssignable(lhsUpperBound, rhsLowerBound)) {
154                         return false;
155                     }
156                 }
157             }
158             for (final Type lhsLowerBound : lhsLowerBounds) {
159                 for (final Type rhsUpperBound : rhsUpperBounds) {
160                     if (!isBoundAssignable(rhsUpperBound, lhsLowerBound)) {
161                         return false;
162                     }
163                 }
164                 for (final Type rhsLowerBound : rhsLowerBounds) {
165                     if (!isBoundAssignable(rhsLowerBound, lhsLowerBound)) {
166                         return false;
167                     }
168                 }
169             }
170         } else {
171             // phew, far less bounds to check
172             for (final Type lhsUpperBound : lhsUpperBounds) {
173                 if (!isBoundAssignable(lhsUpperBound, rhs)) {
174                     return false;
175                 }
176             }
177             for (final Type lhsLowerBound : lhsLowerBounds) {
178                 if (!isBoundAssignable(lhsLowerBound, rhs)) {
179                     return false;
180                 }
181             }
182         }
183         return true;
184     }
185 
186     private static Type[] getEffectiveUpperBounds(final WildcardType type) {
187         final Type[] upperBounds = type.getUpperBounds();
188         return upperBounds.length == 0 ? new Type[]{Object.class} : upperBounds;
189     }
190 
191     private static Type[] getEffectiveLowerBounds(final WildcardType type) {
192         final Type[] lowerBounds = type.getLowerBounds();
193         return lowerBounds.length == 0 ? new Type[]{null} : lowerBounds;
194     }
195 
196     private static boolean isBoundAssignable(final Type lhs, final Type rhs) {
197         return (rhs == null) || ((lhs != null) && isAssignable(lhs, rhs));
198     }
199 }