org.eclipse.yasson.internal.ComponentMatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yasson Show documentation
Show all versions of yasson Show documentation
Eclipse Yasson. Reference implementation of JSR-367 (JSON-B).
/*******************************************************************************
* Copyright (c) 2016, 2017 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Roman Grigoriadi
******************************************************************************/
package org.eclipse.yasson.internal;
import org.eclipse.yasson.internal.components.*;
import org.eclipse.yasson.internal.model.JsonBindingModel;
import javax.json.bind.JsonbConfig;
import javax.json.bind.adapter.JsonbAdapter;
import javax.json.bind.serializer.JsonbDeserializer;
import javax.json.bind.serializer.JsonbSerializer;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Searches for a registered components or Serializer for a given type.
*
* @author Roman Grigoriadi
*/
public class ComponentMatcher {
private final JsonbContext jsonbContext;
/**
* Supplier for component binging.
* @param component binding class
*/
private interface ComponentSupplier {
T getComponent(ComponentBindings componentBindings);
}
private final ConcurrentMap userComponents;
/**
* Create component matcher.
* @param context mandatory
*/
ComponentMatcher(JsonbContext context) {
Objects.requireNonNull(context);
this.jsonbContext = context;
userComponents = new ConcurrentHashMap<>();
init();
}
/**
* Called during context creation, introspecting user components provided with JsonbConfig.
*/
void init() {
final JsonbSerializer>[] serializers = (JsonbSerializer>[])jsonbContext.getConfig().getProperty(JsonbConfig.SERIALIZERS).orElseGet(()->new JsonbSerializer>[]{});
for (JsonbSerializer serializer : serializers) {
introspectSerializerBinding(serializer.getClass(), serializer);
}
final JsonbDeserializer>[] deserializers = (JsonbDeserializer>[])jsonbContext.getConfig().getProperty(JsonbConfig.DESERIALIZERS).orElseGet(()->new JsonbDeserializer>[]{});
for (JsonbDeserializer deserializer : deserializers) {
introspectDeserializerBinding(deserializer.getClass(), deserializer);
}
final JsonbAdapter, ?>[] adapters = (JsonbAdapter, ?>[]) jsonbContext.getConfig().getProperty(JsonbConfig.ADAPTERS).orElseGet(()->new JsonbAdapter, ?>[]{});
for (JsonbAdapter, ?> adapter : adapters) {
introspectAdapterBinding(adapter.getClass(), adapter);
}
}
private ComponentBindings getBindingInfo(Type type) {
return userComponents.compute(type, (type1, bindingInfo) -> bindingInfo != null ? bindingInfo : new ComponentBindings(type1));
}
private void addSerializer(Type bindingType, SerializerBinding serializer) {
userComponents.computeIfPresent(bindingType, (type, bindings) -> {
if (bindings.getSerializer() != null) {
return bindings;
}
registerGeneric(bindingType);
return new ComponentBindings(bindingType, serializer, bindings.getDeserializer(), bindings.getAdapterInfo());
});
}
private void addDeserializer(Type bindingType, DeserializerBinding deserializer) {
userComponents.computeIfPresent(bindingType, (type, bindings) -> {
if (bindings.getDeserializer() != null) {
return bindings;
}
registerGeneric(bindingType);
return new ComponentBindings(bindingType, bindings.getSerializer(), deserializer, bindings.getAdapterInfo());
});
}
private void addAdapter(Type bindingType, AdapterBinding adapter) {
userComponents.computeIfPresent(bindingType, (type, bindings) -> {
if (bindings.getAdapterInfo() != null) {
return bindings;
}
registerGeneric(bindingType);
return new ComponentBindings(bindingType, bindings.getSerializer(), bindings.getDeserializer(), adapter);
});
}
/**
* If type is not parametrized runtime component resolution doesn't has to happen.
*
* @param bindingType component binding type
*/
private void registerGeneric(Type bindingType) {
if (bindingType instanceof ParameterizedType && !jsonbContext.genericComponentsPresent()) {
jsonbContext.registerGenericComponentFlag();
}
}
/**
* Lookup serializer binding for a given property runtime type.
* @param propertyRuntimeType runtime type of a property
* @param propertyModel model of a property
* @return serializer optional
*/
@SuppressWarnings("unchecked")
public Optional> getSerializerBinding(Type propertyRuntimeType, JsonBindingModel propertyModel) {
if (propertyModel == null || propertyModel.getCustomization() == null || propertyModel.getCustomization().getSerializerBinding() == null) {
return searchComponentBinding(propertyRuntimeType, ComponentBindings::getSerializer);
}
return getComponentBinding(propertyRuntimeType, propertyModel.getCustomization().getSerializerBinding());
}
/**
* Lookup deserializer binding for a given property runtime type.
* @param propertyRuntimeType runtime type of a property
* @param model model of a property
* @return serializer optional
*/
@SuppressWarnings("unchecked")
public Optional> getDeserializerBinding(Type propertyRuntimeType, JsonBindingModel model) {
if (model == null || model.getCustomization() == null || model.getCustomization().getDeserializerBinding() == null) {
return searchComponentBinding(propertyRuntimeType, ComponentBindings::getDeserializer);
}
return getComponentBinding(propertyRuntimeType, model.getCustomization().getDeserializerBinding());
}
/**
* Get components from property model (if declared by annotation and runtime type matches),
* or return components searched by runtime type
*
* @param propertyRuntimeType runtime type not null
* @param model model nullable
* @return components info if present
*/
public Optional getAdapterBinding(Type propertyRuntimeType, JsonBindingModel model) {
if (model == null || model.getCustomization() == null || model.getCustomization().getAdapterBinding() == null) {
return searchComponentBinding(propertyRuntimeType, ComponentBindings::getAdapterInfo);
}
return getComponentBinding(propertyRuntimeType, model.getCustomization().getAdapterBinding());
}
private Optional getComponentBinding(Type propertyRuntimeType, T componentBinding) {
//need runtime check, ParameterizedType property may have generic components assigned which is not compatible
//for given runtime type
if (matches(propertyRuntimeType, componentBinding.getBindingType())) {
return Optional.of(componentBinding);
}
return Optional.empty();
}
private Optional searchComponentBinding(Type runtimeType, ComponentSupplier supplier) {
for (ComponentBindings componentBindings : userComponents.values()) {
final T component = supplier.getComponent(componentBindings);
if (component != null && matches(runtimeType, componentBindings.getBindingType())) {
return Optional.of(component);
}
}
return Optional.empty();
}
private boolean matches(Type runtimeType, Type componentBindingType) {
if (componentBindingType.equals(runtimeType)) {
return true;
}
//don't try to runtime generic scan if not needed
if (!jsonbContext.genericComponentsPresent()) {
return false;
}
if (componentBindingType instanceof Class && runtimeType instanceof Class) {
//for polymorphic adapters
return ((Class>) componentBindingType).isAssignableFrom((Class) runtimeType);
}
return runtimeType instanceof ParameterizedType && componentBindingType instanceof ParameterizedType &&
ReflectionUtils.getRawType(runtimeType) == ReflectionUtils.getRawType(componentBindingType) &&
matchTypeArguments((ParameterizedType) runtimeType, (ParameterizedType) componentBindingType);
}
/**
* If runtimeType to adapt is a ParametrizedType, check all type args to match against components args.
*/
private boolean matchTypeArguments(ParameterizedType requiredType, ParameterizedType componentBound) {
final Type[] requiredTypeArguments = requiredType.getActualTypeArguments();
final Type[] adapterBoundTypeArguments = componentBound.getActualTypeArguments();
if (requiredTypeArguments.length != adapterBoundTypeArguments.length) {
return false;
}
for(int i = 0; i< requiredTypeArguments.length; i++) {
Type adapterTypeArgument = adapterBoundTypeArguments[i];
if (!requiredTypeArguments[i].equals(adapterTypeArgument)) {
return false;
}
}
return true;
}
/**
* Introspect components generic information and put resolved types into metadata wrapper.
*
* @param adapterClass class of an components
* @param instance components instance
* @return introspected info with resolved typevar types.
*/
AdapterBinding introspectAdapterBinding(Class extends JsonbAdapter> adapterClass, JsonbAdapter instance) {
final ParameterizedType adapterRuntimeType = ReflectionUtils.findParameterizedType(adapterClass, JsonbAdapter.class);
final Type[] adapterTypeArguments = adapterRuntimeType.getActualTypeArguments();
Type adaptFromType = resolveTypeArg(adapterTypeArguments[0], adapterClass);
Type adaptToType = resolveTypeArg(adapterTypeArguments[1], adapterClass);
final ComponentBindings componentBindings = getBindingInfo(adaptFromType);
if (componentBindings.getAdapterInfo() != null && componentBindings.getAdapterInfo().getAdapter().getClass().equals(adapterClass)) {
return componentBindings.getAdapterInfo();
}
JsonbAdapter newAdapter = instance != null ? instance : jsonbContext.getComponentInstanceCreator().getOrCreateComponent(adapterClass);
final AdapterBinding adapterInfo = new AdapterBinding(adaptFromType, adaptToType, newAdapter);
addAdapter(adaptFromType, adapterInfo);
return adapterInfo;
}
/**
* If an instance of deserializerClass is present in context and is bound for same type, return that instance.
* Otherwise create new instance and set it to context.
*
* @param deserializerClass class of deserializer
* @param instance instance to use if not cached already
* @return wrapper used in property models
*/
DeserializerBinding introspectDeserializerBinding(Class extends JsonbDeserializer> deserializerClass, JsonbDeserializer instance) {
final ParameterizedType deserializerRuntimeType = ReflectionUtils.findParameterizedType(deserializerClass, JsonbDeserializer.class);
Type deserializerBindingType = resolveTypeArg(deserializerRuntimeType.getActualTypeArguments()[0], deserializerClass.getClass());
final ComponentBindings componentBindings = getBindingInfo(deserializerBindingType);
if (componentBindings.getDeserializer() != null && componentBindings.getDeserializer().getClass().equals(deserializerClass)) {
return componentBindings.getDeserializer();
} else {
JsonbDeserializer deserializer = instance != null ? instance : jsonbContext.getComponentInstanceCreator()
.getOrCreateComponent(deserializerClass);
final DeserializerBinding deserializerBinding = new DeserializerBinding(deserializerBindingType, deserializer);
addDeserializer(deserializerBindingType, deserializerBinding);
return deserializerBinding;
}
}
/**
* If an instance of serializerClass is present in context and is bound for same type, return that instance.
* Otherwise create new instance and set it to context.
*
* @param serializerClass class of deserializer
* @param instance instance to use if not cached
* @return wrapper used in property models
*/
SerializerBinding introspectSerializerBinding(Class extends JsonbSerializer> serializerClass, JsonbSerializer instance) {
final ParameterizedType serializerRuntimeType = ReflectionUtils.findParameterizedType(serializerClass, JsonbSerializer.class);
Type serBindingType = resolveTypeArg(serializerRuntimeType.getActualTypeArguments()[0], serializerClass.getClass());
final ComponentBindings componentBindings = getBindingInfo(serBindingType);
if (componentBindings.getSerializer() != null && componentBindings.getSerializer().getClass().equals(serializerClass)) {
return componentBindings.getSerializer();
} else {
JsonbSerializer serializer = instance != null ? instance : jsonbContext.getComponentInstanceCreator()
.getOrCreateComponent(serializerClass);
final SerializerBinding serializerBinding = new SerializerBinding(serBindingType, serializer);
addSerializer(serBindingType, serializerBinding);
return serializerBinding;
}
}
private Type resolveTypeArg(Type adapterTypeArg, Type adapterType) {
if(adapterTypeArg instanceof ParameterizedType) {
return ReflectionUtils.resolveTypeArguments((ParameterizedType) adapterTypeArg, adapterType);
} else if (adapterTypeArg instanceof TypeVariable) {
return ReflectionUtils.resolveItemVariableType(new RuntimeTypeHolder(null, adapterType), (TypeVariable>) adapterTypeArg);
} else {
return adapterTypeArg;
}
}
}