blob: f694d60f9fc9cc1fb28960459bb3502b693fa9ea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.logging.log4j.plugins.util;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Utility class for working with Java {@link Type}s and derivatives. This class is adapted heavily from the
* <a href="http://projects.spring.io/spring-framework/">Spring Framework</a>, specifically the
* <a href="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/TypeUtils.html">TypeUtils</a>
* class.
*
* @see Type
* @see GenericArrayType
* @see ParameterizedType
* @see WildcardType
* @see Class
* @since 2.1
*/
public final class TypeUtil {
private TypeUtil() {
}
/**
* Gets all declared fields for the given class (including superclasses).
*
* @param cls the class to examine
* @return all declared fields for the given class (including superclasses).
* @see Class#getDeclaredFields()
*/
public static List<Field> getAllDeclaredFields(Class<?> cls) {
final List<Field> fields = new ArrayList<>();
while (cls != null) {
fields.addAll(Arrays.asList(cls.getDeclaredFields()));
cls = cls.getSuperclass();
}
return fields;
}
/**
* Indicates if two {@link Type}s are assignment compatible.
*
* @param lhs the left hand side to check assignability to
* @param rhs the right hand side to check assignability from
* @return {@code true} if it is legal to assign a variable of type {@code rhs} to a variable of type {@code lhs}
* @see Class#isAssignableFrom(Class)
*/
public static boolean isAssignable(final Type lhs, final Type rhs) {
Objects.requireNonNull(lhs, "No left hand side type provided");
Objects.requireNonNull(rhs, "No right hand side type provided");
if (lhs.equals(rhs)) {
return true;
}
if (Object.class.equals(lhs)) {
// everything is assignable to Object
return true;
}
// raw type on left
if (lhs instanceof Class<?>) {
final Class<?> lhsClass = (Class<?>) lhs;
if (rhs instanceof Class<?>) {
// no generics involved
final Class<?> rhsClass = (Class<?>) rhs;
return lhsClass.isAssignableFrom(rhsClass);
}
if (rhs instanceof ParameterizedType) {
// check to see if the parameterized type has the same raw type as the lhs; this is legal
final Type rhsRawType = ((ParameterizedType) rhs).getRawType();
if (rhsRawType instanceof Class<?>) {
return lhsClass.isAssignableFrom((Class<?>) rhsRawType);
}
}
if (lhsClass.isArray() && rhs instanceof GenericArrayType) {
// check for compatible array component types
return isAssignable(lhsClass.getComponentType(), ((GenericArrayType) rhs).getGenericComponentType());
}
}
// parameterized type on left
if (lhs instanceof ParameterizedType) {
final ParameterizedType lhsType = (ParameterizedType) lhs;
if (rhs instanceof Class<?>) {
final Type lhsRawType = lhsType.getRawType();
if (lhsRawType instanceof Class<?>) {
return ((Class<?>) lhsRawType).isAssignableFrom((Class<?>) rhs);
}
} else if (rhs instanceof ParameterizedType) {
final ParameterizedType rhsType = (ParameterizedType) rhs;
return isParameterizedAssignable(lhsType, rhsType);
}
}
// generic array type on left
if (lhs instanceof GenericArrayType) {
final Type lhsComponentType = ((GenericArrayType) lhs).getGenericComponentType();
if (rhs instanceof Class<?>) {
// raw type on right
final Class<?> rhsClass = (Class<?>) rhs;
if (rhsClass.isArray()) {
return isAssignable(lhsComponentType, rhsClass.getComponentType());
}
} else if (rhs instanceof GenericArrayType) {
return isAssignable(lhsComponentType, ((GenericArrayType) rhs).getGenericComponentType());
}
}
// wildcard type on left
if (lhs instanceof WildcardType) {
return isWildcardAssignable((WildcardType) lhs, rhs);
}
// strange...
return false;
}
private static boolean isParameterizedAssignable(final ParameterizedType lhs, final ParameterizedType rhs) {
if (lhs.equals(rhs)) {
// that was easy
return true;
}
final Type[] lhsTypeArguments = lhs.getActualTypeArguments();
final Type[] rhsTypeArguments = rhs.getActualTypeArguments();
final int size = lhsTypeArguments.length;
if (rhsTypeArguments.length != size) {
// clearly incompatible types
return false;
}
for (int i = 0; i < size; i++) {
// verify all type arguments are assignable
final Type lhsArgument = lhsTypeArguments[i];
final Type rhsArgument = rhsTypeArguments[i];
if (!lhsArgument.equals(rhsArgument) &&
!(lhsArgument instanceof WildcardType &&
isWildcardAssignable((WildcardType) lhsArgument, rhsArgument))) {
return false;
}
}
return true;
}
private static boolean isWildcardAssignable(final WildcardType lhs, final Type rhs) {
final Type[] lhsUpperBounds = getEffectiveUpperBounds(lhs);
final Type[] lhsLowerBounds = getEffectiveLowerBounds(lhs);
if (rhs instanceof WildcardType) {
// oh boy, this scenario requires checking a lot of assignability!
final WildcardType rhsType = (WildcardType) rhs;
final Type[] rhsUpperBounds = getEffectiveUpperBounds(rhsType);
final Type[] rhsLowerBounds = getEffectiveLowerBounds(rhsType);
for (final Type lhsUpperBound : lhsUpperBounds) {
for (final Type rhsUpperBound : rhsUpperBounds) {
if (!isBoundAssignable(lhsUpperBound, rhsUpperBound)) {
return false;
}
}
for (final Type rhsLowerBound : rhsLowerBounds) {
if (!isBoundAssignable(lhsUpperBound, rhsLowerBound)) {
return false;
}
}
}
for (final Type lhsLowerBound : lhsLowerBounds) {
for (final Type rhsUpperBound : rhsUpperBounds) {
if (!isBoundAssignable(rhsUpperBound, lhsLowerBound)) {
return false;
}
}
for (final Type rhsLowerBound : rhsLowerBounds) {
if (!isBoundAssignable(rhsLowerBound, lhsLowerBound)) {
return false;
}
}
}
} else {
// phew, far less bounds to check
for (final Type lhsUpperBound : lhsUpperBounds) {
if (!isBoundAssignable(lhsUpperBound, rhs)) {
return false;
}
}
for (final Type lhsLowerBound : lhsLowerBounds) {
if (!isBoundAssignable(lhsLowerBound, rhs)) {
return false;
}
}
}
return true;
}
private static Type[] getEffectiveUpperBounds(final WildcardType type) {
final Type[] upperBounds = type.getUpperBounds();
return upperBounds.length == 0 ? new Type[]{Object.class} : upperBounds;
}
private static Type[] getEffectiveLowerBounds(final WildcardType type) {
final Type[] lowerBounds = type.getLowerBounds();
return lowerBounds.length == 0 ? new Type[]{null} : lowerBounds;
}
private static boolean isBoundAssignable(final Type lhs, final Type rhs) {
return (rhs == null) || ((lhs != null) && isAssignable(lhs, rhs));
}
}