下面列出了com.fasterxml.jackson.databind.introspect.BasicBeanDescription#ru.vyarus.java.generics.resolver.context.GenericsContext 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
/**
* Cases:
* <ul>
* <li>Type declared in class and used as is: resolve generics directly from type</li>
* <li>Type declared, but lower type used (e.g. collection impl declared): track lower type generics</li>
* <li>Type is Object: resolve generics from declaration (lower type available when value provided)</li>
* </ul>.
*
* @param genericsContext generics context
* @param type declared property type
* @param typeClass type's class
* @param objectDeclared declared type is not Object
* @param lowerType selected type from declaration (maybe lower then declared or comletely taken from value)
* @return lower type generics or empty list
*/
private static List<Type> resolveLowerGenerics(final GenericsContext genericsContext,
final Type type,
final Class typeClass,
final boolean objectDeclared,
final Class lowerType) {
final List<Type> res;
if (!objectDeclared && !lowerType.equals(typeClass)) {
// case: collection declared, but not interface.. going to lower type
res = genericsContext.inlyingType(type).type(lowerType).genericTypes();
} else {
// normal case: type analyzed directly
res = genericsContext.resolveTypeGenerics(objectDeclared ? lowerType : type);
}
return res;
}
/**
* Cases:
* <ul>
* <li>Object declared in class: nowere to get generics, resolving from type definition</li>
* <li>Track type generics from declared generics (may not recover all, in this case generics declaration
* would be used)</li>
* </ul>.
* Note that lowerType == upperType case checked outside.
*
* @param genericsContext generics context
* @param type declared property type
* @param objectDeclared declared type is not Object
* @param upperType value type (if value available) or declaration type (from class; may be upper then
* lowerType in case of collection)
* @return upper type generics or empty list
*/
private static List<Type> resolveUpperGenerics(final GenericsContext genericsContext,
final Type type,
final boolean objectDeclared,
final Class upperType) {
final List<Type> res;
if (objectDeclared) {
// no generics declared in class - use raw generics (from type declaration)
res = genericsContext.resolveTypeGenerics(upperType);
} else {
// computing from actual generic type because it may contain generics, not required for lower type
// e.g. declaration is ExtraList<String, Integer> lower type will be List<String>
// but we need to preserve full generics info for upper type
res = genericsContext.inlyingTypeAs(type, upperType).genericTypes();
}
return res;
}
/**
* Binds jersey specific component (component implements jersey interface or extends class).
* Specific binding is required for types directly supported by jersey (e.g. ExceptionMapper).
* Such types must be bound to target interface directly, otherwise jersey would not be able to resolve them.
* <p> If type is {@link JerseyManaged}, binds directly.
* Otherwise, use guice "bridge" factory to lazily bind type.</p>
*
* @param binder jersey binder
* @param injector guice injector
* @param type type which implements specific jersey interface or extends class
* @param specificType specific jersey type (interface or abstract class)
* @param jerseyManaged true if bean must be managed by jersey, false to bind guice managed instance
* @param singleton true to force singleton scope
*/
public static void bindSpecificComponent(final AbstractBinder binder,
final Injector injector,
final Class<?> type,
final Class<?> specificType,
final boolean jerseyManaged,
final boolean singleton) {
// resolve generics of specific type
final GenericsContext context = GenericsResolver.resolve(type).type(specificType);
final List<Type> genericTypes = context.genericTypes();
final Type[] generics = genericTypes.toArray(new Type[0]);
final Type bindingType = generics.length > 0 ? new ParameterizedTypeImpl(specificType, generics)
: specificType;
if (jerseyManaged) {
optionalSingleton(
binder.bind(type).to(type).to(bindingType),
singleton);
} else {
optionalSingleton(
binder.bindFactory(new GuiceComponentFactory<>(injector, type)).to(type).to(bindingType),
singleton);
}
}
/**
* Analyze target bean methods, finding all matching (by parameters) methods.
* If method name was specified, only methods with the same name resolved.
*
* @param target target bean type
* @param params repository method params
* @param hint method name hint (may be null)
* @return descriptor of all matching methods
*/
private static List<MatchedMethod> findPossibleMethods(final List<Class<?>> params, final Class<?> target,
final String hint) {
final List<MatchedMethod> possibilities = Lists.newArrayList();
// use generics to enforce type checks
final GenericsContext targetGenerics = GenericsResolver.resolve(target);
for (Method method : target.getMethods()) {
// method hint force to check only methods with this name
final boolean methodHintValid = hint == null || method.getName().equals(hint);
if (!isAcceptableMethod(method) || !methodHintValid) {
continue;
}
final MatchedMethod matched = analyzeMethod(method, params, targetGenerics);
if (matched != null) {
possibilities.add(matched);
}
}
return possibilities;
}
@SuppressWarnings({"unchecked", "PMD.AvoidInstantiatingObjectsInLoops"})
private static MatchedMethod analyzeMethod(final Method method, final List<Class<?>> params,
final GenericsContext targetGenerics) {
final List<ParamInfo> ordinalParamsInfo = Lists.newArrayList();
final List<Class<?>> types = targetGenerics.method(method).resolveParameters();
final Annotation[][] annotations = method.getParameterAnnotations();
boolean extended = false;
for (int i = 0; i < types.size(); i++) {
// ignore extensions (they always add value)
try {
if (ExtUtils.findParameterExtension(annotations[i]) == null) {
ordinalParamsInfo.add(new ParamInfo(i, types.get(i)));
} else {
extended = true;
}
} catch (Exception ex) {
throw new IllegalStateException(String.format("Error analysing method %s parameter %s",
RepositoryUtils.methodToString(method), i), ex);
}
}
MatchedMethod res = null;
if (isParametersCompatible(params, ordinalParamsInfo)) {
res = new MatchedMethod(method, ordinalParamsInfo, extended);
}
return res;
}
/**
* Type analysis in context of analyzed type. For example, resolution of field type class in context of
* analyzed class (so we can correctly resolve it's generics).In essence, the only difference with usual type
* resolution is known root generics.
* <p>
* The result is not intended to be cached as it's context-sensitive.
*
* @param context generics context of containing class
* @param type type to analyze (important: this must be generified type and not raw class in
* order to properly resolve generics)
* @param ignoreClasses classes to exclude from hierarchy analysis
* @return analyzed type generics info
*/
public static GenericsInfo create(
final GenericsContext context, final Type type, final Class<?>... ignoreClasses) {
// root generics are required only to properly solve type
final Map<String, Type> rootGenerics = context.visibleGenericsMap();
// first step: solve type to replace transitive generics with direct values
final Type actual = GenericsUtils.resolveTypeVariables(type, rootGenerics);
final Class<?> target = context.resolveClass(actual);
LinkedHashMap<String, Type> generics = GenericsResolutionUtils.resolveGenerics(actual, rootGenerics);
generics = GenericsResolutionUtils
.fillOuterGenerics(actual, generics, context.getGenericsInfo().getTypesMap());
return create(target, generics,
// store possible owner types from parent context
usePossiblyOwnerGenerics(target, context.getGenericsInfo()), ignoreClasses);
}
/**
* Create item for property.
* <p>
* Almost always property declaration type is used as binding type (for future binding), but:
* <ul>
* <li>If property type is collection implementation, then collection interface used instead</li>
* <li>If property is Object then type would be taken from value. This means binding type will be Object
* when value null and actual type when value provided. Assuming this case will not happen (bad config).</li>
* </ul>
*
* @param root root property (containing), may be null for roots
* @param prop jackson property descriptor
* @param value property value, may be null
* @param genericsContext generics context
* @return path item object
*/
private static ConfigPath createItem(final ConfigPath root,
final BeanPropertyDefinition prop,
final Object value,
final GenericsContext genericsContext) {
// need generified type to resolve generics manually because jackson's generics resolution
// couldn't handle all required cases
final Type type = prop.getGetter() != null
? prop.getGetter().getAnnotated().getGenericReturnType()
: prop.getField().getAnnotated().getGenericType();
final Class typeClass = Primitives.wrap(genericsContext.resolveClass(type));
// upper possible known type (for introspection): ideally type of actually used configuration value
// note that even when value is null upper type could be different from lower type due to collection projection
final Class upperType = value == null ? typeClass : value.getClass();
final boolean customType = isCustomType(upperType);
// either class declaration or value type (in both cases could be projected to collection interface)
final boolean objectDeclared = Object.class.equals(typeClass);
final Class lowerType = correctValueType(objectDeclared ? upperType : typeClass, customType);
final List<Type> lowerGenerics =
resolveLowerGenerics(genericsContext, type, typeClass, objectDeclared, lowerType);
final List<Type> upperGenerics = lowerType.equals(upperType) ? lowerGenerics
: resolveUpperGenerics(genericsContext, type, objectDeclared, upperType);
return new ConfigPath(
root,
prop.getAccessor().getDeclaringClass(),
lowerType,
// as an example, enum constant type could lead to anonymous class
upperType.isAnonymousClass() ? lowerType : upperType,
lowerGenerics,
upperGenerics,
fullPath(root, prop),
value,
customType,
objectDeclared);
}
/**
* Analyze query string for variables.
*
* @param genericsContext generics context (set to the method owner type)
* @param query query string
* @return descriptor object if variables found, null otherwise
*/
public static ElDescriptor analyzeQuery(final GenericsContext genericsContext, final String query) {
final List<String> vars = ElUtils.findVars(query);
ElDescriptor descriptor = null;
if (!vars.isEmpty()) {
descriptor = new ElDescriptor(vars);
checkGenericVars(descriptor, genericsContext);
}
return descriptor;
}
private static void checkGenericVars(final ElDescriptor descriptor, final GenericsContext generics) {
if (generics != null) {
for (Map.Entry<String, Type> entry : generics.genericsMap().entrySet()) {
final String key = entry.getKey();
if (descriptor.vars.contains(key)) {
// using just class name, because orient don't need package
descriptor.directValues.put(key, generics.resolveClass(entry.getValue()).getSimpleName());
}
}
}
}
private static Type[] alignParametrizationArguments(final Class<?> exactActualType,
final Class<?> knownGenericType,
final ParameterizedType knownGeneric,
final LinkedHashMap<String, Type> knownGenerics) {
final Type[] knownArguments;
// if base types are equal we can match types in parametrization
if (exactActualType.equals(knownGenericType)) {
knownArguments = knownGeneric.getActualTypeArguments();
} else {
// known generic type is a subclass of resolved root type.. inception!
// trying to track generics
if (knownGenericType.isAssignableFrom(exactActualType)) {
// Actual type is higher then declared in generic: need to analyze this mismatch
// (again not known root generics and known generics in sub type)
final LinkedHashMap<String, Type> sub = track(exactActualType, knownGenericType,
GenericsResolutionUtils.resolveGenerics(knownGeneric, knownGenerics));
knownArguments = sub.values().toArray(new Type[0]);
} else {
// actual class, resolved in root class hierarchy is a subtype of known generic type
// building hierarchy for known generic value class and look generics of required subclass
final GenericsContext ctx = GenericsResolver.resolve(knownGenericType);
knownArguments = GenericInfoUtils.create(ctx, knownGeneric)
.getTypeGenerics(exactActualType).values().toArray(new Type[0]);
}
}
return knownArguments;
}
/**
* Use jackson serialization api to extract all configuration values with paths from configuration object.
* Always analyze types, even if actual branch is not present at all (null value) in order to always bind
* nulls and avoid "Schrodinger's binding" case. In short, bindings should not depend on configuration values
* (presence).
* <p>
* Still, bindings may vary: for example, bound implementations may differ (best example is dropwizard server type),
* as a consequences, parsed type may be different and so different properties paths could be recognized.
*
* @param config jackson serialization config
* @param content currently parsed paths
* @param type analyzed part type
* @param object analyzed part instance (may be null)
* @return all configuration paths values
*/
@SuppressWarnings("checkstyle:CyclomaticComplexity")
private static List<ConfigPath> resolvePaths(final SerializationConfig config,
final ConfigPath root,
final List<ConfigPath> content,
final Class type,
final Object object,
final GenericsContext genericsContext) {
final BasicBeanDescription description = config.introspect(
config.constructType(type)
);
for (BeanPropertyDefinition prop : description.findProperties()) {
// ignore write-only or groovy special property
if (!prop.couldSerialize() || prop.getName().equals("metaClass")) {
continue;
}
final Object value;
// if configuration doesn't expect serialization and throws error on access
// (like netflix dynamic properties) it should not break app startup
try {
value = readValue(prop.getAccessor(), object);
} catch (Exception ex) {
LOGGER.warn("Can't bind configuration path '{}' due to {}: {}. Enable debug logs to see "
+ "complete stack trace or use @JsonIgnore on property getter.",
fullPath(root, prop), ex.getClass().getSimpleName(), ex.getMessage());
LOGGER.debug("Complete error: ", ex);
continue;
}
final ConfigPath item = createItem(root, prop, value, genericsContext);
content.add(item);
if (root != null) {
root.getChildren().add(item);
}
if (item.isCustomType() && !detectRecursion(item)) {
// build generics context for actual value type (if not null)
final GenericsContext subContext = prop.getGetter() != null
? genericsContext.method(prop.getGetter().getAnnotated()).returnTypeAs(item.getValueType())
: genericsContext.fieldTypeAs(prop.getField().getAnnotated(), item.getValueType());
resolvePaths(config, item, content, item.getValueType(),
item.getValue(), subContext);
}
}
if (root != null) {
// simple properties goes up and composite objects go lower (both groups sorted alphabetically)
root.getChildren().sort(Comparator.comparing(o -> (o.isCustomType() ? 'b' : 'a') + o.getPath()));
}
return content;
}
/**
* Type analysis in context of analyzed type with child class as target type. Case: we have interface
* (or base type) with generic in class (as field or return type), but we need to analyze actual
* instance type (from value). This method will analyze type from new root (where generics are unknown), but
* will add known middle generics.
* <p>
* NOTE: some of the root generics could possibly be resolved if there are any traceable connectivity between
* the root class and known middle generics. All possible (known) cases should be solved. For example,
* {@code Root<K> extends Target<List<K>>} when we know {@code Target<Collection<String>>} then
* K will be tracked as String.
* <p>
* In essence: root generics are partially resolved by tracking definition from known middle class.
* Other root generics resolved as upper bound (the same as in usual type resolution case).
* If middle type generic is not specified (and so resolved as Object) then known specific type used
* (assuming root type would be used in place with known parametrization and so more specifi generic may be
* counted).
* <p>
* The result is not intended to be cached as it's context-sensitive.
*
* @param context generics context of containing class
* @param type type to analyze (important: this must be generified type and not raw class in
* order to properly resolve generics)
* @param asType target child type (this class contain original type in hierarchy)
* @param ignoreClasses classes to exclude from hierarchy analysis
* @return analyzed type generics info
*/
public static GenericsInfo create(final GenericsContext context,
final Type type,
final Class<?> asType,
final Class<?>... ignoreClasses) {
// root generics are required only to properly solve type
final Map<String, Type> rootGenerics = context.visibleGenericsMap();
// first step: solve type to replace transitive generics with direct values
final Type actual = GenericsUtils.resolveTypeVariables(type, rootGenerics);
final Class<?> middleType = context.resolveClass(actual);
if (!middleType.isAssignableFrom(asType)) {
throw new IllegalArgumentException(String.format("Requested type %s is not a subtype of %s",
TypeToStringUtils.toStringType(asType), TypeToStringUtils.toStringType(middleType)));
}
// known middle type
LinkedHashMap<String, Type> typeGenerics = GenericsResolutionUtils.resolveGenerics(actual, rootGenerics);
final Map<Class<?>, LinkedHashMap<String, Type>> knownGenerics =
new HashMap<Class<?>, LinkedHashMap<String, Type>>();
// field could be declared as (Outer<String>.Inner field) and already contain actual outer generics
knownGenerics.put(middleType, GenericsResolutionUtils
.fillOuterGenerics(actual, typeGenerics, context.getGenericsInfo().getTypesMap()));
if (TypeUtils.isInner(middleType)) {
// remember possibly specified outer generics (they were already resolved above)
knownGenerics.put((Class) TypeUtils.getOuter(middleType), new LinkedHashMap<String, Type>(
GenericsUtils.extractOwnerGenerics(middleType, knownGenerics.get(middleType))));
} else {
// store other types for possible outer classes generics resolution
knownGenerics.putAll(usePossiblyOwnerGenerics(asType, context.getGenericsInfo()));
}
// root type
typeGenerics = asType.getTypeParameters().length > 0
? GenericsTrackingUtils.track(asType, middleType, typeGenerics)
: EmptyGenericsMap.getInstance();
typeGenerics = GenericsResolutionUtils
.fillOuterGenerics(asType, typeGenerics, knownGenerics.size() > 1
// if known middle type is inner class then owner already filled
? knownGenerics : context.getGenericsInfo().getTypesMap());
return create(asType, typeGenerics, knownGenerics, ignoreClasses);
}
/**
* By default returned context set on root class (but generic types for root class will be resolved from specified
* generics bounds). To use it switch context to required type from hierarchy:
* {@code returnedContext.type(SomeTypeFromHierarchy.class)}.
* <p>
* Note: when ignore classes provided, produced {@code GenericsInfo} instance will not be cached
* (and full version from cache will not be used also)
*
* @param type root class to resolve generics hierarchy
* @param ignoreClasses list of classes to ignore during inspection (useful to avoid interface clashes
* or to limit resolution depth)
* @return resolved generics context object
*/
public static GenericsContext resolve(final Class<?> type, final Class<?>... ignoreClasses) {
final Class<?> notPrimitiveType = TypeUtils.wrapPrimitive(type);
return new GenericsContext(
GenericsInfoFactory.create(notPrimitiveType, ignoreClasses), notPrimitiveType);
}